Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
schrodingers-toctou — 安全なCコードをTOCTOU脆弱性に変える、コンパイラが生成したメモリロードを検出します。自動化されたソースコード監査、Unicornベースのバイナリ解析、100以上のプロジェクトにわたるコンパイラ/アーキテクチャ/フラグのスイープを含みます。 | Kitploit
ツール/GitHubGitHub/xoreaxeaxeax/schrodingers-toctou
動的分析 (サンドボックス)静的コード分析 (SAST)脆弱性分析エクスプロイトバイナリ解析学習と教育
GitHubxoreaxeaxeax/schrodingers-toctou

schrodingers-toctou

安全なCコードをTOCTOU脆弱性に変える、コンパイラが生成したメモリロードを検出します。自動化されたソースコード監査、Unicornベースのバイナリ解析、100以上のプロジェクトにわたるコンパイラ/アーキテクチャ/フラグのスイープを含みます。

リポジトリを見る
944181ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

シュレーディンガーのTOCTOU

「...『正気のコンパイラ』の定義はますます緩くなっていく」

実行するバイナリは、あなたが書いたプログラムではない。コンパイラの最適化は、あなたのソースを 決して目にすることのない形で書き換え — そして、そうした変更の一部は 黙って、しかも合法的に、一見安全に見えるコードを 脆弱なバイナリへと変えてしまうことがある。同じコード行が あるコンパイラでは安全で、別のコンパイラでは悪用可能 になり得る。ソースコードにはどちらになるかを示す手掛かりは何もない。脆弱性は 重ね合わせの状態にあり、ビルドしたときにのみ収束する。Schrödinger's TOCTOU は コンパイラが作り出すロード と、それらが time-of-check to time-of-use(TOCTOU)脆弱性に与える広範な影響を探求する — 見つかるのは オープンソースの カーネル、 ハイパーバイザ、 エンクレーブ、 ファームウェア、 そしてライブラリ。 どこを見ても、一見安全に見えるコードはコンパイラの気まぐれに さらされたままになっている。しかし、これらは標本であって境界ではない。 同じバグはあなたのコードにも存在する可能性が非常に高い。

チャレンジ

「簡単なものから始めよう。」

この関数は *p を何回ロードするだろうか?```c unsigned int g(unsigned short *p) { short t = p; / copy *p into a local for safekeeping */ return (unsigned short)t - t; }

root@kitploit:~
ヒント: 答えは 1 です — ソースは `*p` を `t` に一度だけロードします。

それを [Compiler Explorer](https://godbolt.org/z/c5K9P4dPd) (`arm gcc 14.2.0`,
`-O2`) に貼り付けて、`p` を保持する `r0` からのロードを数えてください:```asm
g:
        ldrh    r2, [r0]     # load *p, once
        ldrsh   r0, [r0]     # load *p, twice
        subs    r0, r2, r0
        bx      lr

ソースには1つのロード、バイナリには2つのロードがある。2つ目は作り出されたロード — コンパイラが生成した、あなたが決して書いていない読み出しだ。これはCの 抽象機械の下では合法であり、2つの読み出しの間にメモリが変化しないと仮定する。しかし、 そのメモリが攻撃者によって書き換え可能な場合、その仮定は悪用へと変わる。つまり、 作り出されたロードはセキュリティチェックの後に落ちる可能性があり、静かに プログラマーが閉じたと思っていたtime-of-check to time-of-use (TOCTOU) ウィンドウを 再び開く。検証した値と使用する値は、もはや 同じであることは保証されない — たとえ、それを再読み出しするコードを書いていなくても。

どこからともなく現れるバッファオーバーフロー

このチャレンジは、作り出されたロードが存在することを証明している。それがどのようにメモリ破壊に つながるのかを見てみよう。

TOCTOU脆弱性では、プログラムは値が安全であることをチェックしてから、その値を 使用する。 しかし、攻撃者がその2つの読み出しの間のわずかな時間に値を変更できるなら、 悪用の余地が存在する。 – 無害な値は チェックを通過する一方、危険な値が実際に使用されることになる:```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

root@kitploit:~
教科書的な修正方法は、**まずスナップショットを取る**ことです。攻撃者が改ざんし得るデータを、攻撃者の到達できないローカルにコピーし、そのローカル以外は一切信用しません。`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 は それを 元の共有メモリから2回読み取る: 1回は チェックをゲートするスカラーとして、そして再び 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]

root@kitploit:~
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/main/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/main/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/main/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/main/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/main/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/main/cat-states/README.md#cross-class-reload-class-4) | GCC | x86-64, s390x |
| [**CISC mem-op fold**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/observer-effect/audits/audit-qemu-v11.0.1.md#candidate-1--ahci-prdtl-highest-impact), [Xen](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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/main/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/main/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/main/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/main/observer-effect/audits/audit-linux-v7.0-hyperv-vmbus.md#candidate-2--msgtype-dispatch-index), [systemd](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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/main/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/main/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/main/observer-effect/audits/audit-edk2-edk2-stable202605.md#candidate-1--smmlockboxrestore), [coreboot](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/observer-effect)
and its [REPORT.md](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), [`READ_ONCE`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), an
[atomic](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-2--atomic--acquire-load), a [`"memory"`-clobber `barrier()`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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/main/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/main/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/main/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/main/cat-states/volatile_memcpy_overlap.c) — there is [no volatile-preserving `memcpy`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). [Miss one](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/observer-effect/audits/audit-linux-binder-v7.0.md#executive-summary) and [failures](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-libspdm-3.8.2.md#durability-assessment) — is in the
[barriers report](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/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/main/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."

このリポジトリの他の何にも依存していません — ファイルを1つコピーして、そのまま使えます。

将来

Schrödinger's TOCTOU は、500ページのC仕様書が許容する、あるランダムな最適化の特定の実例を分析します。しかし、これは表面をかじったに過ぎません。探求すべき領域はまだまだたくさんあります。このリポジトリは、あなたの愛用コンパイラがあなたを裏切る予想外の方法を探り、記録し、カタログ化し続けます — 静かに、合法的に、そしてあらゆる最適化レベルで。

"... gccがそんなことをしたら、カーネルの大部分は炎上するだろう。"

— Paul E. McKenney, LKML, 2009-04-16 · lore

"人々は'安全なC'について語るのが好きだが、コンパイラ関係者は何十年もの間、積極的にC をより危険にしようとしてきた。C標準化委員会もそれに加担してきた。"

— Linus Torvalds, 2025-02-21 · lore

"カーネル内のロード/ストアのひとつひとつに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

"... これまでに「仕様を読めば、それはOKだ」と言うコンパイラ開発者がいた。 いや、 OKではない。現実は、どんなずる賢い仕様の読み方にも勝るからだ。"

— Linus Torvalds, LKML, 2019-08-16 · lore

"...『正気なコンパイラ』の定義はますます曖昧になっていく。"

— Paul E. McKenney, LKML, 2013-09-24 · lore

参考文献

  • ホワイトペーパー: (近日公開)
  • スライド: (近日公開)
  • プレゼンテーション: (近日公開)

著者

Schrödinger's TOCTOU は Christopher Domas(@xoreaxeaxeax)による研究プロジェクトです。


実験


ツールをダウンロード