]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/subtle: add DIT closure
authorRoland Shoemaker <roland@golang.org>
Mon, 15 Jul 2024 17:05:37 +0000 (10:05 -0700)
committerRoland Shoemaker <roland@golang.org>
Tue, 19 Nov 2024 16:47:03 +0000 (16:47 +0000)
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 <mknyszek@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

12 files changed:
api/next/66450.txt [new file with mode: 0644]
doc/godebug.md
doc/next/6-stdlib/99-minor/crypto/subtle/66450.md [new file with mode: 0644]
src/crypto/subtle/dit.go [new file with mode: 0644]
src/crypto/subtle/dit_test.go [new file with mode: 0644]
src/internal/godebugs/table.go
src/internal/runtime/sys/dit_arm64.go [new file with mode: 0644]
src/internal/runtime/sys/dit_arm64.s [new file with mode: 0644]
src/internal/runtime/sys/no_dit.go [new file with mode: 0644]
src/runtime/cgocall.go
src/runtime/proc.go
src/runtime/runtime1.go

diff --git a/api/next/66450.txt b/api/next/66450.txt
new file mode 100644 (file)
index 0000000..3b2daef
--- /dev/null
@@ -0,0 +1 @@
+pkg crypto/subtle, func WithDataIndependentTiming(func()) #66450
index c5e9491aab400feb6118bfa871cb5b342017c905..b9abfea898837bc975f573790b94ed1a9119b606 100644 (file)
@@ -177,6 +177,17 @@ This behavior can be controlled with the `gotestjsonbuildtext` setting.
 Using `gotestjsonbuildtext=1` restores the 1.23 behavior.
 This setting will be removed in a future release, Go 1.28 at the earliest.
 
+Go 1.24 introduced a mechanism for enabling platform specific Data Independent
+Timing (DIT) modes in the [`crypto/subtle`](/pkg/crypto/subtle) package. This
+mode can be enabled for an entire program with the `dataindependenttiming` setting.
+For Go 1.24 it defaults to `dataindependenttiming=0`. There is no change in default
+behavior from Go 1.23 when `dataindependenttiming` is unset.
+Using `dataindependenttiming=1` enables the DIT mode for the entire Go program.
+When enabled, DIT will be enabled when calling into C from Go. When enabled,
+calling into Go code from C will enable DIT, and disable it before returning to
+C if it was not enabled when Go code was entered.
+This currently only affects arm64 programs. For all other platforms it is a no-op.
+
 ### Go 1.23
 
 Go 1.23 changed the channels created by package time to be unbuffered
diff --git a/doc/next/6-stdlib/99-minor/crypto/subtle/66450.md b/doc/next/6-stdlib/99-minor/crypto/subtle/66450.md
new file mode 100644 (file)
index 0000000..353594b
--- /dev/null
@@ -0,0 +1,6 @@
+The [WithDataIndependentTiming] function allows the user to run a function with
+architecture specific features enabled which guarantee specific instructions are
+data value timing invariant. This can be used to make sure that code designed to
+run in constant time is not optimized by CPU-level features such that it
+operates in variable time. Currently, [WithDataIndependentTiming] uses the
+PSTATE.DIT bit on arm64, and is a no-op on all other architectures.
\ No newline at end of file
diff --git a/src/crypto/subtle/dit.go b/src/crypto/subtle/dit.go
new file mode 100644 (file)
index 0000000..c23df97
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package subtle
+
+import (
+       "internal/runtime/sys"
+       "runtime"
+)
+
+// WithDataIndependentTiming enables architecture specific features which ensure
+// that the timing of specific instructions is independent of their inputs
+// before executing f. On f returning it disables these features.
+//
+// WithDataIndependentTiming should only be used when f is written to make use
+// of constant-time operations. WithDataIndependentTiming does not make
+// variable-time code constant-time.
+//
+// WithDataIndependentTiming may lock the current goroutine to the OS thread for
+// the duration of f. Calls to WithDataIndependentTiming may be nested.
+//
+// On Arm64 processors with FEAT_DIT, WithDataIndependentTiming enables
+// PSTATE.DIT. See https://developer.arm.com/documentation/ka005181/1-0/?lang=en.
+//
+// Currently, on all other architectures WithDataIndependentTiming executes f immediately
+// with no other side-effects.
+//
+//go:noinline
+func WithDataIndependentTiming(f func()) {
+       if !sys.DITSupported {
+               f()
+               return
+       }
+
+       runtime.LockOSThread()
+       defer runtime.UnlockOSThread()
+
+       alreadyEnabled := sys.EnableDIT()
+
+       // disableDIT is called in a deferred function so that if f panics we will
+       // still disable DIT, in case the panic is recovered further up the stack.
+       defer func() {
+               if !alreadyEnabled {
+                       sys.DisableDIT()
+               }
+       }()
+
+       f()
+}
diff --git a/src/crypto/subtle/dit_test.go b/src/crypto/subtle/dit_test.go
new file mode 100644 (file)
index 0000000..8753ed6
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package subtle
+
+import (
+       "internal/cpu"
+       "internal/runtime/sys"
+       "testing"
+)
+
+func TestWithDataIndependentTiming(t *testing.T) {
+       if !cpu.ARM64.HasDIT {
+               t.Skip("CPU does not support DIT")
+       }
+
+       WithDataIndependentTiming(func() {
+               if !sys.DITEnabled() {
+                       t.Fatal("dit not enabled within WithDataIndependentTiming closure")
+               }
+
+               WithDataIndependentTiming(func() {
+                       if !sys.DITEnabled() {
+                               t.Fatal("dit not enabled within nested WithDataIndependentTiming closure")
+                       }
+               })
+
+               if !sys.DITEnabled() {
+                       t.Fatal("dit not enabled after return from nested WithDataIndependentTiming closure")
+               }
+       })
+
+       if sys.DITEnabled() {
+               t.Fatal("dit not unset after returning from WithDataIndependentTiming closure")
+       }
+}
+
+func TestDITPanic(t *testing.T) {
+       if !cpu.ARM64.HasDIT {
+               t.Skip("CPU does not support DIT")
+       }
+
+       defer func() {
+               e := recover()
+               if e == nil {
+                       t.Fatal("didn't panic")
+               }
+               if sys.DITEnabled() {
+                       t.Error("DIT still enabled after panic inside of WithDataIndependentTiming closure")
+               }
+       }()
+
+       WithDataIndependentTiming(func() {
+               if !sys.DITEnabled() {
+                       t.Fatal("dit not enabled within WithDataIndependentTiming closure")
+               }
+
+               panic("bad")
+       })
+}
index d00014eaaef4bb0487062eed5cd9da70cfd80b8f..da6ca78773761ddedbdfb80faec027e774577f24 100644 (file)
@@ -26,6 +26,7 @@ type Info struct {
 // (Otherwise the test in this package will fail.)
 var All = []Info{
        {Name: "asynctimerchan", Package: "time", Changed: 23, Old: "1"},
+       {Name: "dataindependenttiming", Package: "crypto/subtle", Opaque: true},
        {Name: "execerrdot", Package: "os/exec"},
        {Name: "gocachehash", Package: "cmd/go"},
        {Name: "gocachetest", Package: "cmd/go"},
diff --git a/src/internal/runtime/sys/dit_arm64.go b/src/internal/runtime/sys/dit_arm64.go
new file mode 100644 (file)
index 0000000..643fd77
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build arm64
+
+package sys
+
+import (
+       "internal/cpu"
+)
+
+var DITSupported = cpu.ARM64.HasDIT
+
+func EnableDIT() bool
+func DITEnabled() bool
+func DisableDIT()
diff --git a/src/internal/runtime/sys/dit_arm64.s b/src/internal/runtime/sys/dit_arm64.s
new file mode 100644 (file)
index 0000000..fcb44d6
--- /dev/null
@@ -0,0 +1,18 @@
+#include "textflag.h"
+
+TEXT ·EnableDIT(SB),$0-1
+    MRS DIT, R0
+    UBFX $24, R0, $1, R1
+    MOVB R1, ret+0(FP)
+    MSR $1, DIT
+    RET
+
+TEXT ·DITEnabled(SB),$0-1
+    MRS DIT, R0
+    UBFX $24, R0, $1, R1
+    MOVB R1, ret+0(FP)
+    RET
+
+TEXT ·DisableDIT(SB),$0
+    MSR $0, DIT
+    RET
diff --git a/src/internal/runtime/sys/no_dit.go b/src/internal/runtime/sys/no_dit.go
new file mode 100644 (file)
index 0000000..0589d0c
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !arm64
+
+package sys
+
+var DITSupported = false
+
+func EnableDIT() bool  { return false }
+func DITEnabled() bool { return false }
+func DisableDIT()      {}
index 0effcb8053a56112d1b14c0e156218526bff7296..326674cd2e6e1671fd9576a08f98b3d84ca0c572 100644 (file)
@@ -425,6 +425,13 @@ func cgocallbackg1(fn, frame unsafe.Pointer, ctxt uintptr) {
        restore := true
        defer unwindm(&restore)
 
+       var ditAlreadySet bool
+       if debug.dataindependenttiming == 1 && gp.m.isextra {
+               // We only need to enable DIT for threads that were created by C, as it
+               // should already by enabled on threads that were created by Go.
+               ditAlreadySet = sys.EnableDIT()
+       }
+
        if raceenabled {
                raceacquire(unsafe.Pointer(&racecgosync))
        }
@@ -440,6 +447,11 @@ func cgocallbackg1(fn, frame unsafe.Pointer, ctxt uintptr) {
                racereleasemerge(unsafe.Pointer(&racecgosync))
        }
 
+       if debug.dataindependenttiming == 1 && !ditAlreadySet {
+               // Only unset DIT if it wasn't already enabled when cgocallback was called.
+               sys.DisableDIT()
+       }
+
        // Do not unwind m->g0->sched.sp.
        // Our caller, cgocallback, will do that.
        restore = false
index 3f360ef1291916703d0c5312e9a1a5fb5abf996d..17c375de1ab6d1e30b67eb90f249d8ffc036f769 100644 (file)
@@ -1848,6 +1848,10 @@ func mstart1() {
                mstartm0()
        }
 
+       if debug.dataindependenttiming == 1 {
+               sys.EnableDIT()
+       }
+
        if fn := gp.m.mstartfn; fn != nil {
                fn()
        }
index 56886ea571f109c1e9131fe7f131f5c9cb45350d..7a092e80390007d0366083524f0b92a773c8ab3a 100644 (file)
@@ -331,6 +331,7 @@ var debug struct {
        traceadvanceperiod       int32
        traceCheckStackOwnership int32
        profstackdepth           int32
+       dataindependenttiming    int32
 
        // debug.malloc is used as a combined debug check
        // in the malloc function and should be set
@@ -367,6 +368,7 @@ var dbgvars = []*dbgVar{
        {name: "asynctimerchan", atomic: &debug.asynctimerchan},
        {name: "cgocheck", value: &debug.cgocheck},
        {name: "clobberfree", value: &debug.clobberfree},
+       {name: "dataindependenttiming", value: &debug.dataindependenttiming},
        {name: "disablethp", value: &debug.disablethp},
        {name: "dontfreezetheworld", value: &debug.dontfreezetheworld},
        {name: "efence", value: &debug.efence},