7 ms·
Constant-time support coming to LLVM: Protecting cryptographic code
https://archive.is/tIXt7 https://archive.is/tIXt7
- frabert 11mo agoThis has been a sore point in a lot of discussions regarding compiler optimizations and cryptographic code, how compilers and compiler engineers are sabotaging the efforts of cryptographers in making sure there are no side-channels in their code. The issue has never been the compiler, and has always been the language: there was never a way to express the right intention from within C (or most other languages, really). This primitive we're trying to introduce is meant to make up for this shortcoming without having to introduce additional rules in the standard.
- jfindper 11mo ago>how compilers and compiler engineers are sabotaging the efforts of cryptographers I'm not exposed to this space very often, so maybe you or someone else could give me some context. "Sabotage" is a deliberate effort to ruin/hinder something. Are compiler engineers deliberately hindering the efforts of cryptographers? If yes... is there a reason why? Some long-running feud or something? Or, through the course of their efforts to make compilers faster/etc, are cryptographers just getting the "short end of the stick" so to speak? Perhaps forgotten about because the number of cryptographers is dwarfed by the number of non-cryptographers? (Or any other explanation that I'm unaware of?)
- chowells 11mo agoIt's more a viewpoint thing. Any construct cryptographers find that runs in constant time is something that could be optimized to run faster for non-cryptographic code. Constant-time constructs essentially are optimizer bug reports. There is always the danger that by popularizing a technique you are drawing the attention of a compiler contributor who wants to speed up a benchmark of that same construct in non-cryptographic code. So maybe it's not intended as sabotage, but it can sure feel that way when everything you do is explicitly targeted to be changed after you do it.
- stouset 11mo agoIt’s not intentional. The motivations of CPU designers, compiler writers, and optimizers are at odds with those of cryptographers. The former want to use every trick possible to squeeze out additional performance in the most common cases, while the latter absolutely require indistinguishable performance across all possibilities. CPUs love to do branch prediction to have computation already performed in the case where it guesses the branch correctly, but cryptographic code needs equal performance no matter the input. When a programmer asks for some register or memory location to be zeroed, they generally just want to be able to use a zero in some later operation and so it doesn’t really matter that a previous value was really overwritten. When a cryptographer does, they generally are trying to make it impossible to read the previous value. And they want to be able to have some guarantee that it wasn’t implicitly copied somewhere else in the interim.
- layer8 11mo ago“Sabotage” can be used in a figurative sense that doesn’t insinuate intent. An adjacent example is “self-sabotage”, which doesn’t imply intent.
- jfindper 11mo agoEvery dictionary I've looked at, wikipedia, etc. all immediately and prominently highlight the intent part. It really seems like the defining characteristic of "sabotage" vs. other similar verbs. But, language is weird, so, ¯\_(ツ)_/¯.
- layer8 11mo agoSince the sibling comment is dead and thus I can’t reply to it: Search for “unintentional sabotage”, which should illustrate the usage. Despite appearances, it isn’t an oxymoron. See also meaning 3a on https://www.merriam-webster.com/dictionary/sabotage https://www.merriam-webster.com/dictionary/sabotage.
- colmmacc 11mo agoI don't think it's nefarious but it is sabotage. There's long been an implicit assumption that optimization should be more important than safety. Yes, languages do lack good mechanisms to mark variables or sections as needing constant-time operation ... but compiler maintainers could have taken the view that that means all code should be compiled that way. Now instead we're marking data and section as "secret" so that they can be left unoptimized. But why not the other way around? I understand how we get here; speed and size are trivial to measure and they each result in real-world cost savings. I don't think any maintainer could withstand this pressure. But it's still deliberate.
- aw1621107 11mo ago> Now instead we're marking data and section as "secret" so that they can be left unoptimized. But why not the other way around? Worse cost-benefit tradeoff, perhaps? I'd imagine the amount of code that cares more about size/speed than constant-time operation far outnumbers the amount of code which prioritizes the opposite, and given the real-world benefits you mention and the relative newness of concerns about timing attacks I think it makes sense that compiler writers have defaulted to performance over constant-time performance. In addition, I think a complicating factor is that compilers can't infer intent from code. The exact same pattern may be used in both performance- and timing-sensitive code, so absent some external signal the compiler has to choose whether it prioritizes speed or timing. If you think more code will benefit from speed than timing, then that is a reasonable default to go with.
- soulbadguy 11mo agoAs compiler have become more sophisticated, and hardware architecture more complicated, there are been a growing sentiment that some of the code transformation done by modern compiler make the code hard to reason about and to predict. A lot of software engineer are seeing this as compiler engineer only caring about performance as opposed to other aspect such as debuggability, safety, compile time and productivity etc... I think that's where the "sabotage" comes from. Basically the focus on performance at the detriment of other things. My 2 cents : The core problem is programmers expecting invariant and properties not defined in the languange standard. The compiler only garanty things as defined in the standard, expecting anything else is problematic.
- fooker 11mo ago> making sure there are no side-channels in their code Any side effect is a side channel. There are always going to be side channels in real code running on real hardware. Sure you can change your code, compiler, or, or even hardware to account for this but at it's core that is security by obscurity.
- fanf2 11mo agoWhat happened to the blog post? It was moved and now it has disappeared :-(
- Meneth 11mo agoArchived copy here: https://archive.is/tIXt7 https://archive.is/tIXt7
- foresto 11mo agohttps://web.archive.org/web/20251125224147/https://blog.trailofbits.com/2025/11/25/constant-time-support-coming-to-llvm-protecting-cryptographic-code-at-the-compiler-level/ https://web.archive.org/web/20251125224147/https://blog.trai...
- frabert 11mo agoSorry, it was published early and we have to wait for some approval checks to clear
- Asooka 11mo agoThere really ought to be a subset of C that lets you write portable assembly. One where only a defined set of optimisations are allowed and required to be performed, "inline" means always inline, the "register" and "auto" keywords have their original meanings, every stack variable is allocated unless otherwise indicated, every expression has defined evaluation order, every read/write from/to an address is carried out, nothing is ever reordered, and undefined behaviour is switched to machine-specific behaviour. Currently if you need that level of control, your only option is writing it in assembly, which gets painful when you need to support multiple architectures, or want fancy features like autocomplete or structs and functions.
- saagarjha 11mo agoWhy would you like register and auto to have meaning?
- delta_p_delta_x 11mo ago> want fancy features like autocomplete or structs and functions I would argue that given a certain ISA, it's probably easier to write an autocomplete extension for assembly targeting that ISA, rather than autocomplete for C, or goodness forbid, C++. Likewise for structs, functions, jump targets, etc. One could probably set up snippets corresponding to different sorts of conditional execution—loops, if/else/while, switch, etc.
- GhosT078 11mo agoSPARK is a very expressive language for implementing cryptographic applications. It is available for some LLVM targets (e.g. x86-64).
- zzo38computer 11mo agoI think __builtin_ct_select and __builtin_ct_expr would be good ideas. (They could also be implemented in GCC in future, as well as LLVM.) In some cases it might be necessary to consider the possibility of invalid memory accesses (and avoid the side-channels when doing so). (The example given in the article works around this issue, but I don't know if there are any situations where this will not help.)
- connicpu 11mo agoThe side channel from memory access timings are exactly why cmov is its own instruction on x86_64. It retrieves the memory regardless of the condition value. Anything else would change the timings based on condition. If you're going to segfault that's going to be visible to an attacker regardless because you're going to hang up.
- zzo38computer 11mo agoI mean the possibility that the rest of the program guarantees that the address is valid if the condition is true but otherwise it might be valid or invalid. This is probably not important for most applications, but I don't know if there are some unusual ones where it would matter.
- wahern 11mo agoAFAIU, cmov wasn't originally intended to be a guaranteed constant-time operation, Intel and AMD won't commit to keeping it constant-time in the future, but it just so happened that at one point it was implemented in constant-time across CPUs, cryptographers picked up on this and began using it, and now Intel and AMD tacitly recognize this dependency. See, e.g., https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/secure-coding/mitigate-timing-side-channel-crypto-implementation.html https://www.intel.com/content/www/us/en/developer/articles/t... > The CMOVcc instruction runs in time independent of its arguments in all current x86 architecture processors. This includes variants that load from memory. The load is performed before the condition is tested. Future versions of the architecture may introduce new addressing modes that do not exhibit this property.
- ethin 11mo agoSo this makes me curious: is there a reason we don't do something like a __builtin_ct_begin()/__builtin_ct_end() set of intrinsics? Where the begin intrinsic begins a constant-time code region, and all code within that region must be constant-time, and that region must be ended with an end() call? I'm not too familiar with compiler intrinsics or how these things work so thought I'd ask. The intrinsic could be scoped such that the compiler can use it's implementation-defined behavior freedom to enforce the begin/end pairs. But Idk, maybe this isn't feasible?
- zzo38computer 11mo agoMaybe it might be better to be implemented as a function attribute instead
- ethin 11mo agoOr a pragma? Like how OpenMP did it?
- grogers 11mo agoIt'd be very hard for the compiler to enforce constant-time execution for generic code. As an example, if you wrote the naive password checking where the first byte that doesn't match returns false, is that a compiler error if it can't transform it into a constant time version?
- amluto 11mo agoToo bad that Intel chips more or less reserve the right to take LLVM’s nice output and make it non-constant-time anyway. See: https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/best-practices/data-operand-independent-timing-isa-guidance.html https://www.intel.com/content/www/us/en/developer/articles/t... Sure, you could run on some hypothetical OS that supports DOITM and insert syscalls around every manipulation of secret data. Yeah, right.
- JoshTriplett 11mo agoLast I saw, it seemed like the plan was to unconditionally enable it, and on the off chance there's ever a piece of hardware where it's a substantial performance win, offer a way to opt out of it.
- amluto 11mo agoI advocated for that, and then I completely lost track of the status. The whole design is ridiculous.
- matu3ba 11mo agoWhat would be more sane alternatives, when it becomes obvious that any side-effect of timing is a potential attack vector? See https://www.hertzbleed.com/ https://www.hertzbleed.com/ for frequency side channels. I do only see dedicated security cores as options with fast data lanes to the CPU similar to what Apple is doing with Secure Enclave or do you have better suggestions that still allow performance and power savings?
- amluto 11mo agoA design such that it would actually make sense for a compiler to mark code that should permit data-dependent CPU optimizations differently from code that should not. This could be done using an opcode prefix, which would bloat code but would work perfectly. Or it could use an RFLAGS bit or a bit in MXCSR or a new register, etc. Almost anything would be better than an MSR that is only accessible to privileged code.
- charcircuit 11mo agoThese are meaningless without guarantees that the processor will run the instructions in constant time and not run the code as fast as possible. Claims like cmov on x86 always being constant time are dangerous because a microcode update could change that to not be the case anymore. Programmers want an actual guarantee that the code will take the same amount of time. We should be asking our CPU vendors to support enabling a constant time mode of some sort for sensitive operations.
- Gibbon1 11mo agoThat's been one of my counters to the bitch that C isn't safe. The underlying architecture isn't safe. That said WG21 and WG14 don't seem to be able to get the memo that safety is more important than single core speed. Or as I suspect a bunch members are actually malicious.
- derangedHorse 11mo agoI agree. For use cases where side channel attacks are likely to be attempted, the security of the system ultimately depends on both the software and hardware used.
- adrian_b 11mo agoNowadays both Intel/AMD CPUs and Arm-based CPUs guarantee that a certain subset of the instructions are executed in constant time. For an example of a list of such instructions see: https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/resources/data-operand-independent-timing-instructions.html https://www.intel.com/content/www/us/en/developer/articles/t... However, cooperation from the operating system is necessary, as the constant-time execution mode may need to be enabled by setting certain CPU-control bits in protected registers (e.g. IA32_UARCH_MISC_CTL[DOITM]). See for instance: https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/best-practices/data-operand-independent-timing-isa-guidance.html https://www.intel.com/content/www/us/en/developer/articles/t... CMOV is on the list of instructions with constant-time execution, but the list is valid only with the corresponding control bit set correctly.
- 11mo ago
- rurban 11mo agoOr just disable the fucking broken optimizer for such a function. #pragma GCC optimize ("O0")
- adrian_b 11mo agoDisabling optimizations does not necessarily result in more deterministic execution. With "-O0", the generated code normally retains a huge number of useless register loads and stores, which lead to non-deterministic timing due to contention in the use of caches and of the main memory interface. Optimized code may run only inside registers, being thus executed in constant time regardless of what other CPU cores do. The only good part is that this non-deterministic timing will not normally depend on the data values. The main danger of the non-constant execution time is when this time depends on the values of the processed data, which provides information about those values. There are cases when disabling optimization may cause data-dependent timing, e.g. if with optimization the compiler would have chosen a conditional move and without optimization it chooses a data-dependent branch. The only certain way of achieving data-independent timing is to use either assembly language or appropriate compiler intrinsics.
- anematode 11mo agoI'd love if this could make it into Rust... but I'm wondering if it'd be a bit of a burden on the creators of alternative backends (e.g. Cranelift), since if they implemented it naively, they would be unsuitable for compiling cryptographic code using it.
- aw1621107 11mo ago> but I'm wondering if it'd be a bit of a burden on the creators of alternative backends (e.g. Cranelift) Technically any new feature that requires backend support is an additional burden on backend devs. There's nothing special about constant-time builtins in this respect. > since if they implemented it naively Strictly speaking, whether an implementation is naive is independent of whether it is correct. An implementation that purports to be constant time while not actually being constant time is wrong, no matter how naive or sophisticated the implementation may be.
- westurner 11mo ago/? llvm.ct.select and __builtin_ct_select : "Constant-Time Coding Support in LLVM: Protecting Cryptographic Code at the Compiler Level" (2025-10) PDF: https://llvm.org/devmtg/2025-10/slides/quick_talks/alexandre.pdf https://llvm.org/devmtg/2025-10/slides/quick_talks/alexandre... : > Circumvent Branch-base Timing Attacks