rust-timing-shield
has a full disclosure policy for vulnerabilities.
Since rust-timing-shield
intends to protect against adversaries with vast
resources, it is assumed that any vulnerabilities are already known to
attackers. Accordingly, this project prioritizes disclosing vulnerabilities to
users over hiding vulnerabilities from adversaries.
To report a vulnerability: please open an issue on the public GitHub issue tracker. If you believe the issue is serious, I would appreciate it if you also email me separately with a link to the GitHub issue so that I can prioritize it above other issues.
Vulnerability research is an important part of making rust-timing-shield
a
high-quality framework.
Please find bugs!
The rest of this page is a work in progress, but eventually it will be an overview of
rust-timing-shield
’s attack surface, and will serve as a guide to finding
vulnerabilities.
The general principle of an optimizing compiler is that it may transform the input code in any way that it likes, as long as the resulting code is “correct” – it produces the same outputs for the same inputs as the original code. Unfortunately, this principle says nothing about timing or resource usage, which effectively means that optimizing compilers are free to add timing leaks to any program they transform. Nice.
This problem is not just theoretical. There are many documented cases of compilers transforming code that was intended to be “constant-time” into code that, for instance, branches on a secret value.
For example, this Rust code is intended to choose either a
or b
, according to condition
,
without leaking the value of condition
:
However, looking at the LLVM IR generated for this function:
LLVM knows exactly what we’re trying to hide from it. The corresponding x86 assembly uses a conditional jump:
This isn’t coincidental. Amusingly, the LLVM “InstCombine” pass has code that pattern matches on exactly the and/or expression we used. Check out this excerpt from the LLVM source code:
A proper, complete solution to this problem would likely involve deep changes to LLVM (e.g. adding a field to LLVM’s type system to indicate which values require timing protection). Further research in this direction is enthusiastically encouraged.
Although rust-timing-shield
does not make such modifications to LLVM,
no timing leak protection tool would be complete without some sort of mitigation for these
risks.
An existing timing leak protection library, nadeko, takes
advantage of the fact that inline assembly blocks are opaque to LLVM’s optimization passes. By
implementing all operations in inline assembly, nadeko prevents the compiler from performing
any optimizations whatsoever on timing sensitive code. Although great for security, this
approach has a serious performance cost, since it inhibits even simple transformations like
mov c, 1; add x, c
–> add x, 1
.
rust-timing-shield
prioritizes performance, and correspondingly takes a different approach.
As much as possible, rust-timing-shield
interferes with the compiler only when a particular
optimization might impact security. As a result, programs can still benefit from most
optimization passes including constant folding, algebraic simplifications, and even
auto-vectorization.
rust-timing-shield
’s mitigations focus on boolean values (i.e. i1
values in LLVM IR),
based on the observation that all known problematic transformations only apply when a value is
derived from a boolean. For example, the (A & C) | (B & D)
optimization described earlier
only applies when both A
and B
are equivalent to a sign-extended i1
.
In order to prevent LLVM from recognizing booleans,
rust-timing-shield
“launders” all protected boolean values through an empty inline assembly
block. Because inline assembly blocks appear opaque to optimization passes, this is sufficient
to prevent any pattern matching on values derived from booleans.
This approach seems to work well so far in practice, but it might turn out to be insufficient with future versions of the Rust compiler. More research this is needed into whether the current heuristics are sufficient.