Go feature: Secret mode
Part of the Accepted! series: Go proposals and features explained in simple terms.
Automatically erase used memory to prevent secret leaks.
Ver. 1.26 • Stdlib • Low impact
Summary
The new runtime/secret package lets you run a function in secret mode. After the function finishes, it immediately erases (zeroes out) the registers and stack it used. Heap allocations made by the function are erased as soon as the garbage collector decides they are no longer reachable.
secret.Do(func() {
// Generate an ephemeral key and
// use it to negotiate the session.
})
This helps make sure sensitive information doesn't stay in memory longer than needed, lowering the risk of attackers getting to it.
The package is experimental and is mainly for developers of cryptographic libraries, not for application developers.
Motivation
Cryptographic protocols like WireGuard or TLS have a property called "forward secrecy". This means that even if an attacker gains access to long-term secrets (like a private key in TLS), they shouldn't be able to decrypt past communication sessions. To make this work, ephemeral keys (temporary keys used to negotiate the session) need to be erased from memory immediately after the handshake. If there's no reliable way to clear this memory, these keys could stay there indefinitely. An attacker who finds them later could re-derive the session key and decrypt past traffic, breaking forward secrecy.
In Go, the runtime manages memory, and it doesn't guarantee when or how memory is cleared. Sensitive data might remain in heap allocations or stack frames, potentially exposed in core dumps or through memory attacks. Developers often have to use unreliable "hacks" with reflection to try to zero out internal buffers in cryptographic libraries. Even so, some data might still stay in memory where the developer can't reach or control it.
The solution is to provide a runtime mechanism that automatically erases all temporary storage used during sensitive operations. This will make it easier for library developers to write secure code without using workarounds.
Description
Add the runtime/secret package with Do and Enabled functions:
// Do invokes f.
//
// Do ensures that any temporary storage used by f is erased in a
// timely manner. (In this context, "f" is shorthand for the
// entire call tree initiated by f.)
// - Any registers used by f are erased before Do returns.
// - Any stack used by f is erased before Do returns.
// - Any heap allocation done by f is erased as soon as the garbage
// collector realizes that it is no longer reachable.
// - Do works even if f panics or calls runtime.Goexit. As part of
// that, any panic raised by f will appear as if it originates from
// Do itself.
func Do(f func())
// Enabled reports whether Do appears anywhere on the call stack.
func Enabled() bool
The current implementation has several limitations:
- Only supported on linux/amd64 and linux/arm64. On unsupported platforms,
Doinvokesfdirectly. - Protection does not cover any global variables that
fwrites to. - Trying to start a goroutine within
fcauses a panic. - If
fcallsruntime.Goexit, erasure is delayed until all deferred functions are executed. - Heap allocations are only erased if ➊ the program drops all references to them, and ➋ then the garbage collector notices that those references are gone. The program controls the first part, but the second part depends on when the runtime decides to act.
- If
fpanics, the panicked value might reference memory allocated insidef. That memory won't be erased until (at least) the panicked value is no longer reachable. - Pointer addresses might leak into data buffers that the runtime uses for garbage collection. Do not put confidential information into pointers.
Confidential information in pointers
Imagine you're working on a cryptographic algorithm that uses a lookup table. You have a public table array and a secret byte b taken from your private key. You might be tempted to create a pointer to the result:
// BAD: The address stored in p depends on the secret b.
p := &table[b]
In this case, the pointer p stores the memory address AddressOf(table) + b. Since the garbage collector tracks all active pointers, it might save this address in its internal buffers. If an attacker can access the GC's memory, they could see the address in p, subtract the base address of table, and figure out your secret byte b.
The package is mainly for developers who work on cryptographic libraries. Most apps should use higher-level libraries that use secret.Do behind the scenes.
As of Go 1.26, the runtime/secret package is experimental and can be enabled by setting GOEXPERIMENT=runtimesecret at build time.
Example
Generate a session key while keeping the ephemeral private key and shared secret safe using secret.Do:
// DeriveSessionKey performs an ephemeral key exchange to agree on a
// session key. It wraps the sensitive mathematics in secret.Do to
// ensure the ephemeral private key and raw shared secret are
// erased from memory immediately after use.
func DeriveSessionKey(peerPublicKey *ecdh.PublicKey) (*ecdh.PublicKey, []byte, error) {
var pubKey *ecdh.PublicKey
var sessionKey []byte
var err error
// Use secret.Do to contain the sensitive data during the handshake.
// The ephemeral private key and the raw shared secret will be
// wiped out when this function finishes.
secret.Do(func() {
// 1. Generate an ephemeral private key.
// This is highly sensitive; if leaked later, forward secrecy is broken.
privKey, e := ecdh.P256().GenerateKey(rand.Reader)
if e != nil {
err = e
return
}
// 2. Compute the shared secret (ECDH).
// This raw secret is also highly sensitive.
sharedSecret, e := privKey.ECDH(peerPublicKey)
if e != nil {
err = e
return
}
// 3. Derive the final session key (e.g., using HKDF).
// We copy the result out; the inputs (privKey, sharedSecret)
// will be destroyed by secret.Do when they become unreachable.
sessionKey = performHKDF(sharedSecret)
pubKey = privKey.PublicKey()
})
// The session key is returned for use, but the "recipe" to recreate it
// is destroyed. Additionally, because the session key was allocated
// inside the secret block, the runtime will automatically zero it out
// when the application is finished using it.
return pubKey, sessionKey, err
}
Here, the ephemeral private key and the raw shared secret are effectively "toxic waste" — they are necessary to create the final session key, but dangerous to keep around.
If these values stay in the heap and an attacker later gets access to the application's memory (for example, via a core dump or a vulnerability like Heartbleed), they could use these intermediates to re-derive the session key and decrypt past conversations.
By wrapping the calculation in secret.Do, we make sure that as soon as the session key is created, the "ingredients" used to make it are permanently destroyed. This means that even if the server is compromised in the future, this specific past session can't be exposed, which ensures forward secrecy.
Links & Credits
𝗣 21865 👥 Dave Anderson, Filippo Valsorda, Jason A. Donenfeld, Russ Cox
𝗖𝗟 704615 👥 Daniel Morsing, Keith Randall
★ Subscribe to keep up with new posts.