Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
schrodingers-toctou — Détecte les chargements mémoire inventés par le compilateur qui transforment du C sécurisé en vulnérabilités TOCTOU. Comprend des audits automatisés de code source, une analyse binaire basée sur Unicorn et des balayages compilateur/arch/flags sur plus de 100 projets. | Kitploit
Outils/GitHubGitHub/xoreaxeaxeax/schrodingers-toctou
Analyse Dynamique (Sandboxing)Analyse Statique de Code (SAST)Analyse des VulnérabilitésExploitationAnalyse de BinairesApprentissage et Éducation
GitHubxoreaxeaxeax/schrodingers-toctou

schrodingers-toctou

Détecte les chargements mémoire inventés par le compilateur qui transforment du C sécurisé en vulnérabilités TOCTOU. Comprend des audits automatisés de code source, une analyse binaire basée sur Unicorn et des balayages compilateur/arch/flags sur plus de 100 projets.

Voir le dépôt
94418il y a 1 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Le TOCTOU de Schrödinger

"...la définition d'un 'compilateur sain d'esprit' ne cesse de s'assouplir."

Le binaire que vous exécutez n'est pas le programme que vous avez écrit. L'optimiseur du compilateur réécrit votre source de manières que vous ne voyez jamais — et certains de ces changements peuvent silencieusement et légitimement transformer un code d'apparence sécurisée en binaires vulnérables. La même ligne peut être sûre avec un compilateur et exploitable avec un autre, sans que rien dans la source ne vous indique laquelle : une vulnérabilité maintenue en superposition, qui ne se matérialise qu'à la compilation. Schrödinger's TOCTOU explore les chargements inventés par le compilateur et leurs implications étendues pour les vulnérabilités de type temps de vérification / temps d'utilisation (TOCTOU) — découvertes dans des noyaux open source, hyperviseurs, enclaves, firmware, et bibliothèques. Partout où nous regardons, un code d'apparence sécurisée reste . Mais ce ne sont que des exemples, pas une limite ; les mêmes bugs se trouvent très probablement dans .

exposé aux caprices du compilateur
votre code aussi

Challenge

Commençons par quelque chose de facile.

Combien de fois cette fonction charge-t-elle *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:~
Indice : la réponse est 1 — la source charge `*p` une seule fois dans `t`.

Collez-le dans [Compiler Explorer](https://godbolt.org/z/c5K9P4dPd) (`arm gcc 14.2.0`,
`-O2`) et comptez les chargements depuis `r0`, qui contient `p` :```asm
g:
        ldrh    r2, [r0]     # load *p, once
        ldrsh   r0, [r0]     # load *p, twice
        subs    r0, r2, r0
        bx      lr

Une lecture dans la source, deux dans le binaire. La seconde est une lecture inventée — une lecture que le compilateur a fabriquée et que vous n'avez jamais écrite. Elle est légale selon la machine abstraite du C, qui suppose que la mémoire ne peut pas changer entre deux lectures. Mais lorsque cette mémoire est modifiable par un attaquant, l'hypothèse devient un vecteur d'exploitation : la lecture inventée peut survenir après un contrôle de sécurité, rouvrant silencieusement une fenêtre de type time-of-check to time-of-use (TOCTOU) que le programmeur croyait avoir fermée. La valeur que vous avez validée et celle que vous utilisez ne sont plus garanties d'être identiques — même si vous n'avez jamais écrit de code qui la relise.

Un débordement de tampon sorti de nulle part

Le défi prouve que la lecture inventée existe ; voyons comment cela se transforme en corruption de la mémoire.

Dans une vulnérabilité TOCTOU, un programme vérifie qu'une valeur est sûre, puis utilise la valeur. Cependant, une fenêtre d'exploitation existe si un attaquant peut modifier la valeur dans l'infime intervalle de temps entre ces deux lectures – la valeur inoffensive passe le contrôle tandis que la valeur dangereuse est celle qui est utilisée :```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:~
La correction classique consiste à **faire d'abord un instantané** : copier toutes les données que l'attaquant pourrait modifier dans une zone locale à laquelle l'attaquant ne peut pas accéder, puis ne faire confiance qu'à cette zone locale. Une fois `len` dans une variable locale, il est figé — un attaquant en course avec la mémoire partagée ne peut plus le toucher — si bien que la vérification et la copie sont garanties de voir la même valeur. C'est ainsi que le code dans `receive` ci-dessous corrige la faille TOCTOU : il prend un instantané du message, valide l'instantané, puis publie la copie validée dans `slot` pour qu'un consommateur la transmette :```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? */
}

D'après le code source, cela est correct. len est lu exactement une fois — dans l'instantané — donc la valeur qui satisfait la condition <= 20 est celle publiée dans slot. La fenêtre TOCTOU est fermée et le code est sûr.

Sauf que non. Sous x86-64 gcc -O2, receive le lit depuis la mémoire partagée originale deux fois : une fois comme scalaire pour contrôler le test, et une autre fois dans le cadre de la copie en bloc qui est publiée dans 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:~
Le contrôle s'appuie sur la lecture n°1 ; la valeur qui aboutit dans `slot.len` est la lecture n°2. Un
attaquant qui fait basculer `len` entre les deux fait passer une valeur sûre au contrôle `<= 20`
tandis qu'une valeur surdimensionnée est publiée dans `slot` — et `forward` copie alors autant
d'octets dans `out[20]`, exactement le débordement que l'instantané était censé empêcher,
réintroduit par l'optimiseur.

Cela est transformé en preuve de concept complète dans
[`poc/example.c`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/poc/example.c), où le code utilise l'approche canonique de durcissement contre les TOCTOU : une structure `message` non fiable est figée dans
`local` afin qu'elle ne puisse pas être modifiée, le `local.len` de l'instantané est validé
par rapport à la capacité du tampon, et seule la copie validée est publiée dans
`slot` ; un consommateur copie ensuite les octets de charge utile de `slot.len` dans un tampon fixe.
Simultanément, un attaquant fait la course sur `shared->len`. Une charge inventée inattendue
de la part du compilateur relit `shared->len` pour la publication en bloc, de sorte que `slot.len`
porte la valeur surdimensionnée de l'attaquant même si le contrôle a réussi —
réintroduisant le TOCTOU que le programmeur tentait de contrer, et
créant un débordement de tampon apparemment impossible — sorti de nulle part.

## Cause

> *Au moment où C atteint le code machine, il a été remodelé par l'abaissement du frontend,
> les optimisations IR, l'allocation des registres et la génération de code du backend — un pipeline profond et multi-étapes
> qui prend des décisions que vous ne pouvez pas voir. Aucune étape n'est à blâmer. La
> charge inventée est une propriété émergente de l'ensemble du pipeline, pas un bug d'une de ses parties.*

À ce stade : les compilateurs *peuvent* émettre des charges inventées, et l'idiome même censé
prévenir le bug — figer, valider, utiliser — est ce qui le réintroduit. L'étape
suivante (pour savoir si nous sommes réellement vulnérables) consiste à caractériser *quand*
cela se produit. Il s'avère que c'est difficile.

Dans [`cat-states/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states), nous recherchons les preuves de concept qui montrent
que c'est réel — et que c'est partout :

| Mécanisme | Toolchains | Cibles |
|---|---|---|
| [**Rematérialisation**](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 |
| [**Rechargement à largeur non concordante**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#width-mismatch-reload-class-2) | GCC | ARM, MIPS, MIPS64, RV64, s390x |
| [**Chevauchement groupé vs scalaire**](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 |
| [**Rechargement inter-classes**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#cross-class-reload-class-4) | GCC | x86-64, s390x |
| [**Fusion d'op mémoire CISC**](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 |
| [**Rechargement d'ordre d'octets**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#byte-order-divergent-reload-class-8) | GCC | s390x |

Chaque PoC ci-dessus épingle un point unique où la charge *peut* apparaître ;
[`alpha-lab/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab) cartographie l'espace autour pour trouver où tombent les limites
— un pipeline en trois étapes piloté depuis un seul fichier `.c`.
Le [runner de matrice](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/matrix_runner.py) balaie la matrice compilateur ×
architecture × flags sur [Compiler Explorer](https://godbolt.org) ;
le [détecteur de charge](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/detect.py) exécute chaque binaire obtenu sous
[Unicorn](https://www.unicorn-engine.org/) et attrape tout octet lu deux fois ; et
le [minimiseur de flags](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/flag_search.py) réduit par delta chaque résultat jusqu'à l'
ensemble minimal de flags qui transforme une construction sécurisée en TOCTOU à double lecture.

**Le résultat** : aucun compilateur, flag ou passe en particulier n'est à blâmer — la double lecture
émerge de l'interaction complexe de nombreuses couches du compilateur, chacune prenant
des décisions localement valides. L'effet est non linéaire : de petits changements dans la source,
les flags ou la cible peuvent [déclencher en cascade des résultats différents](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). La seule façon fiable de
savoir si une ligne donnée est vulnérable est de [**la compiler et de regarder.**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/README.md#same-source-different-outcome)

**Le chat est vivant — et il ne l'est pas.** Tant que vous ne compilez pas, un site d'appel qui
fige, valide et utilise une copie locale n'est *ni* sûr *ni* vulnérable —
il est les deux, et le compilateur, sa version, la cible et les flags décident
duquel. La compilation est la mesure, et elle effondre la superposition d'un côté
ou de l'autre. C'est un **TOCTOU de Schrödinger** : un contrôle sur une valeur que le
programmeur croyait figée, que la norme C permet silencieusement au compilateur de
relire depuis une mémoire contrôlée par l'attaquant. La boîte reste fermée jusqu'à ce que quelqu'un,
quelque part, choisisse une chaîne d'outils et l'ouvre.

## Effet

> *Le schéma apparaît presque partout — tissé dans le code le plus soigneusement
> relu au monde à travers un simple C idiomatique.*

Le problème est **quasi insoluble**. Le même extrait de code peut être
vulnérable ou non selon la combinaison précise de compilateur ×
version × architecture × flags — et il existe plus de telles combinaisons qu'il
n'y a d'atomes dans l'univers observable. Le borner pour une seule base de code est
une recherche quasi désespérée ; le faire à travers l'écosystème est bien pire.

Même décider si un *seul* site d'appel est sûr résiste à l'inspection : une
barrière possible comme `copy_from_user` du noyau n'[écarte le bug](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/README.md#analysis)
qu'après qu'environ six couches d'inlining, de macros et de bifurcations `CONFIG`/fonctionnalités CPU
aboutissent à un `asm` opaque — et la *même* ligne de source n'est pas du tout une barrière dans d'
autres configurations. Lire le site d'appel ne nous apprend rien.

La seule voie à suivre est l'automatisation. Une analyse heuristique a été menée sur
d'importantes cibles open source — hyperviseurs, runtimes TEE/enclave, firmware,
sous-systèmes du noyau, bibliothèques de protocoles — et a trouvé **plus de 300 TOCTOU de Schrödinger**
dans **plus de 100 projets critiques pour la sécurité** : des sites où la norme C *permet*
au compilateur de relire une mémoire accessible en écriture par l'attaquant entre un contrôle et son utilisation.
L'analyse automatisée identifie les frontières de confiance, recherche le
schéma de Schrödinger et évalue probabilité/impact/risque.

Les résultats montrent que des charges inventées par le compilateur apparemment anodines peuvent facilement déboucher
en cascade sur des conséquences dévastatrices.

Le compilateur n'invente pas tant une *charge* que la *capacité* que cette charge offre
à un attaquant :

---

- **évasion de VM inventée par le compilateur** — [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)
- **root inventé par le compilateur** — [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)
- **persistance de plateforme inventée par le compilateur** — [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)
- **brèche d'enclave inventée par le compilateur** — [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)

---

Chacun de ces cas peut être catastrophique en soi, mais c'est l'ampleur qui dérange :
la même forme apparaît partout où l'analyse regarde, dans du code qui ne partage
rien d'autre que l'idiome :

| Cible | 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) | longueur AHCI PRDT de l'invité mémorisée une fois → **lecture OOB / DMA hôte dirigé par l'attaquant** |
| **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) | `num_sge` de RDMA logiciel réutilisé → **écriture OOB du noyau** |
| **edk2 / UEFI** | [`SmmLockBoxRestore`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-edk2-edk2-stable202605.md#candidate-1--smmlockboxrestore) | longueur du tampon SMM réutilisée → **écriture OOB dans 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) | longueur de déchiffrement sur place réutilisée → **écriture OOB dans la racine de confiance du TPM** |
| **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) | fenêtre d'objet retype réutilisée → **compromission du noyau** |
| **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) | PTE invité réutilisée pendant la marche → **élévation de privilèges** |
| **SGX** | [pont ECALL edger8r](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) | longueur `[in]`/`[in,out]` réutilisée pour `malloc`/`memcpy_s` → **débordement de tas d'enclave** (chaque 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) | champ de descripteur FF-A réutilisé pour dimensionner `memcpy` → **débordement de tas dans le moniteur sécurisé EL3** |
| **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) | `msgtype` hôte réutilisé pour indexer la table de gestionnaires → **appel indirect sauvage** dans le noyau invité |
| **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) | `id` du used-ring virtio réutilisé comme index de tableau → **lecture/écriture OOB de tas** dans le 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) | index de version VERNEED du chargeur dynamique réutilisé comme indice d'écriture → **écriture OOB dans `ld.so`** lors du mappage d'une bibliothèque partagée spécialement conçue |
| **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) | taille de champ de journal réutilisée entre allocation/copie → **écriture OOB de tas** dans `journalctl`/`coredumpctl` (souvent en 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) | décalage de bloc du magasin d'objets réutilisé comme base/taille de bloc → **lecture OOB** lors de l'analyse d'un `.idx` / multi-pack-index / commit-graph contrefait (dépôt partagé / backend de forge) |
| **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) | décalage de bloc libre d'arbre B réutilisé comme index de page → **lecture OOB d'une page de base de données `mmap`'ée** |
| **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) | nombre de points compressés d'une police variable réutilisé → **écriture OOB de tas** lors du rendu d'une police contrefaite (omniprésent : Android / Chrome / bureau) |
| **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) | décalage/longueur d'étendue NeXT-RLE réutilisés → **écriture OOB de tas** lors du décodage d'un TIFF contrefait (mode lecture `mmap`'é par défaut) |
| **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) | nombre de FDE SFrame réutilisé comme taille d'allocation **et** limite de remplissage → **écriture OOB de tas dans l'éditeur de liens** sur un objet contrefait |
| **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) | `csize` AutoIt réutilisé comme taille d'allocation **et** longueur de copie → **écriture OOB de tas** dans le scanneur |
| **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) | nombre d'exports PE réutilisé comme limite de boucle → **lecture OOB** lors du scan d'un échantillon contrefait |
| **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) | décalage `%s` invité relu au-delà de l'arène du sandbox → **lecture OOB fuyant la mémoire hôte vers l'invité wasm** |
| **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) | longueur SUN-raster réutilisée comme taille d'allocation **et** longueur de copie → **écriture OOB de tas → RCE** lors du décodage d'une image contrefaite (builds LTO) |
| **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) | `id` du used-ring virtio écrit par l'hôte réutilisé comme index de tableau non borné → **double-free de descripteur / UAF** dans le noyau |

Tout est vulnérable. Et tout ne l'est pas. Dans chaque situation, la source
fait ce qu'il faut : figer l'entrée non fiable, valider la copie, utiliser la
copie. Mais dans chacune, la norme C permet silencieusement au compilateur de *défaire*
ce processus à sa discrétion et de créer un TOCTOU sorti de nulle part. Qu'
un site donné soit exploitable n'est *pas une propriété de la source* : cela est décidé
par le compilateur, sa version, l'architecture et les flags, et cela ne se résout
que lorsque vous compilez. Jusque-là, chacun est les deux à la fois — une vulnérabilité maintenue en
superposition, impossible à distinguer au niveau de la source d'un code qui est réellement
sain. Chacun est un TOCTOU de Schrödinger — et le tableau ci-dessus montre à quoi ils ressemblent
à grande échelle.

Ce qui est troublant, ce n'est pas que ces projets particuliers soient imparfaits — c'est
que le schéma apparaît presque partout où l'analyse se tourne, tissé dans [le
code le plus soigneusement relu au monde](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-sel4-15.0.0.md#82-executive-summary)
à travers rien de plus qu'un C idiomatique. Les plus de 100 dépôts sont un
**échantillon, pas la limite** : le même bug latent atteint presque certainement votre
propre base de code.

L'audit complet et l'analyse d'impact se trouvent dans [observer-effect/](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect)
et son [REPORT.md](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/REPORT.md).

## Solutions

> *Il n'y en a pas.*

Mais voici quelques choses que nous pouvons essayer quand même.

La solution réflexe consiste à essayer d'épingler la charge — [`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), une
[opération atomique](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-2--atomic--acquire-load), une [`"memory"`-clobber `barrier()`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). Ces solutions sont conformes à la spécification et survivent
à `-O3`, au LTO et à l'inlining ; là où une lecture nue est *[trouvée](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)*, elles constituent [le
correctif approprié](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/README.md#confirmed-in-the-wild). Malheureusement, elles pansent la plaie, mais pas la cause :

- **`volatile` se volatilise en silence.** Il [qualifie *l'accès à la lvalue*](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#the-volatile-cat-state), pas
  l'objet, le pointeur ou la région. Une lecture `volatile T *p` à travers une lvalue
  ordinaire [n'offre aucune protection](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/volatile_lvalue_launder.c), et le
  qualificateur est supprimé **sans diagnostic** lorsqu'il [passe par `const void *` de
  `memcpy`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/volatile_memcpy_overlap.c) — il n'existe [aucun `memcpy` préservant la volatilité](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#false-friends--look-like-barriers-but-are-not). La barrière que vous
  avez écrite s'évapore à l'appel que vous n'avez pas écrit.

- **`READ_ONCE` ne passe pas à l'échelle.** « Utilisez `READ_ONCE` » signifie en réalité : annoter
  *chaque* accès accessible à l'attaquant de [*chaque* champ](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), pour toujours, et [placer une
  barrière entre la lecture et *toutes* ses utilisations](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). [Rater un seul](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) et la discipline est
  caduque. Cela [ne peut pas être appliqué à grande échelle](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), et cela régresse silencieusement.

- **L'exactitude d'une `barrier()` se joue à plusieurs frames de pile de la ligne source.** Décider
  si un simple `copy_from_user(&local, uptr, n)` porte même un clobber `"memory"`
  implique de [remonter cinq couches inlinées et un appel hors ligne](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/README.md#analysis) depuis du C générique
  jusqu'à de l'asm spécifique à l'architecture, en résolvant une poignée de bifurcations `CONFIG`/fonctionnalités CPU/`__builtin`.
  Et même une fois trouvé, le clobber [ne nomme aucune lecture](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier) : un pas de travers et il [n'épingle
  rien](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier) ; un pas dans l'autre sens et il [*force* exactement le rechargement qu'il devrait
  arrêter](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#principles--shared-facts-the-cards-lean-on).

Mais plus important encore : la source ne demande jamais de rechargement en premier lieu. C'est là
le problème plus profond. Le programmeur a écrit `local.len` et *voulait dire* `local.len` : une
valeur, lue une fois. Si nous disons `x`, nous voulons dire `x`, pas « `x`, mais `y` si le compilateur
préfère cela à la place ». Le rechargement est inventé sous la machine abstraite, de sorte que le
code qui a besoin de l'annotation [ressemble exactement au code qui n'en a pas
besoin](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/README.md#same-source-different-outcome) — il n'y a [aucun
signal sur le site indiquant qu'une barrière est
requise](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#proposed-levers-do-not-exist-in-usable-form-today).
Vous ne pouvez pas vous rappeler de protéger une lecture que vous n'avez jamais écrite.

Le catalogue complet des défenses — avec leurs [forces](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-linux-binder-v7.0.md#executive-summary) et leurs [échecs](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-libspdm-3.8.2.md#durability-assessment) — se trouve dans le
[rapport sur les barrières](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md).

## Ouvrir la boîte

> *Le schéma du TOCTOU sorti de nulle part est partout. Vérifiez si votre code en présente.*

Vérifiez votre propre code avec
[`observer-effect/AUDIT-PROMPT.md`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/AUDIT-PROMPT.md), qui
recherchera les frontières de confiance, cherchera le schéma de Schrödinger, élaguera selon les
barrières conformes à la spécification et évaluera probabilité/impact/risque. Confiez-le à votre
agent de codage préféré avec votre source en contexte et orientez-le vers un sous-système :```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."

Il ne dépend de rien d'autre dans ce dépôt — copiez l'unique fichier et c'est parti.

Futur

Schrödinger's TOCTOU dissèque une instanciation spécifique d'une optimisation quelconque permise par la spécification C de 500 pages. Mais ce n'est que le début : il reste tant de terrain à explorer. Ce dépôt continuera à sonder, capturer et cataloguer les façons inattendues dont votre compilateur préféré vous trahit — silencieusement, légalement, et à chaque niveau d'optimisation.

"... si gcc faisait ça, une grande partie du noyau partirait en fumée."

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

"Les gens adorent parler de « C sûr », mais les concepteurs de compilateurs ont activement tenté de rendre C plus dangereux pendant des décennies. Le comité des normes C a été complice."

— Linus Torvalds, 2025-02-21 · lore

"Je préférerais de loin une option de compilateur qui demande au compilateur de ne pas faire des choses sacrément stupides comme ça plutôt que de marquer un load/store sur deux dans le noyau avec volatile."

— Peter Zijlstra, 2015-06-17 · lore

"La spec n'est que du papier toilette. La SEULE chose qui compte, c'est ce que fait le vrai matériel."

— Linus Torvalds, 2006-12-04 · lore

"Avec ce raisonnement, il faut couvrir la moitié du noyau de _ONCE() … Pouvons-nous enfin nous montrer fermes et dire aux gens des compilateurs et des comités de normes d'arrêter cette folie ?

— Thomas Gleixner, 2019-08-16 · lore

"Les compilateurs qui « optimisent » des choses pour toucher des champs que le code source ne touche pas sont simplement des merdes intrinsèquement buguées. Je ne suis pas du tout intéressé à satisfaire leur folie... Prétendre qu'ils doivent être marqués volatile est un symptôme d'un développeur de compilateur malade."

— Linus Torvalds, 2014-12-04 · lore

"Fou ? Probablement. Mais certains développeurs de compilateurs le jurent."

— Paul E. McKenney, LKML, 2008-02-04 · lore

"C'est une bonne chose s'ils ont testé tous les chemins de code, mais ils ont invariablement été testés avec un compilateur qui ne se donne pas la peine d'essayer de générer du code « légal mais idiot ». Les tests ne trouveront donc généralement pas les cas où le compilateur a pu être autorisé à faire autre chose. ... Les développeurs de compilateurs qui ne réalisent pas cela ne sont pas des développeurs de compilateurs. Ce sont des universitaires adonnés à la masturbation mentale."

— Linus Torvalds, LKML, 2007-01-04 · lore

"Bien sûr, ce ne sont pas les compilateurs stupides qui m'inquiètent, mais plutôt les intelligents..."

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

"... nous avons eu des développeurs de compilateurs qui disent « si vous lisez les specs, c'est ok ». Non, ce n'est pas ok. Parce que la réalité l'emporte sur toute lecture sournoise des specs."

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

"... la définition de « compilateur sain » devient de plus en plus lâche."

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

Références

  • Livre blanc : (à venir)
  • Diapositives : (à venir)
  • Présentation : (à venir)

Auteur

Schrödinger's TOCTOU est un effort de recherche de Christopher Domas (@xoreaxeaxeax)


Experiment


Télécharger l’outil