Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
schrodingers-toctou — 컴파일러가 생성한 메모리 로드를 탐지하여 안전한 C 코드가 TOCTOU 취약점으로 변질되는 것을 방지합니다. 자동화된 소스 감사, Unicorn 기반 바이너리 분석, 100개 이상의 프로젝트에 걸친 컴파일러/아키텍처/플래그 스윕을 포함합니다. | Kitploit
도구/GitHubGitHub/xoreaxeaxeax/schrodingers-toctou
Dynamic Analysis (Sandboxing)Static Code Analysis (SAST)Vulnerability AnalysisExploitationBinary AnalysisLearning & Education
GitHubxoreaxeaxeax/schrodingers-toctou

schrodingers-toctou

컴파일러가 생성한 메모리 로드를 탐지하여 안전한 C 코드가 TOCTOU 취약점으로 변질되는 것을 방지합니다. 자동화된 소스 감사, Unicorn 기반 바이너리 분석, 100개 이상의 프로젝트에 걸친 컴파일러/아키텍처/플래그 스윕을 포함합니다.

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
94416일 전아직 검토되지 않음

Schrödinger's TOCTOU

"...'정상적인 컴파일러'의 정의는 점점 더 느슨해진다."

여러분이 실행하는 바이너리는 여러분이 작성한 프로그램이 아니다. 컴파일러 최적화기는 소스를 여러분이 결코 보지 못할 방식으로 다시 작성하며 — 그러한 변경 중 일부는 조용하고도 합법적으로 겉보기에는 안전한 코드를 취약한 바이너리로 바꿀 수 있다. 같은 코드 줄은 한 컴파일러에서는 안전하고 다른 컴파일러에서는 공격에 취약할 수 있으며, 소스에는 어느 쪽인지 알려 줄 것이 없다: 그 취약성은 중첩 상태에 있다가 빌드할 때에만 붕괴된다. Schrödinger's TOCTOU는 **컴파일러가 만들어 낸 로드**와 그것이 오픈소스 커널, 하이퍼바이저, 엔클레이브, 펌웨어, 및 라이브러리에서 발견되는 검사 시점-사용 시점(TOCTOU) 취약성에 미치는 광범위한 영향을 탐구한다. 우리가 살펴본 모든 곳에서 겉보기에 안전한 코드는 컴파일러의 변덕에 노출되어 있다. 그러나 그것들은 표본일 뿐 경계가 아니다; 같은 버그는 여러분의 코드에도 있을 가능성이 매우 높다.

Challenge

"간단한 것부터 시작해 보자."

이 함수는 *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

소스에서 한 번의 로드, 바이너리에서는 두 번의 로드가 발생한다. 두 번째는 조작된 로드(invented load) 다 — 컴파일러가 만들어 낸, 당신이 결코 작성하지 않은 읽기다. 이는 C 추상 머신에서 합법적이며, 추상 머신은 두 번의 읽기 사이에 메모리가 변경될 수 없다고 가정한다. 하지만 그 메모리가 공격자가 쓰기 가능한 상태라면, 그 가정은 익스플로잇이 된다: 조작된 로드는 보안 검사 이후에 실행되어, 프로그래머가 닫았다고 믿었던 검사 시점-사용 시점(TOCTOU) 창을 조용히 다시 열어 버린다. 당신이 검증한 값과 당신이 사용하는 값은 더 이상 동일함이 보장되지 않는다 — 그 값을 다시 읽는 코드를 결코 작성하지 않았는데도 말이다.

공중에서 만들어진 버퍼 오버플로

이 도전 과제는 조작된 로드가 실제로 존재함을 증명한다. 그것이 어떻게 메모리 손상으로 이어지는지 살펴보자.

TOCTOU 취약점에서는 프로그램이 값이 안전한지 확인한 다음 그 값을 사용한다. 그러나 공격자가 그 두 읽기 사이의 아주 짧은 시간에 값을 변경할 수 있다면 악용의 창이 존재한다 — 무해한 값은 검사를 통과하지만 실제로 사용되는 것은 위험한 값이다:```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:~
교과서적인 해결책은 **스냅샷을 먼저 찍는 것**이다. 공격자가 건드릴 수 있는 데이터는 공격자가 닿을 수 없는 로컬(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]

root@kitploit:~
검사는 READ #1에서 실행되고, `slot.len`에 들어가는 값은 READ #2입니다. 그 사이에 `len`을 뒤집는 공격자는 `<= 20` 검사에 안전한 값을 통과시키면서도 과대한 값을 `slot`에 게시합니다 — 그러면 `forward`는 그 많은 바이트를 `out[20]`으로 복사하는데, 이는 스냅샷이 막으려던 바로 그 오버플로가 옵티마이저에 의해 재도입된 것입니다.

이것은 [`poc/example.c`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/poc/example.c)에서 완전한 개념 증명으로 구체화됩니다. 이 코드는 정식 TOCTOU 방어 접근 방식을 사용합니다: 신뢰할 수 없는 `message` 구조체는 수정할 수 없도록 `local`로 스냅샷되고, 스냅샷의 `local.len`은 버퍼 용량에 대해 검증되며, 검증된 복사본만 `slot`에 게시됩니다. 이후 소비자가 `slot.len` 페이로드 바이트를 고정 버퍼로 복사합니다. 동시에 공격자는 `shared->len`을 레이스합니다. 컴파일러의 예기치 않은 발명된 로드가 대량 게시를 위해 `shared->len`을 다시 읽으므로, 검사가 통과했음에도 `slot.len`은 공격자의 과대한 값을 담게 됩니다 — 프로그래머가 막으려던 TOCTOU를 재도입하면서, 공중에서 튀어나온 것 같은 불가능해 보이는 버퍼 오버플로를 만들어냅니다.

## Cause

> *C가 기계 코드에 도달할 때쯤, 그것은 프론트엔드 로어링, IR 최적화, 레지스터 할당, 백엔드 코드 생성에 의해 다시 형성됩니다 — 보이지 않는 결정을 내리는 깊고 다단계 파이프라인입니다. 탓할 단 한 단계는 없습니다. 발명된 로드는 전체 파이프라인의 창발적 속성이지, 어떤 부분의 버그가 아닙니다.*

이 시점에서: 컴파일러는 *실제로* 발명된 로드를 생성할 수 있으며, 버그를 막기 위한 바로 그 관용구 — 스냅샷, 검증, 사용 — 가 그 버그를 재도입합니다. (실제로 취약한지 알기 위한) 다음 단계는 *언제* 발생하는지를 규명하는 것입니다. 알고 보니 그건 어렵습니다.

[`cat-states/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/)에서는 그것이 실재하며 어디에나 존재함을 보여주는 개념 증명을 찾습니다:

| 메커니즘 | 툴체인 | 대상 |
|---|---|---|
| [**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 |

위의 각 PoC는 로드가 *나타날 수 있는* 단일 지점을 고정합니다; [`alpha-lab/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/)는 그 주변 공간을 도표화하여 경계가 어디에 떨어지는지 찾습니다 — 단일 `.c` 파일에서 구동되는 3단계 파이프라인입니다. [매트릭스 러너](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/matrix_runner.py)는 [Compiler Explorer](https://godbolt.org)에서 컴파일러 × 아키텍처 × 플래그 매트릭스를 훑고, [로드 탐지기](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/detect.py)는 생성된 각 바이너리를 [Unicorn](https://www.unicorn-engine.org/)에서 실행하여 두 번 읽히는 바이트를 잡아냅니다. 그리고 [플래그 최소화기](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/flag_search.py)는 각 적중을 델타 디버깅하여 안전한 빌드를 이중 읽기 TOCTOU로 뒤집는 최소 플래그 집합을 찾아냅니다.

**결과**: 단일 컴파일러, 플래그 또는 패스에 탓을 돌릴 수 없습니다 — 이중 읽기는 각각 지역적으로 타당한 결정을 내리는 많은 컴파일러 계층의 복잡한 상호작용에서 발생합니다. 그 효과는 비선형적입니다: 소스, 플래그 또는 대상의 작은 변화가 [다른 결과로 이어질 수 있습니다](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). 특정 줄이 취약한지 여부를 아는 유일한 신뢰할 수 있는 방법은 [**컴파일해서 직접 보는 것**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/README.md#same-source-different-outcome)입니다.

**고양이는 살아 있고 — 동시에 죽어 있습니다.** 빌드하기 전까지, 로컬 복사본을 스냅샷하고 검증하고 사용하는 호출 지점은 *안전하지도 않고* 취약하지도 않습니다 — 둘 다이며, 컴파일러, 그 버전, 대상 및 플래그가 어느 쪽일지 결정합니다. 빌드가 측정이며, 중첩을 어느 한쪽으로 붕괴시킵니다. 이것은 **슈뢰딩거 TOCTOU**입니다: 프로그래머가 고정되었다고 믿은 값에 대한 검사로서, C 표준은 컴파일러가 공격자가 제어하는 메모리에서 다시 읽는 것을 조용히 허용합니다. 누군가, 어딘가에서 툴체인을 선택해 상자를 열기 전까지 상자는 닫혀 있습니다.

## Effect

> *이 패턴은 거의 모든 곳에 나타납니다 — 단순한 관용적 C를 통해 세계에서 가장 주의 깊게 검토된 코드에까지 짜여 들어가 있습니다.*

이 문제는 **사실상 다루기 불가능합니다**. 동일한 코드 조각도 컴파일러 × 버전 × 아키텍처 × 플래그의 정확한 조합에 따라 취약할 수도 있고 그렇지 않을 수도 있습니다 — 그리고 그러한 조합의 수는 관측 가능한 우주의 원자 수보다 많습니다. 단일 코드베이스에 대해서만 범위를 한정하는 것도 거의 희망 없는 탐색이며, 생태계 전체에 걸쳐 수행하는 것은 훨씬 더 심각합니다.

*단일* 호출 지점이 안전한지조차 결정하는 것은 검토로는 불가능합니다: 커널의 `copy_from_user` 같은 잠재적 장벽도 인라인, 매크로, `CONFIG`/CPU 기능 분기의 약 6개 계층이 불투명한 `asm`에서 바닥을 친 후에야 [버그를 차단](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/README.md#analysis)합니다. 그리고 *동일한* 소스 줄은 다른 구성에서는 전혀 장벽이 아닙니다. 호출 *지점*을 읽는 것만으로는 아무것도 알 수 없습니다.

앞으로 나아갈 유일한 길은 자동화입니다. 저명한 오픈소스 대상 — 하이퍼바이저, TEE/엔클레이브 런타임, 펌웨어, 커널 서브시스템, 프로토콜 라이브러리 — 전반에 휴리스틱 기반 분석을 실행한 결과, **100개 이상의 보안 중요 프로젝트**에서 **300개 이상의 슈뢰딩거 TOCTOU**를 발견했습니다: 검사와 사용 사이에 컴파일러가 공격자가 쓰기 가능한 메모리를 다시 읽는 것을 C 표준이 *허용하는* 지점들입니다. 자동화된 분석은 신뢰 경계를 식별하고, 슈뢰딩거 패턴을 검색하며, 가능성/영향/위험을 평가합니다.

결과는 겉보기에 무해한 컴파일러 발명 로드가 쉽게 파괴적인 결과로 이어짐을 보여줍니다.

컴파일러가 *로드*를 발명하기보다는, 그 로드가 공격자에게 넘겨주는 *능력*을 발명합니다:

---

- **컴파일러가 만들어낸 VM 탈출** — [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)
- **컴파일러가 만들어낸 루트 권한** — [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)
- **컴파일러가 만들어낸 플랫폼 영속성** — [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)
- **컴파일러가 만들어낸 엔클레이브 침해** — [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)

---

이들 각각은 그 자체로 치명적일 수 있지만, 불안하게 만드는 것은 그 광범위함입니다: 분석이 들여다보는 모든 곳에서, 오직 그 관용구만 공유하는 코드에서 동일한 형태가 나타납니다:

| 대상 | 위치 | 영향 |
|---|---|---|
| **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) | 게스트 AHCI PRDT 길이가 한 번 래치됨 → **OOB 읽기 / 공격자가 지시하는 호스트 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) | 소프트웨어 RDMA `num_sge` 재사용 → **커널 OOB 쓰기** |
| **edk2 / UEFI** | [`SmmLockBoxRestore`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-edk2-edk2-stable202605.md#candidate-1--smmlockboxrestore) | SMM 버퍼 길이 재사용 → **SMRAM에 대한 OOB 쓰기** (링 -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) | 제자리 복호화 길이 재사용 → **TPM 루트 오브 트러스트에서의 OOB 쓰기** |
| **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) | 리타입 객체-윈도 재사용 → **커널 장악** |
| **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) | 워크 중 게스트 PTE 재사용 → **권한 상승** |
| **SGX** | [edger8r ECALL 브리지](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]` 길이가 `malloc`/`memcpy_s`에 재사용됨 → **엔클레이브 힙 오버플로** (모든 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 디스크립터 필드가 `memcpy` 크기 결정에 재사용됨 → **EL3 시큐어 모니터에서 힙 오버플로** |
| **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) | 호스트 `msgtype`이 핸들러 테이블 인덱스로 재사용됨 → 게스트 커널에서 **wild 간접 호출** |
| **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`가 배열 인덱스로 재사용됨 → 부트로더에서 **힙 OOB 읽기/쓰기** |
| **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) | 동적 로더 VERNEED 버전 인덱스가 쓰기 첨자로 재사용됨 → 조작된 공유 라이브러리를 매핑할 때 `ld.so`에서 **OOB 쓰기** |
| **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) | 저널 필드 크기가 할당/복사에 걸쳐 재사용됨 → `journalctl`/`coredumpctl`에서 **힙 OOB 쓰기** (종종 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) | 객체 저장소 청크 오프셋이 청크 베이스/크기로 재사용됨 → 조작된 `.idx` / multi-pack-index / commit-graph를 파싱할 때 **OOB 읽기** (공유 저장소 / 포지 백엔드) |
| **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-트리 freeblock 오프셋이 페이지 인덱스로 재사용됨 → `mmap`된 데이터베이스 페이지의 **OOB 읽기** |
| **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) | 가변 폰트 패킹 포인트 수 재사용 → 조작된 폰트를 렌더링할 때 **힙 OOB 쓰기** (유비쿼터스: Android / Chrome / 데스크톱) |
| **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 스팬 오프셋/길이 재사용 → 조작된 TIFF를 디코딩할 때 **힙 OOB 쓰기** (기본 `mmap` 읽기 모드) |
| **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 개수가 할당 크기 **및** 채움 경계로 재사용됨 → 조작된 객체에서 **링커 내 힙 OOB 쓰기** |
| **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`가 할당 크기 **및** 복사 길이로 재사용됨 → 스캐너에서 **힙 OOB 쓰기** |
| **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 개수가 루프 경계로 재사용됨 → 조작된 샘플을 스캔할 때 **OOB 읽기** |
| **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) | 게스트 `%s` 오프셋이 샌드박스 아레나를 넘어 다시 읽힘 → **호스트 메모리를 wasm 게스트에 유출하는 OOB 읽기** |
| **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 길이가 할당 크기 **및** 복사 길이로 재사용됨 → 조작된 이미지를 디코딩할 때 **힙 OOB 쓰기 → RCE** (LTO 빌드) |
| **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) | 호스트가 쓴 virtio used-ring `id`가 무제한 배열 인덱스로 재사용됨 → 커널에서 **디스크립터 이중 해제 / UAF** |

모든 것이 취약합니다. 그리고 모든 것이 취약하지도 않습니다. 모든 상황에서 소스는 올바른 일을 합니다: 신뢰할 수 없는 입력을 스냅샷하고, 복사본을 검증하고, 복사본을 사용합니다. 그러나 각각의 경우 C 표준은 컴파일러가 그 과정을 선택적으로 *되돌리는* 것을 조용히 허용하며, 공중에서 TOCTOU를 만들어냅니다. 특정 지점이 악용 가능한지 여부는 *소스의 속성이 아닙니다*: 그것은 컴파일러, 그 버전, 아키텍처, 플래그에 의해 결정되며, 빌드할 때에만 한쪽으로 붕괴됩니다. 그 전까지 각각은 둘 다입니다 — 중첩 상태로 유지되는 취약성으로, 소스 수준에서는 진짜로 괜찮은 코드와 구별할 수 없습니다. 각각은 슈뢰딩거 TOCTOU이며 — 위의 표는 그것들이 대규모로 나타나는 모습입니다.

불안한 점은 이 특정 프로젝트들에 결함이 있다는 것이 아니라, 분석이 들여다보는 거의 모든 곳에서 패턴이 나타나며, [세계에서 가장 주의 깊게 검토된 코드](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-sel4-15.0.0.md#82-executive-summary)에 단순한 관용적 C만으로 짜여 들어가 있다는 것입니다. 100개 이상의 저장소는 **경계가 아니라 표본**입니다: 동일한 잠재적 버그는 거의 확실히 여러분의 코드베이스에도 닿아 있습니다.

전체 감사 및 영향 분석은 [observer-effect/](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/)와 그 [REPORT.md](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/REPORT.md)에 있습니다.

## Solutions

> *해결책은 없습니다.*

하지만 그래도 시도해 볼 수 있는 몇 가지가 있습니다.

반사적인 해결책은 로드를 고정하려는 것입니다 — [`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), [원자적 연산](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-2--atomic--acquire-load), [`"memory"` 클로버 `barrier()`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). 이것들은 스펙에 부합하며 `-O3`, LTO 및 인라인을 견뎌냅니다; 단순 읽기가 *[발견된](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)* 곳에서는 [올바른 패치](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/README.md#confirmed-in-the-wild)입니다. 불행하게도, 그것들은 상처를 봉합하지만 원인은 고치지 못합니다:

- **`volatile`은 조용히 씻겨 내려갑니다.** 그것은 객체, 포인터 또는 영역이 아니라 *lvalue 접근을* 한정합니다. 평범한 lvalue를 통한 `volatile T *p` 읽기는 [전혀 보호를 제공하지 않으며](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/volatile_lvalue_launder.c), 그 한정자는 `memcpy`의 `const void *`를 [통과할 때 **진단 없이** 제거됩니다](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/cat-states/volatile_memcpy_overlap.c) — [volatile을 보존하는 `memcpy`는 없습니다](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#false-friends--look-like-barriers-but-are-not). 당신이 작성한 장벽은 당신이 작성하지 않은 호출에서 증발합니다.

- **`READ_ONCE`는 확장되지 않습니다.** "`READ_ONCE`를 사용하라"는 것은 실제로 다음을 의미합니다: [*모든* 필드](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)에 대한 *모든* 공격자 도달 가능 접근을 영원히 주석 처리하고, 읽기와 그 *모든* 사용 사이에 [펜스를 배치](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier)하라는 것입니다. [하나를 놓치면](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) 그 규율은 무효가 됩니다. [대규모로 강제할 수 없으며](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), 조용히 퇴보합니다.

- **`barrier()`의 정확성은 소스 줄에서 몇 겹 떨어진 곳에 있습니다.** 하나의 `copy_from_user(&local, uptr, n)`이 정말로 `"memory"` 클로버를 수반하는지 결정하려면 일반 C에서 아키텍처별 asm까지 [다섯 겹의 인라인 계층과 아웃오브라인 호출을 추적](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/README.md#analysis)하고, 한 움큼의 `CONFIG`/CPU 기능/`__builtin` 분기를 해석해야 합니다. 그리고 한번 찾은 후에도, 그 클로버는 [어떤 읽기도 지명하지 않습니다](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier): 한 발짝 어긋나면 [아무것도 고정하지 못하며](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier), 반대 방향으로 한 발짝 가면 [막아야 할 바로 그 재로드를 *강제*합니다](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#principles--shared-facts-the-cards-lean-on).

하지만 더 중요하게는: 소스는 애초에 재로드를 요청한 적이 없습니다. 이것이 더 깊은 문제입니다. 프로그래머는 `local.len`이라고 썼고 `local.len`을 *의도했습니다*: 하나의 값, 한 번 읽기. 우리가 `x`라고 말하면 `x`를 의미하지, "`x`이지만 컴파일러가 그 대신 `y`를 좋아한다면 `y`"를 의미하는 게 아닙니다. 재로드는 추상 기계 아래에서 발명되므로, 주석이 필요한 코드는 [그렇지 않은 코드와 동일해 보입니다](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/alpha-lab/README.md#same-source-different-outcome) — [그 지점에 장벽이 필요하다는 신호는 없습니다](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md#proposed-levers-do-not-exist-in-usable-form-today). 결코 작성하지 않은 읽기를 지키라고 기억할 수는 없습니다.

방어 전체 목록 — 그 [강점](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-linux-binder-v7.0.md#executive-summary)과 [실패](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/audits/audit-libspdm-3.8.2.md#durability-assessment) — 은 [장벽 보고서](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/BARRIERS.md)에 있습니다.

## Open the box

> *공중에서 만들어진 TOCTOU 패턴은 어디에나 있습니다. 여러분의 코드에 그것이 있는지 확인하세요.*

여러분의 코드를 [`observer-effect/AUDIT-PROMPT.md`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/HEAD/observer-effect/AUDIT-PROMPT.md)로 확인하세요. 이 문서는 신뢰 경계를 찾고, 슈뢰딩거 패턴을 검색하고, 스펙 준수 장벽에 따라 가지치기하며, 가능성/영향/위험을 평가합니다. 이 문서를 여러분이 선호하는 코딩 에이전트에 소스를 컨텍스트로 넣어 건네주고, 서브시스템을 가리키세요:```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는 Christopher Domas(@xoreaxeaxeax)의 연구 프로젝트입니다.


실험


도구 다운로드