Source file src/runtime/secret/secret.go

     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 goexperiment.runtimesecret
     6  
     7  package secret
     8  
     9  import (
    10  	"runtime"
    11  	_ "unsafe"
    12  )
    13  
    14  // Do invokes f.
    15  //
    16  // Do ensures that any temporary storage used by f is erased in a
    17  // timely manner. (In this context, "f" is shorthand for the
    18  // entire call tree initiated by f.)
    19  //   - Any registers used by f are erased before Do returns.
    20  //   - Any stack used by f is erased before Do returns.
    21  //   - Heap allocations done by f are erased as soon as the garbage
    22  //     collector realizes that all allocated values are no longer reachable.
    23  //   - Do works even if f panics or calls runtime.Goexit.  As part of
    24  //     that, any panic raised by f will appear as if it originates from
    25  //     Do itself.
    26  //
    27  // Users should be cautious of allocating inside Do.
    28  // Erasing heap memory after Do returns may increase garbage collector sweep times and
    29  // requires additional memory to keep track of allocations until they are to be erased.
    30  // These costs can compound when an allocation is done in the service of growing a value,
    31  // like appending to a slice or inserting into a map. In these cases, the entire new allocation is erased rather
    32  // than just the secret parts of it.
    33  //
    34  // To reduce lifetimes of allocations and avoid unexpected performance issues,
    35  // if a function invoked by Do needs to yield a result that shouldn't be erased,
    36  // it should do so by copying the result into an allocation created by the caller.
    37  //
    38  // Limitations:
    39  //   - Currently only supported on linux/amd64 and linux/arm64.  On unsupported
    40  //     platforms, Do will invoke f directly.
    41  //   - Protection does not extend to any global variables written by f.
    42  //   - Protection does not extend to any new goroutines made by f.
    43  //   - If f calls runtime.Goexit, erasure can be delayed by defers
    44  //     higher up on the call stack.
    45  //   - Heap allocations will only be erased if the program drops all
    46  //     references to those allocations, and then the garbage collector
    47  //     notices that those references are gone. The former is under
    48  //     control of the program, but the latter is at the whim of the
    49  //     runtime.
    50  //   - Any value panicked by f may point to allocations from within
    51  //     f. Those allocations will not be erased until (at least) the
    52  //     panicked value is dead.
    53  //   - Pointer addresses may leak into data buffers used by the runtime
    54  //     to perform garbage collection. Users should not encode confidential
    55  //     information into pointers. For example, if an offset into an array or
    56  //     struct is confidential, then users should not create a pointer into
    57  //     the object. Since this function is intended to be used with constant-time
    58  //     cryptographic code, this requirement is usually fulfilled implicitly.
    59  func Do(f func()) {
    60  	const osArch = runtime.GOOS + "/" + runtime.GOARCH
    61  	switch osArch {
    62  	default:
    63  		// unsupported, just invoke f directly.
    64  		f()
    65  		return
    66  	case "linux/amd64", "linux/arm64":
    67  	}
    68  
    69  	// Place to store any panic value.
    70  	var p any
    71  
    72  	// Step 1: increment the nesting count.
    73  	inc()
    74  
    75  	// Step 2: call helper. The helper just calls f
    76  	// and captures (recovers) any panic result.
    77  	p = doHelper(f)
    78  
    79  	// Step 3: erase everything used by f (stack, registers).
    80  	eraseSecrets()
    81  
    82  	// Step 4: decrement the nesting count.
    83  	dec()
    84  
    85  	// Step 5: re-raise any caught panic.
    86  	// This will make the panic appear to come
    87  	// from a stack whose bottom frame is
    88  	// runtime/secret.Do.
    89  	// Anything below that to do with f will be gone.
    90  	//
    91  	// Note that the panic value is not erased. It behaves
    92  	// like any other value that escapes from f. If it is
    93  	// heap allocated, it will be erased when the garbage
    94  	// collector notices it is no longer referenced.
    95  	if p != nil {
    96  		panic(p)
    97  	}
    98  
    99  	// Note: if f calls runtime.Goexit, step 3 and above will not
   100  	// happen, as Goexit is unrecoverable. We handle that case in
   101  	// runtime/proc.go:goexit0.
   102  }
   103  
   104  func doHelper(f func()) (p any) {
   105  	// Step 2b: Pop the stack up to the secret.doHelper frame
   106  	// if we are in the process of panicking.
   107  	// (It is a no-op if we are not panicking.)
   108  	// We return any panicked value to secret.Do, who will
   109  	// re-panic it.
   110  	defer func() {
   111  		// Note: we rely on the go1.21+ behavior that
   112  		// if we are panicking, recover returns non-nil.
   113  		p = recover()
   114  	}()
   115  
   116  	// Step 2a: call the secret function.
   117  	f()
   118  
   119  	return
   120  }
   121  
   122  // Enabled reports whether [Do] appears anywhere on the call stack.
   123  func Enabled() bool {
   124  	return count() > 0
   125  }
   126  
   127  // implemented in runtime
   128  
   129  //go:linkname count
   130  func count() int32
   131  
   132  //go:linkname inc
   133  func inc()
   134  
   135  //go:linkname dec
   136  func dec()
   137  
   138  //go:linkname eraseSecrets
   139  func eraseSecrets()
   140  

View as plain text