"Side channel safe" practices

Intel responded to the reporting on PortSmash by saying:

Software or software libraries can be protected against such issues by employing side channel safe development practices.

Are these practices well-understood by security experts, and does Rust provide sufficient guarantees to employ them?

I found a presentation (older than PortSmash) about techniques for preventing side-channel attacks, giving the following programming guidelines:

  • Avoid all data flow from secrets to branch conditions and memory addresses
  • Avoid the following instructions when manipulating sensitive data:
    • On AMD/Intel:
      • DIV, IDIV, FDIV
      • Higher-level math instructions (e.g. FSIN)
    • On PowerPC:
      • MUL, MULHW, MULHWU
    • On ARM Cortex-M3:
      • UMULL, SMULL, UMLAL, SMLAL

Given these assembly-level guidelines, I expect that the corresponding guidelines for higher-level compiled languages such as C or Rust are fairly straightforward:

  • Don't use sensitive data in conditionals (e.g. switch, if)
  • Don't use sensitive data when indexing or doing pointer-arithmetic
  • When doing arithmetic on sensitive data:
    • On AMD/Intel: Don't use floating-point division or any higher-level floating point math functions
    • On PowerPC: Don't use multiplication at all
    • On ARM: Don't use 64-bit multiplication

So my questions are:

  • Is my interpretations of the guidelines (roughly) accurate?
  • More importantly: can the compiler insert the dangerous instructions while "optimizing", even for code that doesn't explicitly use any of the verboten operations?
4 Likes

The answer is "yes", and is why (AFAIK) most crypto libs use a heavy dose of hand-written assembly code.

2 Likes

@vitalyd Then would it not be useful to have, let's say on the Cargo.toml level, an option to disallow certain optimizations (e.g. by name), or perhaps even any optimization that can result in the usage of certain ASM instructions?

1 Like

Before discussing this at the Cargo.toml (or the like) level, the compilers/backends/optimizers (LLVM, in the case of Rust) would need to support some optimization modes/directives/something that precludes them from creating attack vectors (side channels wouldn't be the only type). Needless to say, this is very difficult and I'm not aware of any compiler actually supporting this type of thing.

2 Likes

If the Intel claim that there are existing well-known effective techniques for avoiding these kinds of attacks is correct, then why would it go without saying that such compiler directives would be difficult to support?

Let me answer your question with a couple of my own :slight_smile:

  • Does Intel’s own compiler (icc) have any support for this?
  • If this is simple, why isn’t it already a feature of compilers?

I’m not a compiler writer nor a crypto implementer, so take what I say as appropriate. Here’s my speculation on why:

  • Need to pin down directives, pragmas, or otherwise compiler extensions on instructing the optimizer on where security sensitive code is located. Like everyone else, pinning down interfaces is a scary thought for maintainers of long-lived products. Add to that the sensitive nature of these, and that aspect gets worse.
  • How do you denote “secrets”? Is it a transient property? What is its granularity? How do you prevent a secret from flowing into a branch in the presence of non-inlined callees? How do you not destroy performance of other interacting code, but perhaps most of which doesn’t present a timing surface?
  • What exactly does the compiler guarantee here? Is it even a guarantee? Is it a best effort? Do all supported architectures have equal (and correct) support? If it’s not a guarantee then I’d expect crypto authors to write the stuff by hand anyway, in assembly (ie status quo).
  • How do you handle microarch revision specifics? What does compilation model look like - do you have to identify the exact CPU model when you compile?
  • How does this interact with the existing complicated opto pass infrastructure? Presumably you don’t want to disable all optimizations, so somehow the optimizer needs to walk a tightrope, selectively.
  • During instruction selection, it’s likely fairly straightforward to not select instructions with input-dependent timing (assuming the compiler has perfect information of this). It’s less clear how target independent optimizations ought to behave.

I think Intel’s claim of the ubiquity of the knowledge of techniques might be accurate amongst the experts (again, I’m not an expert so I wouldn’t know if that claim is irrefutable). But even if the techniques are known in isolation, incorporating them into a complex compiler and not creating a mess, is a whole different ball of wax.

I think anyone that would claim this is trivial or simple isn’t thinking hard enough about the problem, both technically and socially.

6 Likes

I don't know. I would expect they would, given the quote. It appears they do have features for mitigating side channel attacks using speculative execution: Using Intel® Compilers to Mitigate Speculative Execution Side-Channel...

I don't know that it isn't. That's why I started this thread!

....okay, but compiler authors nevertheless do this all the time.

Clearly that would be left to the programmer.

I thought my original post made that pretty clear, at least to the best of my knowledge...?

Either this happens anyway during codegen, or platform capabilites are queried at runtime. This already happens e.g. for SIMD.

Well, either the compiler must, or the author of the assembly code must, so we can assume the information is available.

I'm not sure what you mean, or rather, why you think this would complicate things.

From a bit more research, it really does look like the techniques for providing compiler-level support for preventing side-channel attacks have been demonstrated and studied in academic papers; this paper provides a survey, citing in particular this 2009 paper and this 2012 paper. Both of those are specific to the x86 architecture, but note that the first one actually discusses a prototype implementation of side-channel attack mitigation in LLVM (!).

But I still don't know whether the LLVM main branch has incorporated any similar features since 2009 (nearly a decade ago!).

1 Like

A couple of days ago I posted a related question on StackExchange's Information Security Q&A and, from the comments, not only I discovered the kind of code golfing cryptography implementers have to go through in order to cheat compilers and have the binaries run in constant-time, but that they also have to reverse-engineer them manually afterwards to review the opcodes and make sure everything went as expected.

As you can see, due to Cargo's good practice of building external dependencies locally rather than downloading compiled binaries, this poses a problem with cryptographic libraries because a simple difference in LLVM versions or Instruction Set Architectures between the developer and the library users could mean disaster.

Unfortunately, as @BatmanAoD stated in the quote, it's not just cryptography implementers but everyone who manipulates secrets who should do their best to prevent side-channels, which nowadays translates to almost everybody who manipulates any plaintext -read "not yet ciphertext"- or personal data under GDPR.

Now, instead of trying to solve all problems at once, what about starting by just checking the portion of the resulting compiled opcodes that corresponds to functions that have a #[constant-time] attribute?

3 Likes