Rustc copying cryptographic keys onto the stack instead of using them via pointer

I agree it would be nice if rustc generated IR that didn't use pointers for intrinsics like this.

But on the other hand, I'll reiterate that 'secret values will not be written to the stack' is fundamentally not a security property that LLVM tries to guarantee. The same is true for GCC and other highly optimizing backends. This is an extreme case because the codegen is not just insecure but also inefficient – but if it weren't inefficient, I wouldn't call this a bug in LLVM or rustc.

Also, direct stores to the stack are not the only way values can be leaked. If you look at the generated assembly, it doesn't just leave secret values on the stack, it also leaves parts of them in caller-saved xmm registers. Depending on the attack scenario, it may be possible to retrieve those registers directly, or they may be spilled to the stack by another operation (e.g. C variadic functions tend to dump xmm0-xmm7 to the stack). If on the other hand the function uses callee-saved xmm registers, then those will be restored once the function returns, but if the function calls something else first, the second function may save them to the stack. Even if the original function does not make any calls, it might do so after inlining or outlining. (The x86-64 Unix ABI doesn't actually make any xmm registers callee-saved, but the x86-64 Windows ABI does, and – if you happen to be writing similar code on ARM – the standard ARM ABI makes some vector registers callee-saved.)

Ideally LLVM would have an option to zero the stack and registers before returning, which would effectively mitigate most kinds of leakage, including the stack spills originally being discussed. This has been proposed before in the context of Rust, and (entirely separately) someone made a prototype patch to LLVM a few years ago, to implement this for C. But as far as I know there's nothing upstream. In lieu of that, if you want to reliably prevent key material from being leaked to the stack, you really should be using functions written entirely in assembly.

Though there is one more source of leakage: signal handlers, which can interrupt any code at any point and can store both caller-saved and callee-saved registers to the stack. There is no good way to prevent that, if you are a library running in some arbitrary program that might be using signal handlers. At best you could clean some region below the stack before returning, in the hope that it covers whatever a signal handler might have been using, if one happened to interrupt your function. But that won't work if the signal handler is using sigaltstack, or other OS-specific signal-like mechanisms that might save data somewhere other than the current stack. Of course, the fact that there may be leakage you can't control doesn't mean you shouldn't try to control what you can control, but… just saying.

8 Likes