
कंपाइलर-निर्मित मेमोरी लोड का पता लगाएं जो सुरक्षित C को TOCTOU कमजोरियों में बदल देते हैं। इसमें स्वचालित सोर्स ऑडिट, Unicorn-आधारित बाइनरी विश्लेषण, और 100+ प्रोजेक्ट्स में कंपाइलर/आर्क/फ्लैग स्वीप शामिल हैं।
..."'sane compiler' की परिभाषा और ढीली होती जाती है।"
आप जो बाइनरी चलाते हैं वह वह प्रोग्राम नहीं है जो आपने लिखा था। कंपाइलर ऑप्टिमाइज़र पुनर्लेखित
आपके सोर्स को ऐसे तरीकों से जिन्हें आप कभी नहीं देख पाते — और उनमें से कुछ परिवर्तन
चुपचाप और वैध रूप से प्रतीत होने वाले सुरक्षित कोड को
भेद्य बाइनरियों में बदल सकते हैं। एक ही पंक्ति
एक कंपाइलर के अंतर्गत सुरक्षित और दूसरे के अंतर्गत शोषण योग्य,
और सोर्स में कुछ भी नहीं जो आपको बताए कि कौन-सा है: एक भेद्यता जो
सुपरपोज़िशन में रखी है, केवल बिल्ड करने पर संघटित होती है। Schrödinger's TOCTOU अन्वेषण करता है
कंपाइलर-निर्मित लोड तथा उनके व्यापक
समय-जाँच से समय-उपयोग (TOCTOU) भेद्यताओं के लिए निहितार्थ — पाए गए
विभिन्न ओपन-सोर्स
कर्नेल,
हाइपरवाइज़र,
एन्क्लेव,
फर्मवेयर,
और लाइब्रेरियों में।
हम जहाँ भी देखते हैं, प्रतीत होने वाला सुरक्षित कोड कंपाइलर की मनमर्जी के
प्रति खुला छोड़ दिया गया है। लेकिन वे एक नमूना हैं, सीमा नहीं;
वही बग बहुत संभवतः आपके कोड में भी हैं।
"कुछ आसान से शुरू करें।"
यह फ़ंक्शन *p को कितनी बार लोड करता है?```c
unsigned int g(unsigned short *p)
{
short t = p; / copy *p into a local for safekeeping */
return (unsigned short)t - t;
}
संकेत: उत्तर 1 है — स्रोत `*p` को केवल एक बार `t` में लोड करता है।
इसे [Compiler Explorer](https://godbolt.org/z/c5K9P4dPd) में पेस्ट करें (`arm gcc 14.2.0`, `-O2`) और `r0` से होने वाले लोड्स को गिनें, जो `p` रखता है:```asm
g:
ldrh r2, [r0] # load *p, once
ldrsh r0, [r0] # load *p, twice
subs r0, r2, r0
bx lr
स्रोत में एक लोड, बाइनरी में दो। दूसरा एक आविष्कृत लोड है — एक ऐसा रीड जिसे कंपाइलर ने बनाया और आपने कभी नहीं लिखा। यह C के abstract machine के अंतर्गत कानूनी है, जो मानता है कि दो रीड्स के बीच मेमोरी नहीं बदल सकती। लेकिन जब वह मेमोरी attacker-writable है, तो यह धारणा एक exploit बन जाती है: यह आविष्कृत लोड एक सुरक्षा जाँच के बाद आ सकता है, चुपचाप एक time-of-check to time-of-use (TOCTOU) विंडो को फिर से खोलता है जिसे प्रोग्रामर ने बंद कर दिया था। जिस मान को आपने सत्यापित किया और जिस मान का आप उपयोग करते हैं, अब समान होने की गारंटी नहीं है — भले ही आपने वह कोड कभी नहीं लिखा जो उसे दोबारा पढ़ता।
यह चुनौती साबित करती है कि आविष्कृत लोड मौजूद है; देखते हैं कि वह कैसे मेमोरी भ्रष्टाचार में बदल जाता है।
TOCTOU भेद्यता में, एक प्रोग्राम जाँचता है कि एक मान सुरक्षित है, फिर उस मान का उपयोग करता है। हालाँकि, exploitation की एक खिड़की मौजूद होती है यदि कोई हमलावर उन दो रीड्स के बीच के क्षण में मान को बदल सकता है – हानिरहित मान जाँच पास कर लेता है जबकि खतरनाक मान वही होता है जिसका उपयोग किया जाता है:```c if (shared->len <= 20) // CHECK reads shared->len // ** attacker modifies shared->len ** memcpy(out, shared->data, shared->len); // USE reads it again: buffer overflow
पाठ्यपुस्तकीय समाधान है **पहले स्नैपशॉट लेना**: हमलावर जिस भी डेटा के साथ छेड़छाड़ कर सकता है, उसे ऐसे स्थानीय (local) में कॉपी करें जहाँ हमलावर नहीं पहुँच सकता, और फिर उस स्थानीय के अलावा किसी और चीज़ पर भरोसा न करें। एक बार `len` स्थानीय में आ जाए, यह स्थिर हो जाता है — साझा मेमोरी के साथ प्रतिस्पर्धा करने वाला हमलावर अब उसे छू नहीं सकता — इसलिए जाँच और प्रतिलिपि को एक ही मान देखने की गारंटी मिलती है। इसी प्रकार नीचे `receive` में दिया गया कोड TOCTOU को ठीक करता है: यह संदेश का स्नैपशॉट लेता है, स्नैपशॉट को मान्य करता है, और मान्य की गई प्रति को `slot` में प्रकाशित करता है ताकि एक उपभोक्ता उसे आगे भेज सके:```c
#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? */
}
स्रोत के अनुसार, यह सही है। len को ठीक एक बार पढ़ा जाता है — स्नैपशॉट में —
इसलिए जो मान <= 20 जाँच को पास कराता है वही मान slot में प्रकाशित होता है।
TOCTOU विंडो बंद हो जाती है और कोड सुरक्षित है।
सिवाय इसके कि यह सुरक्षित नहीं है। x86-64 gcc -O2 के अंतर्गत, receive इसे
मूल साझा मेमोरी से दो बार पढ़ता है: एक बार
चेक को गेट करने के लिए एक स्केलर के रूप में, और फिर बल्क
कॉपी के हिस्से के रूप में जो slot में प्रकाशित होती है:```nasm
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`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/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.
## Cause
> *By the time C reaches machine code, it's been reshaped by frontend lowering,
> IR optimizations, register allocation, and backend codegen — a deep, multi-stage
> pipeline making decisions you can't see. There is no one stage to blame. The
> invented load is an emergent property of the whole pipeline, not a bug in any
> part of it.*
At this point: compilers *can* emit invented loads, and the very idiom meant to
prevent the bug — snapshot, validate, use — is what reintroduces it. The next
step (to know whether we are actually vulnerable) is to characterize *when* it
happens. Turns out that's hard.
In [`cat-states/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/), we search for the proofs-of-concept that show
it is real — and that it is everywhere:
| Mechanism | Toolchains | Targets |
|---|---|---|
| [**Rematerialization**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/README.md#rematerialization-class-1) | GCC, Clang, ICX, ICC, MSVC | x86-64, i386, m68k, VAX, MSP430 |
| [**Width-mismatch reload**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/README.md#width-mismatch-reload-class-2) | GCC | ARM, MIPS, MIPS64, RV64, s390x |
| [**Bulk-vs-scalar overlap**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/README.md#bulk-vs-scalar-overlap-class-3) | GCC, Clang, ICX, MSVC | x86-64, ARM, AArch64, AVR, Xtensa, SPARC, PPC64, s390x, MIPS64, RV64, m68k, MSP430, VAX, HPPA |
| [**Cross-class reload**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/README.md#cross-class-reload-class-4) | GCC | x86-64, s390x |
| [**CISC mem-op fold**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/README.md#cisc-alu-mem-op-fold-class-7) | GCC, Clang | m68k, MSP430, s390x, VAX, 6502 |
| [**Byte-order reload**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/README.md#byte-order-divergent-reload-class-8) | GCC | s390x |
Each PoC above pins down a single point where the load *can* appear;
[`alpha-lab/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/) charts the space around it to find where the edges
fall — a three-stage pipeline driven from a single `.c` file.
The [matrix runner](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/matrix_runner.py) sweeps the compiler ×
architecture × flag matrix on [Compiler Explorer](https://godbolt.org);
the [load detector](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/detect.py) runs each resulting binary under
[Unicorn](https://www.unicorn-engine.org/) and catches any byte read twice; and
the [flag minimizer](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/flag_search.py) delta-debugs each hit down to the
minimal flag set that flips a secure build into a double-read TOCTOU.
**The result**: no single compiler, flag, or pass is to blame — the double-read
emerges from the complex interaction of many compiler layers, each making
locally valid decisions. The effect is non-linear: small changes in source,
flags, or target can [cascade into different outcomes](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-imagemagick-7.1.2-25.md#candidate-1--readsunimage-sun_infolength-alloc-vs-copy). The only reliable way to
know whether a given line is vulnerable is to [**compile it and look.**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/README.md#same-source-different-outcome)
**The cat is alive — and it's not.** Until you build, a call site that
snapshots, validates, and uses a local copy is *neither* safe nor vulnerable —
it is both, and the compiler, its version, the target, and the flags decide
which. The build is the measurement, and it collapses the superposition one way
or the other. This is a **Schrödinger TOCTOU**: a check on a value the
programmer believed frozen, that the C standard quietly permits the compiler to
re-read from attacker-controlled memory. The box stays closed until someone,
somewhere, picks a toolchain and opens it.
## Effect
> *The pattern appears nearly everywhere — woven into the most carefully
> reviewed code in the world through simple idiomatic C.*
The problem is **virtually intractable**. The same snippet of code can be
vulnerable or not vulnerable depending on the precise combination of compiler ×
version × architecture × flags — and there are more such combinations than there
are atoms in the observable universe. Bounding it for even a single codebase is
a near-hopeless search; doing it across the ecosystem is far worse.
Even deciding whether a *single* call site is safe resists inspection: a
possible barrier like the kernel's `copy_from_user` only
[forecloses the bug](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/README.md#analysis)
after ~six layers of inlining, macros, and `CONFIG`/CPU-feature forks bottom out
in an opaque `asm` — and the *same* source line is no barrier at all in other
configurations. Reading the call *site* tells us nothing.
The only path forward is automation. A heuristic-based analysis was run across
prominent open-source targets — hypervisors, TEE/enclave runtimes, firmware,
kernel subsystems, protocol libraries — and found **300+ Schrödinger TOCTOUs**
across **100+ security-critical projects**: sites where the C standard *permits*
the compiler to re-read attacker-writable memory between a check and its use.
The automated analysis identifies the trust boundaries, searches for the
Schrödinger pattern, and assesses likelihood/impact/risk.
The results show that seemingly innocuous compiler-invented loads easily cascade
into devastating consequences.
The compiler doesn't invent a *load* so much as the *capability* that load hands
an attacker:
---
- **compiler-invented VM escape** — [QEMU](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-qemu-v11.0.1.md#candidate-1--ahci-prdtl-highest-impact), [Xen](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-xen-ptwalk-RELEASE-4.21.1.md#candidate-1--guest_walk_tables-pte-walk), [bhyve](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-bhyve-release-15.0.0.md#candidate-1--ahci-prdt-byte-count-write-path-oob-write), [KVM](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-kvm-host.md#candidate-1--svm-nested-vmcb12-save-area-cache-flagship), [ACRN](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-acrn-v3.3.md#candidate-1--nested-ept-shadow-walk)
- **compiler-invented root** — [siw](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-rdma-rxe-siw.md#candidate-1--siw-siw_rqe_get-num_sge-headline), [VMBus](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-hyperv-vmbus.md#candidate-2--msgtype-dispatch-index), [systemd](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-systemd-v260.md#candidate-1--sd_journal_enumerate_fields-sz-field-payload-size-alloc-vs-copy), [af-packet](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-af-packet.md#candidate-1--tp_len-tx-packet-length), [snd-pcm](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-snd-pcm-v7.0.md#candidate-1--snd_pcm_indirect_playback_transfer-appl_ptr-snapshot-used-for-diff-and-stored-baseline), [seL4](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-sel4-15.0.0.md#candidate-1-flagship--untyped-retype-object-window)
- **compiler-invented platform persistence** — [edk2](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-edk2-edk2-stable202605.md#candidate-1--smmlockboxrestore), [coreboot](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-coreboot-26.03.md#candidate-1--smmstore_rawread_region-bufsize--com-buffer-mapping-overflow), [U-Boot](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-u-boot-v2026.04.md#candidate-1--virtqueue_get_buf-used-ring-id-primary), [OpenSBI](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-opensbi-v1.8.1.md#candidate-1--dbtr-update-trigger-index-primary)
- **compiler-invented enclave breach** — [SGX](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-intel-sgx-sdk-sgx_2.29.md#candidate-1--generated-ecall-ininout-copy-in-headline-structural), [Keystone](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-keystone-master-88c49ee.md#candidate-1--edge_call_get_ptr_from_offset--edge_call_ret_ptr-host-written-return-offsetsize), [OpenEnclave](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-openenclave-v0.19.15.md#candidate-1--sgx-ecall-context-ocall-buffer), [OP-TEE](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-optee-os-4.10.0.md#candidate-1--register_shm-raw-tmem-reads)
---
Each of these can be catastrophic on its own, but the breadth is what unsettles:
the same shape turns up everywhere the analysis looks, in code that shares
nothing but the idiom:
| Target | Site | Impact |
|---|---|---|
| **QEMU** | [`ahci_populate_sglist`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-qemu-v11.0.1.md#candidate-1--ahci-prdtl-highest-impact) | guest AHCI PRDT length latched once → **OOB read / attacker-directed host DMA** |
| **Linux / RDMA** | [`siw_rqe_get`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-rdma-rxe-siw.md#candidate-1--siw-siw_rqe_get-num_sge-headline) | software-RDMA `num_sge` reused → **kernel OOB write** |
| **edk2 / UEFI** | [`SmmLockBoxRestore`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-edk2-edk2-stable202605.md#candidate-1--smmlockboxrestore) | SMM buffer length reused → **OOB write into SMRAM** (ring -2) |
| **TPM 2.0** | [`CryptParameterDecryption`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-ms-tpm-20-ref-v1.83r1.md#candidate-1--cryptparameterdecryption-in-place-decrypt-length) | in-place decrypt length reused → **OOB write in the TPM root-of-trust** |
| **seL4** | [`decodeUntypedInvocation`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-sel4-15.0.0.md#candidate-1-flagship--untyped-retype-object-window) | retype object-window reused → **kernel compromise** |
| **Xen** | [`guest_walk_tables`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-xen-ptwalk-RELEASE-4.21.1.md#86-per-candidate-finding) | guest PTE reused on the walk → **privilege escalation** |
| **SGX** | [edger8r ECALL bridge](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-intel-sgx-sdk-sgx_2.29.md#candidate-1--generated-ecall-ininout-copy-in-headline-structural) | `[in]`/`[in,out]` length reused for `malloc`/`memcpy_s` → **enclave heap overflow** (every ECALL) |
| **ARM TF-A** | [`spmc_ffa_fill_desc`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-tf-a-v2.15.0.md#candidate-1--spmc-ffa_mem_sharelend-send-path-primary-could--yes) | FF-A descriptor field reused to size `memcpy` → **heap overflow in the EL3 secure monitor** |
| **Linux / Hyper-V** | [Hyper-V VMBus `__vmbus_on_msg_dpc`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-hyperv-vmbus.md#candidate-2--msgtype-dispatch-index) | host `msgtype` reused to index handler table → **wild indirect call** in the guest kernel |
| **U-Boot** | [`virtqueue_get_buf`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-u-boot-v2026.04.md#candidate-1--virtqueue_get_buf-used-ring-id-primary) | virtio used-ring `id` reused as array index → **heap OOB read/write** in the bootloader |
| **glibc** | [`_dl_check_map_versions`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-glibc-glibc-2.42.md#candidate-1--_dl_check_map_versions-verneed-version-index-write) | dynamic-loader VERNEED version index reused as a write subscript → **OOB write in `ld.so`** when mapping a crafted shared library |
| **systemd** | [`sd_journal_enumerate_fields`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-systemd-v260.md#candidate-1--sd_journal_enumerate_fields-sz-field-payload-size-alloc-vs-copy) | journal field size reused across alloc/copy → **heap OOB write** in `journalctl`/`coredumpctl` (frequently root) |
| **git** | [`read_table_of_contents`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-git-v2.54.0.md#candidate-1--read_table_of_contents-chunk-offset-to-start-pointer) | object-store chunk offset reused as a chunk base/size → **OOB read** parsing a crafted `.idx` / multi-pack-index / commit-graph (shared repo / forge backend) |
| **SQLite** | [`btreeComputeFreeSpace`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-sqlite-version-3.53.2.md#candidate-1--btreecomputefreespace-freeblock-offset-pc-→-data-index) | B-tree freeblock offset reused as a page index → **OOB read of an `mmap`'d database page** |
| **FreeType** | [`ft_var_readpackedpoints`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-freetype-VER-2-14-3.md#candidate-1--ft_var_readpackedpoints-gvar-packed-point-count-n) | variable-font packed point-count reused → **heap OOB write** rendering a crafted font (ubiquitous: Android / Chrome / desktop) |
| **libtiff** | [`NeXTDecode`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-libtiff-v4.7.1.md#candidate-1--nextdecode-literalspan-off--n-controlled-oob-write) | NeXT-RLE span offset/length reused → **heap OOB write** decoding a crafted TIFF (default `mmap`'d read mode) |
| **binutils / ld** | [`sframe_decode`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-binutils-binutils-2_46_1.md#candidate-1--sframe_decode-sfh_num_fdes-fde-table-alloc-vs-fill) | SFrame FDE count reused as alloc size **and** fill bound → **heap OOB write in the linker** on a crafted object |
| **ClamAV** | [`autoit` EA05 `csize`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-clamav-clamav-1.5.2.md#candidate-1--autoit-ea05-csize-alloc-vs-fill-heap-oob-write) | AutoIt `csize` reused as alloc size **and** copy length → **heap OOB write** in the scanner |
| **YARA** | [`pe_parse_exports`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-yara-v4.5.7.md#candidate-1--pe_parse_exports-number_of_exports-loop-bound) | PE export count reused as a loop bound → **OOB read** scanning a crafted sample |
| **WAMR** | [`_vprintf_wa`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-wamr-WAMR-2.4.4.md#candidate-1--_vprintf_wa-s-handler-s_offset-string-address-rematerialization) | guest `%s` offset re-read past the sandbox arena → **OOB read leaking host memory to the wasm guest** |
| **ImageMagick** | [`ReadSUNImage`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-imagemagick-7.1.2-25.md#candidate-1--readsunimage-sun_infolength-alloc-vs-copy) | SUN-raster length reused as alloc size **and** copy length → **heap OOB write → RCE** decoding a crafted image (LTO builds) |
| **FreeBSD** | [`virtqueue_dequeue`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-freebsd-drivers-release-15.0.0.md#candidate-1--virtqueue_dequeue-used-ring-desc_idx) | host-written virtio used-ring `id` reused as an unbounded array index → **descriptor double-free / UAF** in the kernel |
Everything is vulnerable. And everything is not. In every situation, the source
does the right thing: snapshot the untrusted input, validate the copy, use the
copy. But in each, the C standard quietly permits the compiler to optionally
*undo* that process, and create a TOCTOU out of thin air. Whether a
given site is exploitable is *not a property of the source*: it is decided
by the compiler, its version, the architecture, the flags, and it collapses one
way only when you build. Until then each one is both — a vulnerability held in
superposition, indistinguishable at the source level from code that is genuinely
fine. Each is a Schrödinger TOCTOU — and the table above is what they look like
at scale.
The unsettling part is not that these particular projects are flawed — it is
that the pattern turns up nearly everywhere the analysis looks, woven into [the
most carefully reviewed code in the world](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-sel4-15.0.0.md#82-executive-summary)
through nothing more than idiomatic C. The 100+ repositories are a
**sample, not the boundary**: the same latent bug almost certainly reaches your
own codebase.
The full audit and impact analysis is in [observer-effect/](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/)
and its [REPORT.md](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/REPORT.md).
## Solutions
> *There are none.*
But here are some things we can try anyway.
The reflex solution is to try to pin the load — [`volatile`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), [`READ_ONCE`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), an
[atomic](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-2--atomic--acquire-load), a [`"memory"`-clobber `barrier()`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). Those are spec-sound and survive
`-O3`, LTO, and inlining; where a bare read is *[found](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-kvm-host-sev-snp.md#candidate-1--snp_begin_psc-idx_end-loop-bound)*, they are [the correct
patch](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/README.md#confirmed-in-the-wild). Unfortunately, they patch the wound, but not the cause:
- **`volatile` launders away silently.** It [qualifies *the lvalue access*](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/README.md#the-volatile-cat-state), not
the object, the pointer, or the region. A `volatile T *p` read through a plain
lvalue [gives zero protection](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/volatile_lvalue_launder.c), and the
qualifier is dropped with **no diagnostic** when it [passes through `memcpy`'s
`const void *`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/volatile_memcpy_overlap.c) — there is [no volatile-preserving `memcpy`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#false-friends--look-like-barriers-but-are-not). The barrier you
wrote evaporates at the call you didn't.
- **`READ_ONCE` doesn't scale.** "Use `READ_ONCE`" really means: annotate
*every* attacker-reachable access of [*every* field](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-rdma-rxe-siw.md#candidate-1--siw-siw_rqe_get-num_sge-headline), forever, and [position a
fence between the read and *all* of its uses](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). [Miss one](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-v7.0-io_uring.md#candidate-1--nvme_uring_cmd_io-nsid) and the discipline is
void. It [cannot be enforced at scale](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), and it regresses silently.
- **A `barrier()`'s correctness lives frames from the source line.** Deciding
whether one `copy_from_user(&local, uptr, n)` even carries a `"memory"` clobber
means [tracing five inlined layers and an out-of-line call](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/README.md#analysis) from generic C
into arch-specific asm, resolving a fistful of `CONFIG`/CPU-feature/`__builtin`
forks. And even once found, the clobber [names no read](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier): a step out of place it [pins
nothing](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier); a step the other way it [*forces* the very reload it should
stop](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#principles--shared-facts-the-cards-lean-on).
But more importantly: the source never asks for a reload to begin with. This is
the deeper issue. The programmer wrote `local.len` and *meant* `local.len`: one
value, read once. If we say `x` we mean `x`, not "`x`, but `y` if the compiler
likes that instead." The reload is invented beneath the abstract machine, so the
code that needs the annotation [looks identical to the code that
doesn't](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/README.md#same-source-different-outcome) — there is [no
signal at the site that a barrier is
required](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#proposed-levers-do-not-exist-in-usable-form-today).
You cannot remember to guard a read you never wrote.
The full catalog of defenses — with their [strengths](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-binder-v7.0.md#executive-summary) and [failures](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-libspdm-3.8.2.md#durability-assessment) — is in the
[barriers report](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md).
## Open the box
> *The TOCTOU-from-thin-air pattern is everywhere. Check if your code has it.*
Check your own code with
[`observer-effect/AUDIT-PROMPT.md`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/AUDIT-PROMPT.md), which will
look for the trust boundaries, search for the Schrödinger pattern, prune based on
spec-compliant barriers, and assess likelihood/impact/risk. Hand it to your
preferred coding agent with your source in context and point it at a subsystem:```sh
cd ~/your-project # the codebase you want audited
claude -p "$(cat path/to/observer-effect/AUDIT-PROMPT.md)
Audit drivers/net/ for invented-load TOCTOUs."
यह इस रिपो की किसी और चीज़ पर निर्भर नहीं करता — एक फ़ाइल कॉपी करें और चलें।
Schrödinger's TOCTOU किसी यादृच्छिक अनुकूलन के एक विशिष्ट उदाहरण का विच्छेदन करता है,
जो 500-पृष्ठ C-विनिर्देश द्वारा अनुमत है। पर यह केवल सतह को
खरोंचना है: अन्वेषण के लिए बहुत कुछ शेष है। यह रिपॉज़िटरी
आपके पसंदीदा कंपाइलर द्वारा आपको चुपचाप, कानूनी रूप से, और हर अनुकूलन स्तर पर
कमज़ोर करने के अप्रत्याशित तरीकों को खोजती, कैप्चर करती और सूचीबद्ध करती रहेगी।
"... अगर gcc ने ऐसा किया, तो कर्नेल का बड़ा हिस्सा आग में जलकर खाक हो जाएगा।"
— Paul E. McKenney, LKML, 2009-04-16 · lore
"लोग 'सुरक्षित C' की बात करना पसंद करते हैं, लेकिन कंपाइलर लोगों ने दशकों से C को और असुरक्षित बनाने की सक्रिय कोशिश की है। C मानक समिति भी इसमें शामिल रही है।"
— Linus Torvalds, 2025-02-21 · lore
"मैं एक कंपाइलर स्विच बहुत पसंद करूँगा जो कंपाइलर को निर्देश दे कि वह कर्नेल में हर दूसरे load/store को volatile से चिह्नित करने के बजाय इस तरह की बेहद बेवकूफी भरी चीज़ें न करे, जैसे यह।"
— Peter Zijlstra, 2015-06-17 · lore
"स्पेक तो बस टॉयलेट पेपर है। केवल एक चीज़ मायने रखती है—क्या वास्तविक हार्डवेयर करता है।"
— Linus Torvalds, 2006-12-04 · lore
"उस तर्क के अनुसार हमें कर्नेल के आधे हिस्से पर _ONCE() लगाना होगा … क्या हम आखिरकार पैर जमाकर कंपाइलर और मानक समिति वालों से कह सकते हैं कि यह पागलपन बंद करें?
— Thomas Gleixner, 2019-08-16 · lore
"जो कंपाइलर स्रोत कोड द्वारा छुए न जाने वाले फ़ील्ड्स को छूने के लिए 'अनुकूलन' करते हैं, वे कोड सीधे तौर पर स्वाभाविक रूप से बगी बकवास हैं। मुझे उनके पागलपन को खुश करने में ज़रा भी दिलचस्पी नहीं है... यह दावा करना कि उन्हें volatile से चिह्नित करने की आवश्यकता है, एक बीमार कंपाइलर लेखक का लक्षण है।"
— Linus Torvalds, 2014-12-04 · lore
"पागलपन? शायद हाँ। लेकिन कुछ कंपाइलर लोग हैं जो इसकी कसम खाते हैं।"
— Paul E. McKenney, LKML, 2008-02-04 · lore
"यह अच्छी बात है अगर उन्होंने सभी कोड-पथों का परीक्षण किया है, लेकिन उन्होंने हमेशा ऐसे कंपाइलर के साथ परीक्षण किया है जो "कानूनी लेकिन मूर्खतापूर्ण" कोड उत्पन्न करने की कोशिश में अपना रास्ता नहीं छोड़ता। तो परीक्षण आम तौर पर उन मामलों को नहीं खोजेगा जहाँ कंपाइलर को कुछ और करने की अनुमति रही हो। ... कंपाइलर लोग जो इसे नहीं समझते, वे कंपाइलर लोग नहीं हैं। वे शिक्षाविद हैं, जो मानसिक हस्तमैथुन में लगे हैं।"
— Linus Torvalds, LKML, 2007-01-04 · lore
"बेशक, मुझे बेवकूफ कंपाइलर चिंतित नहीं करते, बल्कि चालाक वाले..."
— Paul E. McKenney, LKML, 2013-10-09 · lore
"... हमारे पास ऐसे कंपाइलर लेखक रहे हैं जो कहते हैं "अगर आप स्पेक पढ़ लें, तो यह ठीक है"। नहीं, यह ठीक नहीं है। क्योंकि वास्तविकता किसी भी चालाकी भरी स्पेक-पठन पर भारी पड़ती है।"
— Linus Torvalds, LKML, 2019-08-16 · lore
"... 'समझदार कंपाइलर' की परिभाषा और ढीली होती जा रही है।"
— Paul E. McKenney, LKML, 2013-09-24 · lore
Schrödinger's TOCTOU क्रिस्टोफर डोमास (@xoreaxeaxeax) का एक शोध प्रयास है।
