In 2014 I gave a talk on cryptography in Rust, highlighting how LLVM can look at cryptographic code which uses bitwise masking in an attempt to be constant-time and decide to insert a branch:
Today, we just published a security advisory for curve25519-dalek
where such a problem was occurring:
This is of course not a Rust-specific problem, but more of an LLVM problem (and really, any sufficiently smart optimizing compiler). Some cryptographers recently encountered a similar problem with Clang when compiling the reference implementation of Kyber, a new post quantum key exchange algorithm:
https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/hqbtIGFKIpU/m/cnE3pbueBgAJ
What would be really handy here, short of full-blown secret types which hint to LLVM that they shouldn't be branched upon, is literally any kind of optimization barrier considered "approved" for cryptographic use.
On the Kyber thread, they mitigated the problem using a volatile read, and we used a similar approach for the curve25519-dalek
fix, with the idea that compilers won't elide volatile reads.
We defined our own black_box
function because core::hint::black_box
has a big scary warning on it
Programs cannot rely on
black_box
for correctness , beyond it behaving as the identity function. As such, it must not be relied upon to control critical program behavior. This immediately precludes any direct use of this function for cryptographic or security purposes.
So that really leaves us with nothing short of volatile reads, and even building our own black_box
with it feels like an off-label use. Now the thing is, core::hint::black_box
fixes the specific problem of LLVM inserting jns
instructions on x86
/x86_64
targets, but if we were to use it, someone would probably open an issue pointing to the documentation that it isn't approved for cryptographic use.
Looking at the fix though, it still feels quite brittle and low-level. Someone could easily introduce new code which reads the mask without using black_box
, and that could potentially introduce another branch.
I think what might be handy here is a VolatileCell
-like type, something like BlackBox<T>
(where T: Copy
), which has a read(&self) -> T
method as the only way to access the inner value, which would prevent the potential bug I just described where the value is accessed without going through black_box
.
tl;dr: it sure would be nice to have a first-class API, whatever it looks like, that can prevent this class of bug where branches are inserted on masks for bitwise operations