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