
Detect compiler-invented memory loads that turn secure C into TOCTOU vulnerabilities. Includes automated source audits, Unicorn-based binary analysis, and compiler/arch/flag sweeps across 100+ projects.
"...the definition of 'sane compiler' grows ever looser."
The binary you run is not the program you wrote. The compiler optimizer rewrites
your source in ways you never see — and some of those changes can
silently and legally turn seemingly secure code into
vulnerable binaries. The same line can be
safe under one compiler and exploitable under another,
with nothing in the source to tell you which: a vulnerability held in
superposition, collapsed only when you build. Schrödinger's TOCTOU explores
compiler-invented loads and their widespread
implications for time-of-check to time-of-use (TOCTOU) vulnerabilities — found
across open-source
kernels,
hypervisors,
enclaves,
firmware,
and libraries.
Everywhere we look, seemingly secure code is left exposed to the whims of the
compiler. But those are a sample, not a boundary; the
same bugs are very likely in your code too.
Start with something easy.
How many times does this function load *p?
unsigned int g(unsigned short *p)
{
short t = *p; /* copy *p into a local for safekeeping */
return (unsigned short)t - t;
}
Hint: the answer is 1 — the source loads *p a single time into t.
Paste it into Compiler Explorer (arm gcc 14.2.0,
-O2) and count the loads from r0, which holds p:
g:
ldrh r2, [r0] # load *p, once
ldrsh r0, [r0] # load *p, twice
subs r0, r2, r0
bx lr
One load in the source, two in the binary. The second is an invented load — a read the compiler manufactured that you never wrote. It is legal under the C abstract machine, which assumes memory cannot change between two reads. But when that memory is attacker-writable, the assumption becomes an exploit: the invented load can fall after a security check, silently reopening a time-of-check to time-of-use (TOCTOU) window the programmer believed they had closed. The value you validated and the value you use are no longer guaranteed to be the same — even though you never wrote code that re-read it.
The challenge proves the invented load exists; let's see how that turns into memory corruption.
In a TOCTOU vulnerability, a program checks that a value is safe, then uses the value. However, a window for exploitation exists if an attacker can change the value in the sliver of time between those two reads – the harmless value passes the check while the dangerous one is the one that gets used:
if (shared->len <= 20) // CHECK reads shared->len
// ** attacker modifies shared->len **
memcpy(out, shared->data, shared->len); // USE reads it again: buffer overflow
The textbook fix is to snapshot first: copy any data the attacker might
tamper with into a local the attacker can't reach, and then trust nothing but
that local. Once len is in a local it is frozen — an attacker racing the
shared memory can no longer touch it — so the check and the copy are guaranteed
to see the same value. That is how the code in receive below fixes the TOCTOU:
it snapshots the message, validates the snapshot, and publishes the validated
copy into slot for a consumer to forward:
#include <string.h>
struct message {
int len; /* payload length */
char data[20]; /* payload */
};
struct message slot; /* the most recently validated message */
char out[20]; /* fixed 20-byte destination */
void receive(struct message *shared) {
struct message local = *shared; /* 1. snapshot untrusted input */
if (local.len <= 20) /* 2. validate the snapshot */
slot = local; /* 3. publish the validated copy */
}
void forward(void) { /* the time of use, later */
memcpy(out, slot.data, slot.len); /* slot.len was checked <= 20 ... right? */
}
By the source, this is correct. len is read exactly once — into the snapshot —
so the value that clears the <= 20 check is the value published into slot.
The TOCTOU window is closed and the code is safe.
Except it isn't. Under x86-64 gcc -O2, receive reads it from the
original shared memory twice: once
as a scalar to gate the check, and again as part of the bulk
copy that gets published into slot:
receive:
cmp DWORD PTR [rdi], 20 ; READ #1: the CHECK reads shared->len directly
movdqu xmm0, XMMWORD PTR [rdi] ; READ #2: the bulk copy re-reads it (len is byte 0)
mov rax, QWORD PTR [rdi+16] ; (the bulk copy's tail: struct bytes 16-23)
jg .L1 ; len > 20? skip the publish
mov QWORD PTR slot[rip+16], rax ; (publish that tail)
movaps XMMWORD PTR slot[rip], xmm0 ; and publish the TOCTOU-vulnerable snapshot
.L1:
ret
forward:
movsx rdx, DWORD PTR slot[rip] ; copy size = slot.len, the unchecked READ #2 value
mov esi, OFFSET FLAT:slot+4 ; src = slot.data
mov edi, OFFSET FLAT:out ; dst = out[20]
jmp memcpy ; copies slot.len bytes into out[20]
The check runs on READ #1; the value that lands in slot.len is READ #2. An
attacker who flips len between them passes a safe value to the <= 20 check
while an oversized one is published into slot — and forward then copies that
many bytes into out[20], the exact overflow the snapshot was meant to prevent,
reintroduced by the optimizer.
This is turned into a complete proof-of-concept in
poc/example.c, where the code uses the canonical
TOCTOU-hardened approach: an untrusted message struct gets snapshotted into
local so that it cannot be modified, the snapshot's local.len is validated
against the buffer capacity, and only the validated copy is published into
slot; a consumer later copies slot.len payload bytes into a fixed buffer.
Simultaneously, an attacker races shared->len. An unexpected invented load
from the compiler re-reads shared->len for the bulk publish, so slot.len
carries the attacker's oversized value even though the check passed —
reintroducing the TOCTOU the programmer was trying to defend against, and
creating a seemingly impossible buffer overflow — from thin air.