# Compiling countermeasures against fault attacks with "Tracing LLVM"

Sébastien Michelland, Christophe Deleuze, Laure Gonnord

► To cite this version:

Sébastien Michelland, Christophe Deleuze, Laure Gonnord. Compiling countermeasures against fault attacks with "Tracing LLVM". 10th Workshop on Principles of Secure Compilation, Jan 2026, Rennes, France. hal-05483310

HAL Id: hal-05483310

https://hal.science/hal-05483310v1

Submitted on 29 Jan 2026

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_1.png" /></figure>

Distributed under a Creative Commons CC BY 4.0 - Attribution - International License
# Compiling countermeasures against fault attacks with “Tracing LLVM”

Sébastien Michelland

Christophe Deleuze

Laure Gonnord

sebastien.michelland@grenoble-inp.fr

christophe.deleuze@grenoble-inp.fr

laure.gonnord@grenoble-inp.fr

Université Grenoble-Alpes, Grenoble INP, LCIS

France

## Abstract

Fault injections are physical attacks that disrupt systems' hardware to create exploitable erroneous behaviors. Countermeasures that rely on software (exclusively or not) frequently need much more precise control of the binary code than available through high-level languages' semantics. This causes significant friction when compiling, as lowerings and optimizations only obey semantics and often destroy security code or annotations. As a step to address this issue, we introduce Tracing LLVM, an open-source extension of LLVM [2] with primitives to protect and compile non-functional aspects of programs (backed mostly by implementation-defined behavior and internal LLVM API contracts). We showcase its current capabilities by protecting typical countermeasures, lowering source information to late stages, and composing four protection schemes on a PIN verification function.

**Keywords** Secure Compilation, Fault Tolerance, Compiler Traceability, LLVM

## 1 Semantics vs. Security in a Compiler

Fault injections (and in many cases also side-channels), by their physical nature, escape the modeling power of programming languages. This is structural, as programming languages purposely forsake hardware details in exchange for compatibility and abstraction. As a result, a high-level program constitutes only a loose, "functional" specification of the system's behavior [3].

Security programmers can still write code that has favorable security behaviors, such as cleaning up memory after use, or duplicating sensitive computations, even if such protections are functionally invisible. Compiling such code however, performs complex transformations that are only required to preserve semantics, and almost invariably break security countermeasures [1], either as an unfortunate side-effect or as a deliberate optimization choice.

While many transformations can be skipped, like most optimizations (for a price in performance), lowerings (changes

in representation or abstraction level) are required in the compilation flow. They can break countermeasures and also affect how security is expressed, making them critical in secure compilation.

Fault countermeasures typically work around these hardships with tricks (like volatility or inter-procedural obfuscation), disable optimizations, or write security code directly in assembly. This leaves us with an adversarial relationship with the compiler and lacking a solid basis for security certification. Previous work has raised the need for proper compiler support for security primitives, both for attacks that can be described functionally [5] and for attacks that can't [4].

## 2 Security primitives in Tracing LLVM

Tracing LLVM provides countermeasure developers with two categories of primitives to facilitate the implementation and preservation of non-functional security schemes.

**Opacification**, as introduced by Vu [4], leverages primitives with *implementation-defined behavior* to protect elements of the program from transformation. Opacification primitives are implemented as the identity, but declared as *implementation-defined*. As a result, only transformations that would be valid for *all* possible implementations are functionally legal, which limits transformations to a manageable set engineering-wise (e.g. dead code elimination).

Tracing LLVM provides opacification for:

* **Values**: Vu defines a primitive *opacification function* to opacify values (via a C builtin, an LLVM IR intrinsic, a Selection DAG node and Machine IR instructions), which we ported to our work.

```c
int x = __builtin_opacify(73);
/* x cannot be constant-folded */
```

* **Operations**: Tracing LLVM defines *LLVM IR wrappers*, a new instruction type that wraps and opacifies the semantics of an IR instruction.

```c
%y = simplewrapper i32 1 2
    add i32 %key, %x
    ; compiler sees %y = <black box> i32 %key, %x
```
*   **Types**: Tracing LLVM introduces the “traced” type constructor “!”, which hides the nature and representation of a type in C, LLVM IR and the early back-end. This can be useful for disabling vast type-directed analyses but mostly enables tracing (see below).

```c
int !x = 73;
/* not an integer, so no arith optimizations */
```

Tracing, the other main feature, establishes a connection between high-level elements of the program and their low-level counterparts. Unlike debug information, which becomes incorrect if it cannot be maintained through transformations, tracing inhibits optimizations locally if needed to ensure correctness.

Two tracing primitives are currently provided:

*   **Tracing dataflow**: this taints the outgoing dataflow of a given variable with an attributed traced type. Operations are then protected with wrappers (a not-so-subtle way to avoid intermediate rewrites) until the back-end, where traced types mark the tainted dataflow.

*   **Tracing writes**: this enforces the semantics of source-level assignments down to assembler level. All live ranges of a `trace(writes)` variable are allocated to a single register, and all source assignments materialize the source value through a move or computation into said register. The source order is preserved.

Essentially, opacification preserves security code through optimizations and lowerings while tracing lowers source information to facilitate hardening transformations.

## 3 A typical example

Applications worried about bit flips commonly employ *hardened booleans*: integers that take one of two values more than one bit apart from each other, such as `0x55` and `0xa0`. However, compilers notice the trick and optimize them away back to normal booleans. One way to protect a hardened boolean is to trace its assignments in the C source:

```c
int! __attribute__ ((trace(writes)))
valid = 0x55; /* FALSE */
```

Tracing is enabled by the use of a traced type with the appropriate attribute. This protects assignments and the assigned values from transformations throughout the compiler middle-end using a wrapper; here is the IR for the store:

```Rust
simplewrapper void 1 2
    store i32 0x55, ptr %valid; store hidden
```

Once we get to the back-end, Tracing LLVM performs late register promotion to avoid a memory access; this particular assignment becomes a load-immediate in RISC-V assembly.

```Rust
li a0, 0x55
```

This ensures that hardened booleans are used at runtime and facilitates observation and debugging of the program, even with -O3. No combination of usual tricks and disabled optimizations would yield this result without also un-optimizing the rest of the code.

## 4 Conclusion and Resources

Tracing LLVM has been demonstrated on other prototype-scale programs, including:

*   Challenges typical of fault countermeasure issues: tracking variables down to assembly level, cleaning sensitive registers, avoiding hard-coded optimizations;

*   A PIN verification function with a composition of four simple countermeasures: doubling memory loads (data-flow, C level), checkpointing (control-flow, C level), ensuring that values derived from the secret PIN are not spilled to stack (assembly level), and that all registers they touch are zeroed out on function exit (assembly level).

In the future, we plan to expand this set of primitives and formalize their use for hardware security (maybe other tasks that go beyond semantics like debug information). Translation validation appears very promising as a mechanism to bridge a clean formal model and the practical complexity of LLVM. We intend to maintain Tracing LLVM as a large patch set with major LLVM updates.

Tracing LLVM is open-sourced under the same (modified) Apache 2.0 license as LLVM; the repository is available at https://gricad gitlab.univ-grenoble-alpes.fr /tracing-llvm.

## Acknowledgments

This study was partially funded by the “France 2030” government investment plan managed by the French National Research Agency, under the references ANR-22-PECY-0004, and “IRT Nanoelec”, ANR-10-AIRT-05.

## References

[1] Vijay D'Silva, Mathias Payer, and Dawn Song. 2015. The Correctness-Security Gap in Compiler Optimization. In *IEEE Security and Privacy Workshops*. 73–87. https://doi.org/10.1109/SPW.2015.33

[2] Chris Lattner and Vikram Adve. 2004. LLVM: A compilation framework for lifelong program analysis & transformation. In *International symposium on code generation and optimization (CGO)*. IEEE, 75–86.

[3] Laurent Simon, David Chisnall, and Ross Anderson. 2018. What You Get is What You C: Controlling Side Effects in Mainstream C Compilers. In *IEEE European Symposium on Security and Privacy (EuroS&P)*. 1–15. https://doi.org/10.1109/EuroSP.2018.00009

[4] Son Tuan Vu. 2021. *Optimizing Property-Preserving Compilation*. Ph.D. Dissertation. Sorbonne Université. http://www.theses.fr/2021SORUS435 Thèse de doctorat dirigée par Heydemann, Karine et Cohen, Albert Henri Informatique Sorbonne université.

[5] Hans Winderix. 2018. Security Enhanced LLVM.