-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compiler-level changes #55
Comments
I've talked at length with various people who work on both rustc and LLVM about this. In fact there was a (now postponed) RFC for it: See also this RustFest Global talk about it. My understanding is there has been some work on the LLVM side by Google to add support for this to at least a RISC-V backend, although AFAIK that work is still not public. It seems like there isn't interest in adding support for this to rustc until the corresponding changes have landed on the LLVM side. |
Do we want to use this issue for all compiler issues? I'd like auto-zeroization when dropping tagged variables. |
I'm wondering how much this is a compiler- vs- language issue. It seems like as much the latter. I'd be interested in hashing out a bit more concretely what's desired, if there's low-hanging fruit it may be worth acting on. |
Zeroization is another issue that has come up before. While there are library-level solutions to zeroizing data on drop ( Additionally zeroize-on-drop is suboptimal for things like transient secrets. During the execution of a cryptographic algorithm there can be many transient secrets, and zeroizing them on drop may be a completely pointless performance hit if these secrets live on the stack and are immediately overwritten by new stack frames. An alternative in this case is waiting until a particular section of code has completely finished executing, and then zeroizing memory which was used for the stack up to some high watermark, an approach I've sometimes heard referred to as "stack bleaching". I've opened a rust-internals issue about this before: |
Yeah, particularly in regard to something like a "secret type", it's one of those things that touches every level, from the language itself (adding something like |
During the last call, on top of zeroization or language support for secret types to convey intent and let the compiler frontend help the developers, we discussed also related items, which seems are not covered in the current issue. One important part of the conversation that is not captured above, and which @beldmit referred to, was about disabling compiler optimization for a function or a code block, in order to prevent the compiler from transforming code that the developer already wrote in a constant-time fashion into non constant-time machine code. This is orthogonal to having language features such as I'll take hand-crafted, specialized C code as an example: often, to achieve constant-timeness in C, we use bitwise logic to transform comparisons in bit masks to be applied to the results of operations that are run even if their results will be discarded, so that instruction flow does not depend on secret inputs.
It would be nice, instead of racing against each other, if the communities of cryptographic developers, compiler developers and language designer, could reach an understanding of each other needs and establish a foundation of basic tools to get the different pieces to cooperate instead of fighting. During the call, one such compromise that seemed reasonable for the 3 communities was some way to annotate sections in which all compiler optimizations are off, and what the dev writes in terms of bitwise operations on primitive types (and possibly
|
I'm not aware of any work in that particular area however...
Intrinsics for |
I managed to find some of my previous notes on this topic, from when I started looking into this, before being distracted by other issues.
I find the last quite disheartening, as it means yet another instance of "insecure-by-default", and also a case-by-case fight of arguments about CT vs performance, on compiler code that cannot distinguish between IR-blocks requiring CT and IR-blocks that do not, and wish for the most efficient transformation possible.
Mentioning the x86 |
Yeah, that's a great blog post, and where I learned about that Unfortunately it seems to come to the conclusion that the best way around If there are alternatives, they might be worth exploring. But this is very much deep in the realm of the internals of the LLVM optimizer. |
I thought of updating the thread on this issue after the sync-up call today, they could both serve as items to jumpstart the survey of patterns/desirables/properties or as resources for anyone, with different backgrounds, to delve deeper into the problems, the guidelines and the recommendations when dealing with side-channels/constant-time issues/code. I apologize as they are not really Rust centric, but I still think they can be a good starting point, rich with examples from different point of views, without being forced to parse through many academic papers.
|
During today's call @nikomatsakis suggested it might be possible to disable the LLVM optimizer at the level of individual functions. If that were possible, it could potentially be used to implement a Separate but related: some past discussion on llvm-dev about avoiding optimizations for the purpose of sidechannel resistance: https://groups.google.com/g/llvm-dev/c/qwogIiwHCAc/m/HGsKD8QFBAAJ |
@chandlerc pointed me at this C++ example of a dataflow optimization barrier using an LLVM instruction which to my knowledge does not currently have a Rust equivalent. It seems like the sort of (comparatively) simple solution being suggested earlier. Perhaps it wouldn't be too difficult to add as something like a nightly-only intrinsic. It seems like an improvement over the current approach used by the |
@tarcieri since you mentioned RISC-V. There's some discussion on adding constant time to the Spec. |
I believe this is a working translation of the C++ code (please do not just copy and paste it into anything willy nilly, this is just for discussion): https://godbolt.org/z/ndqxe6qv4 However it's using the now deprecated nightly-only |
Also worth noting what aspect of this is potentially unsafe -- it relies on only being used with types that fit into a physical register. For a value that doesn't fit into a register, a different sequence would be needed (you'd need to pass the address of that value into the inline asm, and use a different constraint). I'm not aware of a truly generic way to express this directly in IR, but it should be possible to lower generic MIR-style stuff into some equivalent LLVM-IR that has the desired effect of blocking dataflow optimizations. |
@chandlerc yeah, I think the final deliverable is probably an unsafe function which could be used to reliably implement something like Although perhaps it could have a safe API that looks like |
Here's an attempt at a reference-based
The generated assembly isn't the nice Edit: here's a version without the |
Not sure you want the volatile flag, you can just clobber memory while passing the pointer through. For example in C++: https://compiler-explorer.com/z/bh9WzvTPq |
Now that inline assembly is in beta, due for stabilization in Rust 1.59, I started playing around with a https://godbolt.org/z/vs83n31sa The following function: #[inline]
pub fn cmovnz(condition: u64, src: u64, dst: &mut u64) {
let mut tmp = *dst;
unsafe {
asm!(
"test {0}, {0}",
"cmovnz {1}, {2}",
in(reg) condition,
inout(reg) tmp,
in(reg) src
);
}
*dst = tmp;
} ...was lowered to test rdx, rdx
cmovne rax, rcx I was really hoping that inline assembly would be treated as a black box free of LLVM optimizations, but it seems that was not the case here. @Amanieu @chandlerc any idea where this could be happen within LLVM, and should I still be worried about CMOV instructions emitted from inline assembly being rewritten as branches? |
|
As an experiment, I made a Still a bit worried about things like flags LLVM isn't aware of being clobbered, but it seems like it should be possible to start using these instructions from stable Rust programs soon. |
You should not rely on flags being preserved outside of an |
Seems we'll need a different strategy for constructing assembly for ADX/MULX chains than mere function calls, then, in order to ensure the flags aren't clobbered. Perhaps |
yeah, I was afraid the flags might not work out. I suspect using some simple macros to assemble a single inline assembly call is the way to go here |
I've released an initial v0.1 version of a
On x86/x86_64 targets, it uses inline assembly to emit On other targets, it provides a "best effort" fallback to a bitwise implementation which is not guaranteed to execute in constant time. Hopefully the latter can be addressed by adding more architecture-specific inline assembly, particularly for popular architectures like AArch64. |
Thanks to @codahale, I've also posted a release announcement here: https://users.rust-lang.org/t/ann-cmov-v0-1-constant-time-conditional-move-intrinsics-for-x86-x86-64-aarch64/72473 |
During the last meeting we discussed about possible compiler enchantments (e.g. explicit switching optimization off). Otherwise we have no chance to defend from side-channel attacks, that means we can't safely use the pure Rust crypto implementations.
I wonder if any from the Rust compiler team is involved in this group so we have chances to meet these requirements.
The text was updated successfully, but these errors were encountered: