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