Skip to content

Commit bc1da38

Browse files
crypto/subtle: add DIT closure
Add a new function, WithDataIndependentTiming, which takes a function as an argument, and encloses it with calls to set/unset the DIT PSTATE bit on Arm64. Since DIT is OS thread-local, for the duration of the execution of WithDataIndependentTiming, we lock the goroutine to the OS thread, using LockOSThread. For long running operations, this is likely to not be performant, but we expect this to be tightly scoped around cryptographic operations that have bounded execution times. If locking to the OS thread turns out to be too slow, another option is to add a bit to the g state indicating if a goroutine has DIT enabled, and then have the scheduler enable/disable DIT when scheduling a g. Additionally, we add a new GODEBUG, dataindependenttiming, which allows setting DIT for an entire program. Running a program with dataindependenttiming=1 enables DIT for the program during initialization. In an ideal world PSTATE.DIT would be inherited from the parent thread, so we'd only need to set it in the main thread and then all subsequent threads would inherit the value. While this does happen in the Linux kernel [0], it is not the case for darwin [1]. Rather than add complex logic to only set it on darwin for each new thread, we just unconditionally set it in mstart1 and cgocallbackg1 regardless of the OS. DIT will already impose some overhead, and the cost of setting the bit is only ~two instructions (CALL, MSR), so it should be cheap enough. Fixes #66450 Updates #49702 [0] https://github.com/torvalds/linux/blob/e8bdb3c8be08c9a3edc0a373c0aa8729355a0705/arch/arm64/kernel/process.c#L373 [1] https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/osfmk/arm64/status.c#L1666 Change-Id: I78eda691ff9254b0415f2b54770e5850a0179749 Reviewed-on: https://go-review.googlesource.com/c/go/+/598336 Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> Reviewed-by: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 44cea12 commit bc1da38

File tree

12 files changed

+196
-0
lines changed

12 files changed

+196
-0
lines changed

api/next/66450.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg crypto/subtle, func WithDataIndependentTiming(func()) #66450

doc/godebug.md

+11
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,17 @@ This behavior can be controlled with the `gotestjsonbuildtext` setting.
177177
Using `gotestjsonbuildtext=1` restores the 1.23 behavior.
178178
This setting will be removed in a future release, Go 1.28 at the earliest.
179179

180+
Go 1.24 introduced a mechanism for enabling platform specific Data Independent
181+
Timing (DIT) modes in the [`crypto/subtle`](/pkg/crypto/subtle) package. This
182+
mode can be enabled for an entire program with the `dataindependenttiming` setting.
183+
For Go 1.24 it defaults to `dataindependenttiming=0`. There is no change in default
184+
behavior from Go 1.23 when `dataindependenttiming` is unset.
185+
Using `dataindependenttiming=1` enables the DIT mode for the entire Go program.
186+
When enabled, DIT will be enabled when calling into C from Go. When enabled,
187+
calling into Go code from C will enable DIT, and disable it before returning to
188+
C if it was not enabled when Go code was entered.
189+
This currently only affects arm64 programs. For all other platforms it is a no-op.
190+
180191
### Go 1.23
181192

182193
Go 1.23 changed the channels created by package time to be unbuffered
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
The [WithDataIndependentTiming] function allows the user to run a function with
2+
architecture specific features enabled which guarantee specific instructions are
3+
data value timing invariant. This can be used to make sure that code designed to
4+
run in constant time is not optimized by CPU-level features such that it
5+
operates in variable time. Currently, [WithDataIndependentTiming] uses the
6+
PSTATE.DIT bit on arm64, and is a no-op on all other architectures.

src/crypto/subtle/dit.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package subtle
6+
7+
import (
8+
"internal/runtime/sys"
9+
"runtime"
10+
)
11+
12+
// WithDataIndependentTiming enables architecture specific features which ensure
13+
// that the timing of specific instructions is independent of their inputs
14+
// before executing f. On f returning it disables these features.
15+
//
16+
// WithDataIndependentTiming should only be used when f is written to make use
17+
// of constant-time operations. WithDataIndependentTiming does not make
18+
// variable-time code constant-time.
19+
//
20+
// WithDataIndependentTiming may lock the current goroutine to the OS thread for
21+
// the duration of f. Calls to WithDataIndependentTiming may be nested.
22+
//
23+
// On Arm64 processors with FEAT_DIT, WithDataIndependentTiming enables
24+
// PSTATE.DIT. See https://developer.arm.com/documentation/ka005181/1-0/?lang=en.
25+
//
26+
// Currently, on all other architectures WithDataIndependentTiming executes f immediately
27+
// with no other side-effects.
28+
//
29+
//go:noinline
30+
func WithDataIndependentTiming(f func()) {
31+
if !sys.DITSupported {
32+
f()
33+
return
34+
}
35+
36+
runtime.LockOSThread()
37+
defer runtime.UnlockOSThread()
38+
39+
alreadyEnabled := sys.EnableDIT()
40+
41+
// disableDIT is called in a deferred function so that if f panics we will
42+
// still disable DIT, in case the panic is recovered further up the stack.
43+
defer func() {
44+
if !alreadyEnabled {
45+
sys.DisableDIT()
46+
}
47+
}()
48+
49+
f()
50+
}

src/crypto/subtle/dit_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package subtle
6+
7+
import (
8+
"internal/cpu"
9+
"internal/runtime/sys"
10+
"testing"
11+
)
12+
13+
func TestWithDataIndependentTiming(t *testing.T) {
14+
if !cpu.ARM64.HasDIT {
15+
t.Skip("CPU does not support DIT")
16+
}
17+
18+
WithDataIndependentTiming(func() {
19+
if !sys.DITEnabled() {
20+
t.Fatal("dit not enabled within WithDataIndependentTiming closure")
21+
}
22+
23+
WithDataIndependentTiming(func() {
24+
if !sys.DITEnabled() {
25+
t.Fatal("dit not enabled within nested WithDataIndependentTiming closure")
26+
}
27+
})
28+
29+
if !sys.DITEnabled() {
30+
t.Fatal("dit not enabled after return from nested WithDataIndependentTiming closure")
31+
}
32+
})
33+
34+
if sys.DITEnabled() {
35+
t.Fatal("dit not unset after returning from WithDataIndependentTiming closure")
36+
}
37+
}
38+
39+
func TestDITPanic(t *testing.T) {
40+
if !cpu.ARM64.HasDIT {
41+
t.Skip("CPU does not support DIT")
42+
}
43+
44+
defer func() {
45+
e := recover()
46+
if e == nil {
47+
t.Fatal("didn't panic")
48+
}
49+
if sys.DITEnabled() {
50+
t.Error("DIT still enabled after panic inside of WithDataIndependentTiming closure")
51+
}
52+
}()
53+
54+
WithDataIndependentTiming(func() {
55+
if !sys.DITEnabled() {
56+
t.Fatal("dit not enabled within WithDataIndependentTiming closure")
57+
}
58+
59+
panic("bad")
60+
})
61+
}

src/internal/godebugs/table.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Info struct {
2626
// (Otherwise the test in this package will fail.)
2727
var All = []Info{
2828
{Name: "asynctimerchan", Package: "time", Changed: 23, Old: "1"},
29+
{Name: "dataindependenttiming", Package: "crypto/subtle", Opaque: true},
2930
{Name: "execerrdot", Package: "os/exec"},
3031
{Name: "gocachehash", Package: "cmd/go"},
3132
{Name: "gocachetest", Package: "cmd/go"},

src/internal/runtime/sys/dit_arm64.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build arm64
6+
7+
package sys
8+
9+
import (
10+
"internal/cpu"
11+
)
12+
13+
var DITSupported = cpu.ARM64.HasDIT
14+
15+
func EnableDIT() bool
16+
func DITEnabled() bool
17+
func DisableDIT()

src/internal/runtime/sys/dit_arm64.s

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include "textflag.h"
2+
3+
TEXT ·EnableDIT(SB),$0-1
4+
MRS DIT, R0
5+
UBFX $24, R0, $1, R1
6+
MOVB R1, ret+0(FP)
7+
MSR $1, DIT
8+
RET
9+
10+
TEXT ·DITEnabled(SB),$0-1
11+
MRS DIT, R0
12+
UBFX $24, R0, $1, R1
13+
MOVB R1, ret+0(FP)
14+
RET
15+
16+
TEXT ·DisableDIT(SB),$0
17+
MSR $0, DIT
18+
RET

src/internal/runtime/sys/no_dit.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !arm64
6+
7+
package sys
8+
9+
var DITSupported = false
10+
11+
func EnableDIT() bool { return false }
12+
func DITEnabled() bool { return false }
13+
func DisableDIT() {}

src/runtime/cgocall.go

+12
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,13 @@ func cgocallbackg1(fn, frame unsafe.Pointer, ctxt uintptr) {
425425
restore := true
426426
defer unwindm(&restore)
427427

428+
var ditAlreadySet bool
429+
if debug.dataindependenttiming == 1 && gp.m.isextra {
430+
// We only need to enable DIT for threads that were created by C, as it
431+
// should already by enabled on threads that were created by Go.
432+
ditAlreadySet = sys.EnableDIT()
433+
}
434+
428435
if raceenabled {
429436
raceacquire(unsafe.Pointer(&racecgosync))
430437
}
@@ -440,6 +447,11 @@ func cgocallbackg1(fn, frame unsafe.Pointer, ctxt uintptr) {
440447
racereleasemerge(unsafe.Pointer(&racecgosync))
441448
}
442449

450+
if debug.dataindependenttiming == 1 && !ditAlreadySet {
451+
// Only unset DIT if it wasn't already enabled when cgocallback was called.
452+
sys.DisableDIT()
453+
}
454+
443455
// Do not unwind m->g0->sched.sp.
444456
// Our caller, cgocallback, will do that.
445457
restore = false

src/runtime/proc.go

+4
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,10 @@ func mstart1() {
18481848
mstartm0()
18491849
}
18501850

1851+
if debug.dataindependenttiming == 1 {
1852+
sys.EnableDIT()
1853+
}
1854+
18511855
if fn := gp.m.mstartfn; fn != nil {
18521856
fn()
18531857
}

src/runtime/runtime1.go

+2
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ var debug struct {
331331
traceadvanceperiod int32
332332
traceCheckStackOwnership int32
333333
profstackdepth int32
334+
dataindependenttiming int32
334335

335336
// debug.malloc is used as a combined debug check
336337
// in the malloc function and should be set
@@ -367,6 +368,7 @@ var dbgvars = []*dbgVar{
367368
{name: "asynctimerchan", atomic: &debug.asynctimerchan},
368369
{name: "cgocheck", value: &debug.cgocheck},
369370
{name: "clobberfree", value: &debug.clobberfree},
371+
{name: "dataindependenttiming", value: &debug.dataindependenttiming},
370372
{name: "disablethp", value: &debug.disablethp},
371373
{name: "dontfreezetheworld", value: &debug.dontfreezetheworld},
372374
{name: "efence", value: &debug.efence},

0 commit comments

Comments
 (0)