<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Frank DENIS random thoughts.</title>
  <link href="https://00f.net/atom.xml" rel="self"/>
  <link href="https://00f.net"/>
  <updated>2026-10-09T16:52:55+02:00</updated>
  <id>https://00f.net</id>
  
  <author>
    <name>Frank Denis (Jedi/Sector One)</name>
    <email>blog at pureftpd.org</email>
  </author>
  
  
  <entry>
    <title>Branches in branch-free code</title>
    <link href="https://00f.net/2026/10/09/llvm-compiles-branch-free-code-into-branches-on-risc-v/"/>
   <updated>2026-10-09T00:00:00+02:00</updated>
   <id>https://00f.net/2026/10/09/llvm-compiles-branch-free-code-into-branches-on-risc-v</id>
   <content type="html">&lt;h2 id=&quot;lets-add-two-numbers&quot;&gt;Let’s add two numbers&lt;/h2&gt;

&lt;p&gt;Here’s a complete C function that adds two unsigned 128-bit integers:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_BitInt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;u128&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u128&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u128&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s compile it for 32-bit RISC-V:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;clang &lt;span class=&quot;nt&quot;&gt;--target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;riscv32 &lt;span class=&quot;nt&quot;&gt;-march&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;rv32imac &lt;span class=&quot;nt&quot;&gt;-mabi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ilp32 &lt;span class=&quot;nt&quot;&gt;-O2&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; add.c &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see the output on &lt;a href=&quot;https://godbolt.org/z/z97rT71ds&quot;&gt;Compiler Explorer&lt;/a&gt;, alongside versions compiled with GCC and with the Zicond extension we’ll look at below.&lt;/p&gt;

&lt;p&gt;Here’s the relevant snippet:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        add     a1, t1, t0
        add     a2, a7, t2
        sltu    t0, a1, t1
        add     a2, a2, t0
        beq     a2, a7, .LBB0_2
        sltu    t0, a2, a7
.LBB0_2:
        add     a5, a5, a3
        add     a4, a4, a6
        add     t0, t0, a5
        sltu    a3, a5, a3
        sltu    a5, t0, a5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Wait, why is there a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beq&lt;/code&gt; in an addition? That’s a conditional branch, right?&lt;/p&gt;

&lt;p&gt;RV32 registers hold 32 bits, so LLVM has to split our 128-bit addition into four smaller ones, passing the carry from each to the next.&lt;/p&gt;

&lt;p&gt;This is how the compiled code performs the addition; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a0&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b0&lt;/code&gt; are the lowest 32-bit words of our inputs; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b1&lt;/code&gt; are the next ones.
All values are unsigned, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;low32()&lt;/code&gt; keeps only the lowest 32 bits, and comparisons return 0 or 1.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sum0 = low32(a0 + b0)
carry0 = (sum0 &amp;lt; a0)

sum1 = low32(a1 + b1 + carry0)

if sum1 == b1:
    carry1 = carry0
else:
    carry1 = (sum1 &amp;lt; b1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Can you guess why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sum1 == b1&lt;/code&gt; is checked?&lt;/p&gt;

&lt;p&gt;CPUs such as x86 and AArch64 have instructions to perform conditional moves (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmov&lt;/code&gt;), allowing carry propagation to be implemented without a branch.&lt;/p&gt;

&lt;p&gt;But on RV32, even comparing two 64-bit integers with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;&lt;/code&gt; produces a branch.&lt;/p&gt;

&lt;h2 id=&quot;what-about-the-usual-bit-mask&quot;&gt;What about the usual bit mask?&lt;/h2&gt;

&lt;p&gt;We’ve been talking about carry propagation in large integers, but if you’ve written constant-time code, you’ve probably used some version of this everywhere:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ct_select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the low bit of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bit&lt;/code&gt; is set, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mask&lt;/code&gt; is all ones, so the expression keeps &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; and zeros out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;.
Otherwise, the mask is zero and we get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just bitwise operations,  no branch in the source.&lt;/p&gt;

&lt;p&gt;Let’s compile that for RV32 with clang 23:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ct_select:
        andi    a0, a0, 1
        beqz    a0, .LBB0_2
        mv      a2, a1
.LBB0_2:
        mv      a0, a2
        ret
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Aaaahhhhhhhh, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beqz&lt;/code&gt; instruction, branching on the bit we just masked. So much for carefully writing the selection with bitwise operations.&lt;/p&gt;

&lt;p&gt;And this also happens on 64-bit RISC-V.&lt;/p&gt;

&lt;p&gt;You can see the compiled &lt;a href=&quot;https://godbolt.org/z/zq7nnWonn&quot;&gt;code on Compiler Explorer&lt;/a&gt; which includes both targets, plus clang 17, GCC and Zicond for comparison.&lt;/p&gt;

&lt;p&gt;Why include clang 17? Because the branch was there in 15, gone in 16 and 17, and back from 18 to 23. Fun, uh?&lt;/p&gt;

&lt;p&gt;So, even if you reviewed assembly code with a given version of the compiler, and everything looked fine, every change to the compiler version of compiler flags requires a new review.&lt;/p&gt;

&lt;h2 id=&quot;what-about-zig&quot;&gt;What about Zig?&lt;/h2&gt;

&lt;p&gt;Let’s try the 128-bit addition in Zig, along with the same bit-mask selection:&lt;/p&gt;

&lt;div class=&quot;language-zig highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;u128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;u128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;u128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-zig highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctSelect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;u32&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zig build-obj add.zig &lt;span class=&quot;nt&quot;&gt;-target&lt;/span&gt; riscv32-freestanding &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; ReleaseFast &lt;span class=&quot;nt&quot;&gt;-femit-asm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;add.s &lt;span class=&quot;nt&quot;&gt;-fno-emit-bin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beq&lt;/code&gt; after the second word, and the selection gets the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beqz&lt;/code&gt; (&lt;a href=&quot;https://godbolt.org/z/9jrqdvPjo&quot;&gt;Compiler Explorer code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Changing the source language doesn’t get us out of this. And yes, Rust has the same issue.&lt;/p&gt;

&lt;h2 id=&quot;what-about-other-platforms&quot;&gt;What about other platforms?&lt;/h2&gt;

&lt;p&gt;Now let’s compile C examples for a few other targets.&lt;/p&gt;

&lt;p&gt;I also added a 64-bit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a &amp;lt; b&lt;/code&gt; comparison, since that was enough to produce a branch on RV32.&lt;/p&gt;

&lt;p&gt;These are the numbers of conditional branches and conditional returns emitted by clang 23 at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-O2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As usual everything can be verified on &lt;a href=&quot;https://godbolt.org/z/9Ys7Tovfz&quot;&gt;Compiler Explorer&lt;/a&gt;:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Target&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ct_select&lt;/code&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;64-bit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;x86_64, x86&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AArch64, 32-bit ARM, Cortex-M3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MIPS32, LoongArch64, WebAssembly&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cortex-M0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;32-bit PowerPC&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RV32&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RV64&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RV32 and RV64 with Zicond&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Targets with zeros are safe. Everything else has ugly side channels in spite of source code looking like it runs in constant time.&lt;/p&gt;

&lt;p&gt;WebAssembly has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmov&lt;/code&gt;) instruction, so no obvious conditional jumps are visible in the modules, but then WebAssembly compilers can do whatever they want. On platforms without equivalent native instructions, it’s likely that we’ll get a jump.&lt;/p&gt;

&lt;p&gt;Cortex-M0 (Thumb-1) and generic 32-bit PowerPC don’t have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmov&lt;/code&gt;-like instructions, so they branch.&lt;/p&gt;

&lt;h2 id=&quot;lets-try-gcc&quot;&gt;Let’s try GCC&lt;/h2&gt;

&lt;p&gt;Now here’s a pleasant surprise: GCC 16.1 compiles both examples without branches on RISC-V. Its carries use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sltu&lt;/code&gt;, and it leaves the mask arithmetic alone.&lt;/p&gt;

&lt;p&gt;Cool. But let’s make a small change: derive the mask from a comparison.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And… the branch is back!&lt;/p&gt;

&lt;p&gt;GCC now emits a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bgeu&lt;/code&gt; on both RV32 and RV64 (&lt;a href=&quot;https://godbolt.org/z/65P3eWeTr&quot;&gt;Compiler Explorer&lt;/a&gt;).
It also branches on 64-bit comparisons on RV32.&lt;/p&gt;

&lt;h2 id=&quot;can-we-hide-the-mask-from-the-optimizer&quot;&gt;Can we hide the mask from the optimizer?&lt;/h2&gt;

&lt;p&gt;For the bit-mask example, there’s a common workaround: pass the mask through an empty &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asm&lt;/code&gt; statement before using it. Let’s do that:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ct_select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__asm__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;+r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The assembly does nothing, but its declaration tells the compiler that it may change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mask&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, both versions compile without branches on RV32 and RV64. Phew.&lt;/p&gt;

&lt;p&gt;Can we do the same for the addition?&lt;/p&gt;

&lt;p&gt;I tried hiding the inputs behind a memory barrier, and the branch stayed.
But putting a register barrier on &lt;em&gt;each&lt;/em&gt; of their 32-bit words worked, and every carry became an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sltu&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both attempts are &lt;a href=&quot;https://godbolt.org/z/KWj99rshe&quot;&gt;on Compiler Explorer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To be honest, I wouldn’t rely on either barrier experiment as a fix for the addition.&lt;/p&gt;

&lt;p&gt;For arithmetic involving secrets, I’d avoid integer types wider than two registers and write the carries explicitly, using 32-bit words on RV32.&lt;/p&gt;

&lt;p&gt;Currently, clang 23 keeps my hand-written carry chain free of branches, but who knows what will happen in the next releases.&lt;/p&gt;

&lt;h2 id=&quot;giving-llvm-the-missing-instructions&quot;&gt;Giving LLVM the missing instructions&lt;/h2&gt;

&lt;p&gt;For RISC-V, there’s a solution, though: RISC-V has an extension called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zicond&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It adds &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;czero.eqz&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;czero.nez&lt;/code&gt;, which zero a register depending on whether another register is zero.
And that can be used to select a value without branching.&lt;/p&gt;

&lt;p&gt;Let’s enable it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-march=rv32imac_zicond&lt;/code&gt; and compile our bit-mask example again:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ct_select:
        andi       a0, a0, 1
        czero.eqz  a1, a1, a0
        czero.nez  a0, a2, a0
        or         a0, a0, a1
        ret
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yay, no jumps. Every case tested above is free of branches with Zicond enabled.&lt;/p&gt;

&lt;p&gt;Zicond is part of the RVA23 profile, but unfortunately many cores in use today don’t implement it, especially microcontrollers.&lt;/p&gt;

&lt;p&gt;And even if it’s available, there’s an important detail that’s easy to overlook: the &lt;a href=&quot;https://docs.riscv.org/reference/isa/unpriv/zicond.html&quot;&gt;Zicond specification&lt;/a&gt; only guarantees that their timing is independent of the data if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zkt&lt;/code&gt; extension is also implemented.&lt;/p&gt;

&lt;p&gt;Writing secure, portable code is hard. Protecting against side-channels is as footgunish as zeroing secrets.&lt;/p&gt;

&lt;p&gt;Oh, and if you haven’t read it yet, Thomas Pornin’s &lt;a href=&quot;https://www.bearssl.org/constanttime.html&quot;&gt;Why constant-time crypto?&lt;/a&gt; and &lt;a href=&quot;https://www.bearssl.org/ctmul.html&quot;&gt;Constant-time multiplication&lt;/a&gt; pages are absolutely worth a read.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Zeroization, part 3: Copies you can’t wipe</title>
    <link href="https://00f.net/2026/10/06/zeroization-3/"/>
   <updated>2026-10-06T00:00:00+02:00</updated>
   <id>https://00f.net/2026/10/06/zeroization-3</id>
   <content type="html">&lt;p&gt;Welcome to part 3 on zeroization! We added a wipe in &lt;a href=&quot;/2026/10/06/zeroization-1/&quot;&gt;part 1&lt;/a&gt; and created an extra copy. And in &lt;a href=&quot;/2026/10/06/zeroization-2/&quot;&gt;part 2&lt;/a&gt;, we looked at some options to clear the stack and registers.&lt;/p&gt;

&lt;p&gt;But why did we need some of those copies in the first place? Turns out that sometimes the answer is right there in the API.&lt;/p&gt;

&lt;h2 id=&quot;do-we-need-that-buffer&quot;&gt;Do we need that buffer?&lt;/h2&gt;

&lt;p&gt;Let’s go back to the textbook example: MAC verification.&lt;/p&gt;

&lt;p&gt;We initialize the state of a MAC function (could be HMAC, an AEAD, etc.), update the state with a message to authenticate, finalize the state, compute the expected tag, compare it with the received tag, and return whether they match.&lt;/p&gt;

&lt;p&gt;In the toy example below, the state is just two 128-bit blocks, and to produce a tag, we just XOR them to produce a 128-bit tag.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stddef.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;final_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;__attribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noinline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;extract_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;final_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ct_verify16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;verify_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;final_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;extract_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ct_verify16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;noinline&lt;/code&gt; attribute deliberately prevents the extraction from being inlined, which is what a compiler would do in a real implementation if the extraction code is in a substantial finalization function.&lt;/p&gt;

&lt;p&gt;Look at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expected&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Even if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extract_tag()&lt;/code&gt; computes the tag entirely in registers, its interface requires it to write those bytes through an output pointer. And the caller has to provide storage for them.&lt;/p&gt;

&lt;p&gt;We could wipe that storage afterward, but we’d still have required it to exist.&lt;/p&gt;

&lt;p&gt;Let’s do something else, then: fuse the computation and the check of the tag in the same function:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;verify_fused&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;final_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                 &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s no output pointer for an intermediate tag here. Each extracted byte goes straight into the comparison, and the only result is a boolean.&lt;/p&gt;

&lt;p&gt;The state may still be sensitive information, but the expected tag is not present in stack memory. There’s no hard promise that a compiler wouldn’t spill a temporary, but it is very unlikely as it would make absolutely no sense from a performance perspective.&lt;/p&gt;

&lt;p&gt;So, simply combining finalization and verification can help mitigate leakage.&lt;/p&gt;

&lt;p&gt;The same reasoning applies to passing small keys by pointer. If an out-of-line callee needs an address, the caller may have to store a value it already has in a register.&lt;/p&gt;

&lt;p&gt;Passing by value can avoid that on a suitable ABI, even though there’s still no hard guarantee that the callee won’t spill it.&lt;/p&gt;

&lt;p&gt;So don’t pick the pointer version merely because it looks easier to wipe. Check the generated code and decide who owns the value and when they’re done with it.&lt;/p&gt;

&lt;h2 id=&quot;can-the-comparison-erase-the-tag&quot;&gt;Can the comparison erase the tag?&lt;/h2&gt;

&lt;p&gt;Here’s another tempting idea.&lt;/p&gt;

&lt;p&gt;We already XOR the tags to compare them, so why not overwrite the computed tag as we compare it with the candidate?&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Did that erase the computed tag?&lt;/p&gt;

&lt;p&gt;Looking at the source code, it should. But compilers are sneaky and in a all the tests I ran, I got &lt;strong&gt;exactly&lt;/strong&gt; the same instructions as a normal XOR/OR equality reduction.&lt;/p&gt;

&lt;p&gt;So, don’t try to be creative. Keep tag extraction close to that comparison where it avoids unnecessary storage, and inspect whether the compiler actually kept them together.&lt;/p&gt;

&lt;h2 id=&quot;webassembly-makes-this-worse&quot;&gt;WebAssembly makes this worse&lt;/h2&gt;

&lt;p&gt;In native code, the boundary we described in part 2 could clear the registers and stack region used by an operation.&lt;/p&gt;

&lt;p&gt;But WebAssembly is a different beast. WebAssembly code doesn’t control the machine registers or the engine’s native stack. The engine decides how its values are represented there.&lt;/p&gt;

&lt;p&gt;To the VM, a key is just an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i64&lt;/code&gt;, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v128&lt;/code&gt;, or some bytes in memory.&lt;/p&gt;

&lt;p&gt;And WebAssembly doesn’t define any ways to tell the engine to track sensitive copies and erase them. A key gets the same treatment as any other value of its type. Proposals to improve that situation have been ignored and eventually abandoned.&lt;/p&gt;

&lt;p&gt;In a JIT-based VM, that leaves another optimizer free to introduce copies, spill values, move calculations across a wipe, or discard an unused assignment of zero. The VM only cares about observable behavior.&lt;/p&gt;

&lt;p&gt;The application neither chooses those copies’ locations nor gets a way to clear them.&lt;/p&gt;

&lt;p&gt;WebAssembly itself is useless without external, imported functions provided by the runtime. Think about them like syscalls in a native environment.&lt;/p&gt;

&lt;p&gt;A WebAssembly application passing a secret to an external function implemented by the runtime has no control over it. The runtime can make as many copies as needed, and from within the WebAssembly sandbox, none of these copies is accessible and can be cleared.&lt;/p&gt;

&lt;h3 id=&quot;clearing-a-wasm-local-doesnt-clear-a-register&quot;&gt;Clearing a Wasm local doesn’t clear a register&lt;/h3&gt;

&lt;p&gt;Inspecting a WebAssembly module is not enough to predict whether secrets will be cleared, even if the disassembly of the module says so.&lt;/p&gt;

&lt;p&gt;Here’s an example of a module that XORs an input with a constant, uses that intermediate in a calculation, then assigns zero to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$intermediate&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-wat&quot;&gt;(module
  (func (export &quot;local_clear&quot;) (param $input i64) (result i64)
    (local $intermediate i64)
    (local $result i64)
    local.get $input
    i64.const 0x9e3779b97f4a7c15
    i64.xor
    local.tee $intermediate
    i64.const 17
    i64.shr_u
    local.get $intermediate
    i64.const 0xd6e8feb86659fd93
    i64.mul
    i64.xor
    local.set $result
    i64.const 0
    local.set $intermediate
    local.get $result))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We were able to defeat the C/Rust/Zig and LLVM optimizations, and we got a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local.set $intermediate&lt;/code&gt; that replaces the secret with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;. Perfect!&lt;/p&gt;

&lt;p&gt;But here comes the second compiler, the one that will compile the WebAssembly code to native code so it can actually run.&lt;/p&gt;

&lt;p&gt;I tested this with Wasmtime and Wasmer, using Cranelift and LLVM: both produced identical native function bodies with and without the local clear.&lt;/p&gt;

&lt;p&gt;Let’s look at the relevant Wasmer output, with constant loads omitted:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;eor     x8, x1, x8
mul     x9, x8, x9
eor     x0, x9, x8, lsr #17
ret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x8&lt;/code&gt; contains the secret. It’s not cleared, in spite of all our efforts and what the WebAssembly bytecode says.&lt;/p&gt;

&lt;h3 id=&quot;the-wipe-can-create-a-native-spill-too&quot;&gt;The wipe can create a native spill too&lt;/h3&gt;

&lt;p&gt;Let’s see another example:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;__attribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noinline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;check_and_wipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                   &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x5a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;same&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ct_verify16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;secure_zero&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;same&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The source compares first, then wipes.&lt;/p&gt;

&lt;p&gt;But compiling this with LLVM 23 shows that the compiled Wasm keeps the computed tag and candidate in locals, calls the wipe, and only then finishes the comparison.&lt;/p&gt;

&lt;p&gt;This is an acceptable thing to do for a compiler: it doesn’t change the function’s result or the bytes the wipe writes.&lt;/p&gt;

&lt;p&gt;But what does the engine do with those locals across the call?&lt;/p&gt;

&lt;p&gt;With Wasmtime, this is what I saw (relevant instructions only):&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;str     q20, [x0, w4, uxtw]
stur    q20, [sp, #0x10]
bl      #0x380
ldur    q20, [sp, #0x10]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x0&lt;/code&gt; is the base of linear memory and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w4&lt;/code&gt; is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expected&lt;/code&gt; offset within it.&lt;/p&gt;

&lt;p&gt;The first store writes the computed tag there. The second writes the same tag to the engine’s native stack.&lt;/p&gt;

&lt;p&gt;The call at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x380&lt;/code&gt; clears the linear-memory object, and the final load brings back the untouched native copy for comparison.&lt;/p&gt;

&lt;p&gt;The complete function returns without clearing that spill slot.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expected&lt;/code&gt; is cleared, but the engine still has another copy, at an address the module can’t wipe.&lt;/p&gt;

&lt;h3 id=&quot;even-a-module-without-memory-uses-memory&quot;&gt;Even a module without memory uses memory&lt;/h3&gt;

&lt;p&gt;Let’s remove linear memory completely.
No C stack, no buffers, no imports, and no load or store instructions.
This complete module just computes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x XOR (4 * (x + 1))&lt;/code&gt;, with arithmetic modulo 2^64:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-wat&quot;&gt;(module
  (func $triple (param $x i64) (result i64)
    local.get $x
    i64.const 3
    i64.mul)
  (func $combine (param $x i64) (result i64)
    local.get $x
    local.get $x
    call $triple
    i64.add)
  (func $across (export &quot;across&quot;) (param $x i64) (result i64)
    local.get $x
    local.get $x
    i64.const 1
    i64.add
    call $combine
    i64.xor))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; were sensitive, where would it go?&lt;/p&gt;

&lt;p&gt;Let’s try with Cranelift: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;across()&lt;/code&gt; keeps its original input in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x19&lt;/code&gt; while calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;combine()&lt;/code&gt;.
But &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;combine()&lt;/code&gt; also wants to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x19&lt;/code&gt;, so it saves the caller’s value on the native stack:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;str     x19, [sp, #-0x10]!
mov     x19, x4
bl      #0
add     x2, x19, x2
ldr     x19, [sp], #0x10
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bl #0&lt;/code&gt; calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;triple()&lt;/code&gt;. The final load restores &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x19&lt;/code&gt;, but leaves the input on the native stack after both functions return.&lt;/p&gt;

&lt;p&gt;The wasm module can’t do anything to wipe it.&lt;/p&gt;

&lt;h3 id=&quot;a-note-on-volatile&quot;&gt;A note on “volatile”&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;volatile&lt;/code&gt; attribute is understood by source-to-wasm compilers and can keep a buffer wipe in the generated WebAssembly module.&lt;/p&gt;

&lt;p&gt;But the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;volatile&lt;/code&gt; attribute doesn’t exist in the specification; from a WebAssembly runtime perspective, clearing memory with or without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;volatile&lt;/code&gt; makes no difference: the code is exactly the same, so it will be optimized out the same way.&lt;/p&gt;

&lt;p&gt;By the way, in WebAssembly, memory barriers don’t do anything either. So don’t expect them to mitigate speculative execution or help mitigate side channels.&lt;/p&gt;

&lt;h2 id=&quot;so-what-can-we-do&quot;&gt;So what can we do?&lt;/h2&gt;

&lt;p&gt;Zeroing memory using compiled languages and/or virtual machines is pretty much impossible to do correctly.&lt;/p&gt;

&lt;p&gt;My recommendations for library authors:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Inspect the generated code. This is the source of truth, not the source code.&lt;/li&gt;
  &lt;li&gt;Wipe secrets that will almost certainly be stored in memory, such as passwords, hex/base64-encoded keys, etc.&lt;/li&gt;
  &lt;li&gt;Always wipe secrets present on the heap&lt;/li&gt;
  &lt;li&gt;Use protected memory when possible. For example, the &lt;a href=&quot;https://libsodium.gitbook.io/doc/memory_management&quot;&gt;libsodium guarded memory functions&lt;/a&gt; are quite useful. If only to find bugs in implementations.&lt;/li&gt;
  &lt;li&gt;For everything else, small values that are actually used to perform computations, don’t bother.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My recommendations for library users:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use encrypted swap and encrypted filesystems, and configure your OS and hardware correctly.&lt;/li&gt;
  &lt;li&gt;Apply the recommendations from part 2 if you’re really worried about dangling secrets in memory and registers. Write libraries like &lt;a href=&quot;https://github.com/jedisct1/zig-secretstack&quot;&gt;zig-secretstack&lt;/a&gt; for your favorite programming language that are easy to use.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Zeroization, part 2: Clearing the stack and registers</title>
    <link href="https://00f.net/2026/10/06/zeroization-2/"/>
   <updated>2026-10-06T00:00:00+02:00</updated>
   <id>https://00f.net/2026/10/06/zeroization-2</id>
   <content type="html">&lt;p&gt;In &lt;a href=&quot;/2026/10/06/zeroization-1/&quot;&gt;part 1&lt;/a&gt;, we saw that code intended to wipe secrets can end up creating extra copies.&lt;/p&gt;

&lt;p&gt;So what can we do?&lt;/p&gt;

&lt;p&gt;The best mitigation is to use a compiler designed to natively address that issue.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://jasmin-lang.readthedocs.io/en/stable/&quot;&gt;Jasmin&lt;/a&gt; is a perfect example of this. Jasmin is a high-level assembler, similar to Dan Bernstein’s &lt;a href=&quot;https://cr.yp.to/qhasm.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qhasm&lt;/code&gt;&lt;/a&gt;, that makes writing assembly code very pleasant, if only because it can do automatic register allocation.&lt;/p&gt;

&lt;p&gt;But Jasmin does a little bit more: the compiler is able to prove that values tagged as secrets are never returned as public values, are not involved in operations that wouldn’t run in constant time, and are safe against some classes of SPECTRE attacks. And for code using pointers, it’s even able to prove memory safety.&lt;/p&gt;

&lt;p&gt;In addition to that, and this is where things become relevant to our topic, &lt;a href=&quot;https://eprint.iacr.org/2023/1713&quot;&gt;Jasmin can automatically wipe all the values tagged as secret&lt;/a&gt; before returning from a function, regardless of where they live (registers or stack memory).&lt;/p&gt;

&lt;p&gt;Jasmin is an awesome tool for generating high-performance, high-assurance code.&lt;/p&gt;

&lt;p&gt;And for a real-world example of Jasmin source code, check out the &lt;a href=&quot;https://github.com/aegis-aead/aegis-jasmin&quot;&gt;Jasmin implementations of AEGIS&lt;/a&gt;, which are also the fastest implementations of AEGIS to date.&lt;/p&gt;

&lt;p&gt;Unfortunately, writing everything in Jasmin is not always practical, so we have to find mitigations for common compiled languages.&lt;/p&gt;

&lt;h2 id=&quot;decide-where-the-operation-ends&quot;&gt;Decide where the operation ends&lt;/h2&gt;

&lt;p&gt;Instead of trying to solve the problem at the microscopic level, we can take a higher-level approach: find clear boundaries within which secrets are used, but beyond which they’re no longer used.&lt;/p&gt;

&lt;p&gt;Suppose our application derives an ephemeral key, processes a batch of records, and then discards the key.
We can put that whole sequence inside one boundary:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enter boundary (function)
    derive ephemeral key
    process records
    retire owned key and context buffers
leave boundary
    clear stack used by the work and its callees
    clear residual registers
return status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice where the key derivation happens: inside the boundary.&lt;/p&gt;

&lt;p&gt;For practical purposes, a boundary will be a function, so if we derive the ephemeral key in the caller first, we may already have copies in that caller’s frame or preserved registers.&lt;/p&gt;

&lt;h2 id=&quot;lets-ask-the-compiler&quot;&gt;Let’s ask the compiler&lt;/h2&gt;

&lt;p&gt;GCC and Clang have little-known compilation flags to automatically clear registers before a function returns:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;clang &lt;span class=&quot;nt&quot;&gt;-O3&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-fzero-call-used-regs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;all &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; crypto.c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The flag applies to the compilation unit, but with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zero_call_used_regs(&quot;all&quot;)&lt;/code&gt; attribute, we can apply this selectively to relevant functions instead:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;__attribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noinline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zero_call_used_regs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;all&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;secret_batch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now look at the end of the leaky example from part 1, compiled with that feature enabled. After calculating the return value, we get these instructions among the added overwrites:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov     x8, #0
mov     x9, #0
movi.2d v0, #0000000000000000
movi.2d v1, #0000000000000000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There we go. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x8&lt;/code&gt; actually gets cleared this time.
And the result in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x0&lt;/code&gt; survives, as it should.&lt;/p&gt;

&lt;p&gt;This &lt;a href=&quot;https://godbolt.org/z/nc5KKe44o&quot;&gt;Godbolt comparison&lt;/a&gt; shows the same source with and without register cleanup.&lt;/p&gt;

&lt;p&gt;This is a great improvement, but it’s not enough. It doesn’t discover spills in callees, and a spotless wrapper epilogue can leave a key schedule below it on the stack.&lt;/p&gt;

&lt;p&gt;Now let’s ask GCC to handle the stack as well:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc &lt;span class=&quot;nt&quot;&gt;-O2&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-fstrub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;all &lt;span class=&quot;nt&quot;&gt;-fzero-call-used-regs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;all &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; crypto.c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strub&lt;/code&gt; mechanism tracks stack use with a watermark, then clears the recorded region after the call. &lt;a href=&quot;https://gcc.gnu.org/onlinedocs/gcc/Stack-Scrubbing.html&quot;&gt;GCC documents those interfaces here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strub&lt;/code&gt; contract&lt;/a&gt; is also a useful read.&lt;/p&gt;

&lt;h2 id=&quot;what-about-our-own-register-wiping-helper&quot;&gt;What about our own register-wiping helper?&lt;/h2&gt;

&lt;p&gt;We saw compiler-specific flags. But why not write our own implementations in portable C, maybe just with a bit of assembly? Writing a few zeroing instructions sounds easy enough.&lt;/p&gt;

&lt;p&gt;Unfortunately, there’s a trap: ABIs. Calling conventions that typically require functions to preserve a set of registers.&lt;/p&gt;

&lt;p&gt;As an illustration, let’s look at what happens when an ordinary function tries to clear a register it must preserve for its caller. Here’s an abbreviated AArch64 sequence:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;str     x19, [sp, #-16]!
mov     x19, #0
ldr     x19, [sp], #16
ret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There’s our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mov x19, #0&lt;/code&gt;. Code we presumably explicitly added to clear the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x19&lt;/code&gt; register containing a secret.
And right after it… &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ldr&lt;/code&gt; restores the old value.&lt;/p&gt;

&lt;p&gt;Oops! The helper has undone its own wipe :)&lt;/p&gt;

&lt;p&gt;The caller gets its register back, as the ABI requires, and the temporary stack save is still in memory too.&lt;/p&gt;

&lt;p&gt;So, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wipe_registers()&lt;/code&gt; helper (which itself is trickier to write than it appears, especially for software supposed to be portable) can’t freely destroy all registers and remain an ordinary ABI-compliant call.&lt;/p&gt;

&lt;p&gt;And putting the registers in an inline assembly clobber list doesn’t get us out of this either. It would just tell the compiler to protect live values, which can mean making more copies.&lt;/p&gt;

&lt;p&gt;A boundary wrapper instead preserves the caller’s original state, runs the secret work, clears that work’s stack, and clears the residual register state it may discard.&lt;/p&gt;

&lt;h2 id=&quot;what-about-our-own-stack-wiping-helper&quot;&gt;What about our own stack-wiping helper?&lt;/h2&gt;

&lt;p&gt;How much stack should we wipe when leaving a boundary?&lt;/p&gt;

&lt;p&gt;This is not easy to answer: adding up our local arrays isn’t enough. We need to include callees, compiler spills, alignment, saved registers, and ABI areas such as the x86_64 red zone. Yay.&lt;/p&gt;

&lt;p&gt;We could measure how deep one call goes, but recursion, variable-sized frames, callbacks, and instrumentation can change it.&lt;/p&gt;

&lt;p&gt;Compilers have flags that can help track the stack usage of functions, but in practice, this is a mess of hard-coded, hard-to-maintain, unreliable limits that may become invalid as soon as a single change is made to the code or compiler.&lt;/p&gt;

&lt;h2 id=&quot;giving-the-work-its-own-stack&quot;&gt;Giving the work its own stack&lt;/h2&gt;

&lt;p&gt;A different approach: let’s give an operation involving secrets a dedicated stack. We can run the sensitive sequence there, switch back, and clear the whole allocation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We&lt;/em&gt; allocate the region, so we know where it starts and where it ends.
We know that wiping the entire region is going to wipe everything stored on the stack inside the boundary.&lt;/p&gt;

&lt;p&gt;Plus, the region can be surrounded by guard pages, marked for exclusion from swap and core dumps, etc.
Keeping the stack that holds secrets in a different (and randomized) memory location than the regular application stack also makes a whole class of attacks more difficult.&lt;/p&gt;

&lt;p&gt;I wrote C and Zig (&lt;a href=&quot;https://github.com/jedisct1/zig-secretstack&quot;&gt;secretstack&lt;/a&gt;) implementations. For reference, the C implementation has a simple entry point that looks like this:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;secure_call_on_stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stack_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The callback returns a status and receives its inputs through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctx&lt;/code&gt;. And this is what the wrapper does after the callback returns:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;    mov     sp, x21
    mov     x9, x19
    b       2f
1:  stp     xzr, xzr, [x9], #16
2:  cmp     x9, x20
    b.lo    1b
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x21&lt;/code&gt; holds the wrapper’s original stack pointer. We’re switching back before overwriting the dedicated stack.
Then the dedicated stack gets wiped: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x19&lt;/code&gt; holds the start of the region and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x20&lt;/code&gt; its end.&lt;/p&gt;

&lt;p&gt;The beauty of this approach is that it works with virtually anything. Any existing library. Even if that library didn’t itself try to wipe the secrets it receives or creates, we end up with zero copies of these secrets in memory after the function returns.&lt;/p&gt;

&lt;p&gt;Clear the registers in addition to this, and you’re in pretty good shape.&lt;/p&gt;

&lt;p&gt;Unfortunately, there’s more to switching stacks than changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sp&lt;/code&gt;. Unwinding, sanitizers, profilers, and language runtimes may keep their own stack bookkeeping, so integration can be tricky.&lt;/p&gt;

&lt;p&gt;Either support exceptions, cancellation, and nonlocal exits through boundaries, or (way easier) prohibit them and enforce normal callback return.&lt;/p&gt;

&lt;p&gt;And of course, ban &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;longjmp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another issue is that if a signal interrupts the work, its saved register context may land on a signal stack outside the region we’re about to erase, because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sigaltstack()&lt;/code&gt; uses a signal-handler stack.&lt;/p&gt;

&lt;p&gt;So, one has to remain careful about what runs inside a boundary. But these constraints can usually be easily satisfied for cryptographic code.&lt;/p&gt;

&lt;h2 id=&quot;overhead&quot;&gt;Overhead&lt;/h2&gt;

&lt;p&gt;Of course, clearing entire regions and tons of registers has a cost.&lt;/p&gt;

&lt;p&gt;If the boundary only encrypts or hashes a short input, it’s likely that clearing registers and a dedicated stack can take longer than the operation itself.&lt;/p&gt;

&lt;p&gt;However, doing it once after a handshake or a batch that shares the same secret lifetime is likely to have a negligible cost.&lt;/p&gt;

&lt;p&gt;As always in security, everything is about tradeoffs.&lt;/p&gt;

&lt;p&gt;See you in &lt;a href=&quot;https://00f.net/2026/10/06/zeroization-3/&quot;&gt;part 3&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Zeroization, part 1: Wiping can make things worse</title>
    <link href="https://00f.net/2026/10/06/zeroization-1/"/>
   <updated>2026-10-06T00:00:00+02:00</updated>
   <id>https://00f.net/2026/10/06/zeroization-1</id>
   <content type="html">&lt;p&gt;A good hygiene when processing secrets is to wipe them after use.&lt;/p&gt;

&lt;p&gt;And projects receives countless PRs about adding calls to zeroization functions for anything that looks like a secret.&lt;/p&gt;

&lt;p&gt;This is a low-hanging fruit, something LLMs love to report, and it feels theoretically useful. But unfortunately, things are a little bit more complicated. Blindly zeroing secrets can do more harm than good.&lt;/p&gt;

&lt;p&gt;Turns out that adding a wipe for a secret can leave &lt;strong&gt;more&lt;/strong&gt; copies of a secret than the original code. Copies that wouldn’t have existed without it, and that are still there after it returns.&lt;/p&gt;

&lt;p&gt;Code snippets and their compiled output can be verified in this &lt;a href=&quot;https://godbolt.org/z/zaMY8WajG&quot;&gt;Godbolt example&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;lets-start-with-memset&quot;&gt;Let’s start with memset()&lt;/h2&gt;

&lt;p&gt;Classic starting point: the good old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memset()&lt;/code&gt; call that gets optimized out:&lt;/p&gt;

&lt;p&gt;We calculate an intermediate value, use it to produce a result, then clear it before returning.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;uint64_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ordinary_memset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint64_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint64_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UINT64_C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x9e3779b97f4a7c15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint64_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UINT64_C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0xd6e8feb86659fd93&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memset()&lt;/code&gt; and compare the generated instructions. They are identical.&lt;/p&gt;

&lt;p&gt;The compiler was smart enough to understand that wiping &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;intermediate&lt;/code&gt; is a useless operation since after the function returns, it’s never read again.&lt;/p&gt;

&lt;p&gt;But before we try to fix this, notice something else in the assembly output: the intermediate never went into memory at all. It stayed in registers.&lt;/p&gt;

&lt;h2 id=&quot;lets-make-the-stores-survive&quot;&gt;Let’s make the stores survive&lt;/h2&gt;

&lt;p&gt;The most common practice to address the issue: replacing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memset()&lt;/code&gt; with volatile byte stores:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;volatile&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;volatile&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s do it and observe the arm64 output (I used XCode, shipping Clang 21):&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;eor     x8, x0, x8
strb    wzr, [sp, #8]
strb    wzr, [sp, #7]
strb    wzr, [sp, #6]
strb    wzr, [sp, #5]
strb    wzr, [sp, #4]
strb    wzr, [sp, #3]
strb    wzr, [sp, #2]
strb    wzr, [sp, #1]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Eight &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strb&lt;/code&gt; instructions. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wzr&lt;/code&gt; supplies zero.&lt;/p&gt;

&lt;p&gt;Perfect, we defeated the compiler’s optimization passes and actually wrote eight zero bytes to stack memory!&lt;/p&gt;

&lt;p&gt;Wait… where’s the instruction that put our intermediate into those stack slots?&lt;/p&gt;

&lt;p&gt;Surprise: there isn’t one.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x8&lt;/code&gt; holds the intermediate. We’ve just written eight zero bytes to locations that never received the value we wanted to erase.&lt;/p&gt;

&lt;p&gt;The function then calculates the result from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x8&lt;/code&gt; and returns without clearing it.&lt;/p&gt;

&lt;p&gt;The actual secret value is still around and we just wasted CPU cycles for nothing.&lt;/p&gt;

&lt;h2 id=&quot;what-if-we-hide-the-wipe-from-the-compiler&quot;&gt;What if we hide the wipe from the compiler?&lt;/h2&gt;

&lt;p&gt;Let’s try another common approach: put the wiping code in a dedicated function, in a separately compiled file, without LTO.&lt;/p&gt;

&lt;p&gt;You know, some variant of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;secure_zero()&lt;/code&gt; function that can be found everywhere in cryptographic libraries:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stddef.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;opaque_wipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;volatile&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The caller only sees the declaration, so it can’t inspect the implementation when compiling this call:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;opaque_wipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intermediate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Cool, so let’s look at the caller’s assembly output now.&lt;/p&gt;

&lt;p&gt;Intestering: there’s a new instruction before the call:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;str     x8, [sp, #8]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Duh? We’re storing the intermediate on the stack now? The original function didn’t do that!&lt;/p&gt;

&lt;p&gt;Yep, the wipe needs an address. And since the caller can’t see the implementation, it has to allow for the callee reading the old contents at that address.
So it writes the value there first.&lt;/p&gt;

&lt;p&gt;The wipe clears that new stack copy and leaves &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x8&lt;/code&gt; alone.&lt;/p&gt;

&lt;p&gt;We’ve introduced an interval during which the value exists in memory, paid to clear it, and still kept the register copy.&lt;/p&gt;

&lt;p&gt;Also, a recognized write-only operation can skip this particular store, and if LTO is enabled (quite the norm these days), our attempt to hide the implementation in another file is likely to become useless.&lt;/p&gt;

&lt;h2 id=&quot;now-lets-compare-a-tag&quot;&gt;Now let’s compare a tag&lt;/h2&gt;

&lt;p&gt;Now let’s do something very common but a little less trivial: compute a tag (HMAC output, etc.), compare it, wipe it, return the result.&lt;/p&gt;

&lt;p&gt;In this example, we’re going to compute the tag as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seed[i] ^ 0x5a&lt;/code&gt;. This is completely dumb and insecure, but easy to follow through the assembly.&lt;/p&gt;

&lt;p&gt;We want to securely compare it against an application-provided tag.&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stddef.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;opaque_wipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compare_and_wipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                     &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x5a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;equal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;different&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;opaque_wipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Compile the caller and wipe separately, without LTO:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;clang &lt;span class=&quot;nt&quot;&gt;-std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;c11 &lt;span class=&quot;nt&quot;&gt;-O3&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Wall&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Wextra&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Werror&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; compare.c &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; compare.s
clang &lt;span class=&quot;nt&quot;&gt;-std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;c11 &lt;span class=&quot;nt&quot;&gt;-O3&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Wall&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Wextra&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Werror&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; wipe.c &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; wipe.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the C code, the comparison appears before the wipe.&lt;/p&gt;

&lt;p&gt;But let’s take a closer look at the disassembled output.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;sub     sp, sp, #80
stp     x29, x30, [sp, #64]
add     x29, sp, #64
ldr     q0, [x0]
movi.16b v1, #90
eor.16b v0, v0, v1
str     q0, [sp, #16]
stur    q0, [x29, #-24]
ldr     q0, [x1]
str     q0, [sp]
sub     x0, x29, #24
mov     w1, #16
bl      _opaque_wipe
ldp     q1, q0, [sp]
cmeq.16b v0, v1, v0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Wait. Why are there two stores of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q0&lt;/code&gt; after the XOR?&lt;/p&gt;

&lt;p&gt;And why is the comparison instruction, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmeq&lt;/code&gt;, &lt;em&gt;after&lt;/em&gt; the call to the wipe?&lt;/p&gt;

&lt;p&gt;To understand, let’s map the stack. We’re going to call the stack pointer after allocating the frame &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt;; the frame pointer, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x29&lt;/code&gt;, is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S + 64&lt;/code&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Stack bytes&lt;/th&gt;
      &lt;th&gt;Contents&lt;/th&gt;
      &lt;th&gt;Wiped?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S + 15&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Candidate&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S + 16&lt;/code&gt; through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S + 31&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Extra computed-tag copy&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S + 40&lt;/code&gt; through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S + 55&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Addressed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;computed&lt;/code&gt; object&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Ah! The compiler loaded the comparison operands before the call, but postponed the comparison itself until afterward.&lt;/p&gt;

&lt;p&gt;To keep the computed tag available across the call, &lt;strong&gt;it made a second copy&lt;/strong&gt; in a spill slot.&lt;/p&gt;

&lt;p&gt;Our wipe clears the requested 16 bytes perfectly, no problem here. And then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ldp&lt;/code&gt; reloads the other copy, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmeq&lt;/code&gt; compares it.&lt;/p&gt;

&lt;p&gt;But nothing clears that spill slot before the function returns!&lt;/p&gt;

&lt;p&gt;Now the fun part: what if we &lt;em&gt;remove&lt;/em&gt; the wipe and compile again?&lt;/p&gt;

&lt;p&gt;Surprise: the computed tag no longer gets stored on the stack at all. Everything stays in registers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding the wipe created the copy we left behind.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note that there’s no compiler bug here. The return value is correct, the addressed object is overwritten, and C doesn’t promise the security ordering you intended between those two events.&lt;/p&gt;

&lt;p&gt;You can compare both functions in &lt;a href=&quot;https://godbolt.org/z/Pba6nrcbz&quot;&gt;Godbolt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There, the addressed object is at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sp + 32&lt;/code&gt; rather than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sp + 40&lt;/code&gt; (which I got with Xcode), but the extra copy at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sp + 16&lt;/code&gt; still survives the wipe.&lt;/p&gt;

&lt;p&gt;Disabling stack protection generates different code, but this is more of an accidental unreliable side effect than a fix or a good idea.&lt;/p&gt;

&lt;p&gt;So we’re sometimes paying for stores, and sometimes for a new call and stack copy, and the secrets are still around. Pretty frustrating for a change that was supposed to improve cleanup.&lt;/p&gt;

&lt;h2 id=&quot;registers-shouldnt-be-ignored&quot;&gt;Registers shouldn’t be ignored&lt;/h2&gt;

&lt;p&gt;Registers are copied to memory after every context switch. Registers are visible in core dumps, hibernation files and VM snapshots. Registers can leak through microarchitectural bugs (&lt;a href=&quot;https://lock.cmpxchg8b.com/zenbleed.html&quot;&gt;Zenbleed&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;And a single SIMD register can hold an entire 256-bit secret key, or even a 512-bit hash. They also tend to be recycled less frequently than general-purpose registers. But traditional zeroing functions are just designed to write zeros to memory, and have no clue about actual execution flows.&lt;/p&gt;

&lt;h2 id=&quot;keep-the-useful-wipes&quot;&gt;Keep the useful wipes&lt;/h2&gt;

&lt;p&gt;Does this mean we should stop wiping password buffers?
No: those buffers already exist in memory, and clearing them when we’re done is useful. The same goes for an allocated key schedule or a context we’re retiring.&lt;/p&gt;

&lt;p&gt;But in the examples above, the values we cared about were also somewhere else.&lt;/p&gt;

&lt;p&gt;So before adding a wipe to a small local, inspect whether the value already lives in memory, whether taking its address creates storage, and what stays live across the call.
Check the optimized build you ship, including LTO and hardening flags.&lt;/p&gt;

&lt;p&gt;Or stop blindly sprinkling calls to buffer wiping functions. There are more reliable techniques to wipe secrets.&lt;/p&gt;

&lt;p&gt;We’ll see that in &lt;a href=&quot;https://00f.net/2026/10/06/zeroization-2/&quot;&gt;part 2&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Do your employees actually use your product?</title>
    <link href="https://00f.net/2026/09/25/do-your-employees-actually-use-your-product/"/>
   <updated>2026-09-25T00:00:00+02:00</updated>
   <id>https://00f.net/2026/09/25/do-your-employees-actually-use-your-product</id>
   <content type="html">&lt;p&gt;I’m old, and I’ve worked with a lot of companies, large and small.
Some employed me, while others were clients.&lt;/p&gt;

&lt;p&gt;The most innovative and productive companies I’ve worked with had something in common: employees used the company’s products because they wanted to.&lt;/p&gt;

&lt;p&gt;When I worked on &lt;a href=&quot;https://fr.wikipedia.org/wiki/Skyrock.com&quot;&gt;Skyblog&lt;/a&gt;, Skyrock’s French blogging platform, everybody there used the product. And I really mean it: everybody. Night and day. And we had a lot of fun with it.&lt;/p&gt;

&lt;p&gt;So we cared &lt;em&gt;a lot&lt;/em&gt; about it as both employees and as &lt;em&gt;users&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Skyblog is gone now, but my former colleagues and I still have great memories of working on it, even though we worked like crazy.&lt;/p&gt;

&lt;p&gt;That willingness to use the product tells me more about a company than the size of its budget.&lt;/p&gt;

&lt;p&gt;A company can hire excellent people and have a convincing plan for the future. But neither guarantees that anyone there will choose the product when they have a choice.&lt;/p&gt;

&lt;p&gt;That’s why I like seeing employees use their company’s product for their own projects outside work. They might even be paying for it.&lt;/p&gt;

&lt;p&gt;Using it at work can mean nothing more than testing a feature or preparing a demo for a customer. But that doesn’t tell you whether someone would choose it for themselves.&lt;/p&gt;

&lt;p&gt;When you’re trying to do something for yourself, the product’s limitations become your problem too, so you want them fixed.&lt;/p&gt;

&lt;p&gt;Knowing how the code works won’t spare you the annoyance of a bug that gets in your way.
And once you’ve had to live with that bug, you understand the complaint a lot better than you did while reading its description.&lt;/p&gt;

&lt;p&gt;In my experience, people who enjoy using a product already have a reason to want it improved.&lt;/p&gt;

&lt;p&gt;They get something out of the work themselves. That sort of attachment is hard to create through pressure or a company culture program.&lt;/p&gt;

&lt;p&gt;It also gives people ideas about what to build next: as they keep using the product, they notice what’s missing and start thinking about how they’d like it to work. So they can suggest something useful before a customer has had to ask for it.&lt;/p&gt;

&lt;p&gt;A product manager can know the market very well and still get something out of using the product every day. Developers can too.&lt;/p&gt;

&lt;p&gt;Building a feature and relying on it for your own project give you different experiences, and I’ve learned to be careful about assuming one gives you the other.&lt;/p&gt;

&lt;p&gt;For that to work, employees who report a problem need the same attention as other customers.&lt;/p&gt;

&lt;p&gt;They should be able to get an answer without having to chase several teams. If a report from an employee is harder to get addressed than the same report sent through customer support, something needs fixing in that process.&lt;/p&gt;

&lt;p&gt;And people who enjoy using a product recommend it to others. They can explain why it’s useful because they’ve relied on it themselves.&lt;/p&gt;

&lt;p&gt;That can benefit the company years later, when a former employee recommends the product to colleagues at another job.&lt;/p&gt;

&lt;p&gt;Their new company may become a customer because someone there knows how to get useful work done with it. Having worked on the code alone doesn’t give someone the same experience to draw on.&lt;/p&gt;

&lt;p&gt;Customer requests are useful, but if every change starts with a request from a customer or a potential customer, the company is always waiting for somebody else to identify the next thing to do.&lt;/p&gt;

&lt;p&gt;Employees who use the product can bring ideas of their own.&lt;/p&gt;

&lt;p&gt;So when I think about what helps a company come up with useful improvements, one of the first things I’d look at is whether its employees choose to use what they’re building.&lt;/p&gt;
</content>
  </entry>
  
</feed>

