Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
schrodingers-toctou — Detecta cargas de memoria inventadas por el compilador que convierten C seguro en vulnerabilidades TOCTOU. Incluye auditorías automatizadas del código fuente, análisis binario basado en Unicorn y barridos de compilador/arquitectura/banderas en más de 100 proyectos. | Kitploit
Herramientas/GitHubGitHub/xoreaxeaxeax/schrodingers-toctou
Análisis Dinámico (Sandboxing)Análisis Estático de Código (SAST)Análisis de VulnerabilidadesExplotaciónAnálisis de BinariosAprendizaje y Educación
GitHubxoreaxeaxeax/schrodingers-toctou

schrodingers-toctou

Detecta cargas de memoria inventadas por el compilador que convierten C seguro en vulnerabilidades TOCTOU. Incluye auditorías automatizadas del código fuente, análisis binario basado en Unicorn y barridos de compilador/arquitectura/banderas en más de 100 proyectos.

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Ver Repositorio
94418hace 1 mesAún no revisado
Compartir

El TOCTOU de Schrödinger

"...la definición de 'compilador sensato' se vuelve cada vez más flexible."

El binario que ejecutas no es el programa que escribiste. El optimizador del compilador reescribe tu código fuente de maneras que nunca ves — y algunos de esos cambios pueden convertir silenciosa y legalmente código aparentemente seguro en binarios vulnerables. La misma línea puede ser segura con un compilador y explotable con otro, sin nada en el código fuente que te indique cuál: una vulnerabilidad mantenida en superposición, que solo se colapsa cuando compilas. Schrödinger's TOCTOU explora cargas inventadas por el compilador y sus amplias implicaciones para las vulnerabilidades de tiempo de verificación a tiempo de uso (TOCTOU) — encontradas en núcleos, hipervisores, enclaves, firmware, y bibliotecas de código abierto. En todas partes, el código aparentemente seguro queda . Pero esos son una muestra, no un límite; los mismos errores están muy probablemente también en .

expuesto a los caprichos del compilador
tu código

Desafío

Empieza con algo fácil.

¿Cuántas veces carga esta función *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:~
Pista: la respuesta es 1 — el código fuente carga `*p` una sola vez en `t`.

Pégalo en [Compiler Explorer](https://godbolt.org/z/c5K9P4dPd) (`arm gcc 14.2.0`,
`-O2`) y cuenta las cargas desde `r0`, que contiene `p`:```asm
g:
        ldrh    r2, [r0]     # load *p, once
        ldrsh   r0, [r0]     # load *p, twice
        subs    r0, r2, r0
        bx      lr

Una carga en el código fuente, dos en el binario. La segunda es una carga inventada — una lectura que el compilador fabricó y que tú nunca escribiste. Es legal bajo la máquina abstracta de C, que asume que la memoria no puede cambiar entre dos lecturas. Pero cuando esa memoria puede ser escrita por un atacante, la suposición se convierte en una explotación: la carga inventada puede caer después de una comprobación de seguridad, reabriendo silenciosamente una ventana de tiempo de comprobación a tiempo de uso (TOCTOU) que el programador creía haber cerrado. El valor que validaste y el valor que usas ya no se garantiza que sean el mismo — aunque nunca escribiste código que lo volviera a leer.

Un desbordamiento de búfer de la nada

El desafío demuestra que la carga inventada existe; veamos cómo eso se convierte en corrupción de memoria.

En una vulnerabilidad TOCTOU, un programa comprueba que un valor es seguro, y luego usa el valor. Sin embargo, existe una ventana de explotación si un atacante puede cambiar el valor en la fracción de tiempo entre esas dos lecturas – el valor inofensivo pasa la comprobación mientras que el peligroso es el que se usa:```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 solución clásica es **crear una instantánea primero**: copiar cualquier dato que el atacante podría manipular a un lugar local al que el atacante no pueda acceder, y luego no confiar en nada más que en ese local. Una vez que `len` está en una variable local queda congelado — un atacante que compita por la memoria compartida ya no puede tocarlo — por lo que la comprobación y la copia están garantizadas de ver el mismo valor. Así es como el código en `receive` a continuación corrige el TOCTOU: crea una instantánea del mensaje, valida la instantánea y publica la copia validada en `slot` para que un consumidor la reenvíe:```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? */
}

Según la fuente, esto es correcto. len se lee exactamente una vez — dentro de la instantánea — por lo que el valor que supera la comprobación <= 20 es el valor publicado en slot. La ventana TOCTOU queda cerrada y el código es seguro.

Excepto que no lo es. Bajo x86-64 gcc con -O2, receive lo lee de la memoria compartida original dos veces: una vez como escalar para la comprobación, y de nuevo como parte de la copia masiva que se publica en 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:~
El control se aplica a la LECTURA #1; el valor que llega a `slot.len` es la LECTURA #2. Un atacante que altera `len` entre ambas pasa un valor seguro al control `<= 20` mientras que uno sobredimensionado se publica en `slot` — y `forward` copia entonces esa cantidad de bytes en `out[20]`, el desbordamiento exacto que la instantánea pretendía evitar, reintroducido por el optimizador.

Esto se convierte en una prueba de concepto completa en [`poc/example.c`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/poc/example.c), donde el código usa el enfoque canónico endurecido contra TOCTOU: un struct `message` no confiable se captura en `local` para que no pueda modificarse, el `local.len` de la instantánea se valida contra la capacidad del búfer, y solo la copia validada se publica en `slot`; un consumidor copia después `slot.len` bytes de payload en un búfer fijo. Simultáneamente, un atacante induce una carrera sobre `shared->len`. Una carga inventada inesperada del compilador relee `shared->len` para la publicación masiva, de modo que `slot.len` lleva el valor sobredimensionado del atacante aunque el control haya pasado — reintroduciendo el TOCTOU que el programador intentaba evitar y creando un desbordamiento de búfer aparentemente imposible — de la nada.

## Causa

> *Para cuando C llega al código máquina, ya ha sido remodelado por el lowering del frontend, las optimizaciones de IR, la asignación de registros y el codegen del backend — un pipeline profundo y de múltiples etapas que toma decisiones que no puedes ver. No hay una única etapa a la que culpar. La carga inventada es una propiedad emergente de todo el pipeline, no un error de ninguna de sus partes.*

En este punto: los compiladores *pueden* emitir cargas inventadas, y el modismo mismo destinado a prevenir el error — capturar, validar, usar — es lo que lo reintroduce. El siguiente paso (para saber si en realidad somos vulnerables) es caracterizar *cuándo* ocurre. Resulta que eso es difícil.

En [`cat-states/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states), buscamos las pruebas de concepto que demuestran que es real — y que está en todas partes:

| Mecanismo | Toolchains | Objetivos |
|---|---|---|
| [**Rematerialización**](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 |
| [**Recarga por desajuste de anchura**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#width-mismatch-reload-class-2) | GCC | ARM, MIPS, MIPS64, RV64, s390x |
| [**Solapamiento masivo vs. escalar**](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 |
| [**Recarga entre clases**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#cross-class-reload-class-4) | GCC | x86-64, s390x |
| [**Fusión de mem-op 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 |
| [**Recarga por orden de bytes**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#byte-order-divergent-reload-class-8) | GCC | s390x |

Cada PoC anterior fija un único punto donde la carga *puede* aparecer; [`alpha-lab/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab) traza el espacio a su alrededor para encontrar dónde caen los bordes — un pipeline de tres etapas impulsado por un único archivo `.c`. El [ejecutor de matrices](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/matrix_runner.py) barre la matriz compilador × arquitectura × flags en [Compiler Explorer](https://godbolt.org); el [detector de cargas](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/detect.py) ejecuta cada binario resultante bajo [Unicorn](https://www.unicorn-engine.org/) y detecta cualquier byte leído dos veces; y el [minimizador de flags](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/flag_search.py) aplica delta-debugging a cada acierto hasta reducirlo al conjunto mínimo de flags que convierte una compilación segura en un TOCTOU de doble lectura.

**El resultado**: ningún compilador, flag o pase concreto tiene la culpa — la doble lectura surge de la interacción compleja de muchas capas del compilador, cada una tomando decisiones localmente válidas. El efecto es no lineal: pequeños cambios en el código fuente, los flags o el objetivo pueden [desencadenar resultados diferentes](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 única forma fiable de saber si una línea concreta es vulnerable es [**compilarla y mirar.**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/README.md#same-source-different-outcome)

**El gato está vivo — y no lo está.** Hasta que compilas, un punto de llamada que captura, valida y usa una copia local no es *ni* seguro *ni* vulnerable — es ambas cosas, y el compilador, su versión, el objetivo y los flags deciden cuál. La compilación es la medición, y colapsa la superposición en un sentido u otro. Esto es un **TOCTOU de Schrödinger**: un control sobre un valor que el programador creía congelado, y que el estándar de C permite silenciosamente que el compilador relea de memoria controlada por el atacante. La caja permanece cerrada hasta que alguien, en algún lugar, elige un toolchain y la abre.

## Efecto

> *El patrón aparece en casi todas partes — tejido en el código más cuidadosamente revisado del mundo mediante simple C idiomático.*

El problema es **prácticamente intratable**. El mismo fragmento de código puede ser vulnerable o no según la combinación precisa de compilador × versión × arquitectura × flags — y hay más combinaciones de ese tipo que átomos en el universo observable. Acotarlo incluso para un único codebase es una búsqueda casi desesperada; hacerlo en todo el ecosistema es mucho peor.

Incluso decidir si un *único* punto de llamada es seguro se resiste a la inspección: una posible barrera como `copy_from_user` del kernel solo [descarta el error](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/README.md#analysis) después de que ~seis capas de inlining, macros y bifurcaciones de `CONFIG`/características de CPU desemboquen en un `asm` opaco — y la *misma* línea de código fuente no es ninguna barrera en otras configuraciones. Leer el *sitio* de la llamada no nos dice nada.

La única vía a seguir es la automatización. Se ejecutó un análisis basado en heurísticas sobre objetivos destacados de código abierto — hipervisores, runtimes de TEE/enclaves, firmware, subsistemas del kernel, bibliotecas de protocolos — y encontró **más de 300 TOCTOU de Schrödinger** en **más de 100 proyectos críticos para la seguridad**: sitios donde el estándar de C *permite* que el compilador relea memoria escribible por el atacante entre un control y su uso. El análisis automatizado identifica los límites de confianza, busca el patrón de Schrödinger y evalúa probabilidad/impacto/riesgo.

Los resultados muestran que las cargas inventadas por el compilador, aparentemente inofensivas, se convierten fácilmente en consecuencias devastadoras.

El compilador no inventa tanto una *carga* como la *capacidad* que esa carga entrega a un atacante:

---

- **escape de VM inventado por el compilador** — [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 inventado por el compilador** — [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)
- **persistencia de plataforma inventada por el compilador** — [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)
- **brecha de enclave inventada por el compilador** — [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)

---

Cada uno de estos puede ser catastrófico por sí solo, pero lo que inquieta es la amplitud: la misma forma aparece en todos lados donde mira el análisis, en código que no comparte nada salvo el modismo:

| Objetivo | Sitio | Impacto |
|---|---|---|
| **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) | longitud del PRDT AHCI de la invitada fijada una vez → **lectura fuera de límites / DMA del host dirigido por el atacante** |
| **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 software-RDMA reutilizado → **escritura fuera de límites en el kernel** |
| **edk2 / UEFI** | [`SmmLockBoxRestore`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-edk2-edk2-stable202605.md#candidate-1--smmlockboxrestore) | longitud del búfer SMM reutilizada → **escritura fuera de límites en 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) | longitud de descifrado in situ reutilizada → **escritura fuera de límites en la raíz de confianza del 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) | ventana de objeto de retype reutilizada → **compromiso del kernel** |
| **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 de la invitada reutilizada en el recorrido → **escalada de privilegios** |
| **SGX** | [puente ECALL de 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) | longitud `[in]`/`[in,out]` reutilizada para `malloc`/`memcpy_s` → **desbordamiento de heap en el enclave** (cada 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) | campo del descriptor FF-A reutilizado para dimensionar `memcpy` → **desbordamiento de heap en el monitor seguro EL3** |
| **Linux / Hyper-V** | [`__vmbus_on_msg_dpc` de Hyper-V VMBus](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-linux-v7.0-hyperv-vmbus.md#candidate-2--msgtype-dispatch-index) | `msgtype` del host reutilizado para indexar la tabla de manejadores → **llamada indirecta salvaje** en el kernel invitado |
| **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` del used-ring de virtio reutilizado como índice de array → **lectura/escritura fuera de límites de heap** en el 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) | índice de versión VERNEED del cargador dinámico reutilizado como subíndice de escritura → **escritura fuera de límites en `ld.so`** al mapear una biblioteca compartida manipulada |
| **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) | tamaño de campo del journal reutilizado entre alloc/copia → **escritura fuera de límites de heap** en `journalctl`/`coredumpctl` (frecuentemente como 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) | desplazamiento de chunk del object-store reutilizado como base/tamaño de chunk → **lectura fuera de límites** al analizar un `.idx` / multi-pack-index / commit-graph manipulado (repo compartido / backend de forja) |
| **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) | desplazamiento del freeblock del árbol B reutilizado como índice de página → **lectura fuera de límites de una página de base de datos mapeada con `mmap`** |
| **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) | recuento de puntos empaquetados de fuente variable reutilizado → **escritura fuera de límites de heap** al renderizar una fuente manipulada (omnipresente: Android / Chrome / escritorio) |
| **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) | desplazamiento/longitud de span NeXT-RLE reutilizados → **escritura fuera de límites de heap** al decodificar un TIFF manipulado (modo de lectura con `mmap` por defecto) |
| **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) | recuento de FDE de SFrame reutilizado como tamaño de alloc **y** límite de relleno → **escritura fuera de límites de heap en el enlazador** sobre un objeto manipulado |
| **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` de AutoIt reutilizado como tamaño de alloc **y** longitud de copia → **escritura fuera de límites de heap** en el escáner |
| **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) | recuento de exportaciones PE reutilizado como límite de bucle → **lectura fuera de límites** al escanear una muestra manipulada |
| **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) | desplazamiento `%s` de la invitada releído más allá del arena del sandbox → **lectura fuera de límites que filtra memoria del host a la invitada 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) | longitud SUN-raster reutilizada como tamaño de alloc **y** longitud de copia → **escritura fuera de límites de heap → RCE** al decodificar una imagen manipulada (compilaciones 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` del used-ring de virtio escrito por el host reutilizado como índice de array sin límites → **doble liberación de descriptor / UAF** en el kernel |

Todo es vulnerable. Y nada lo es. En todas las situaciones, el código fuente hace lo correcto: capturar la entrada no confiable, validar la copia, usar la copia. Pero en cada una, el estándar de C permite silenciosamente que el compilador *deshaga* opcionalmente ese proceso y cree un TOCTOU de la nada. Que un sitio concreto sea explotable *no es una propiedad del código fuente*: lo deciden el compilador, su versión, la arquitectura y los flags, y se colapsa en un sentido solo cuando compilas. Hasta entonces, cada uno es ambas cosas — una vulnerabilidad mantenida en superposición, indistinguible a nivel de código fuente de un código genuinamente correcto. Cada uno es un TOCTOU de Schrödinger — y la tabla anterior es cómo se ven a escala.

Lo inquietante no es que estos proyectos concretos tengan fallos — es que el patrón aparece en casi todas partes donde mira el análisis, tejido en [el código más cuidadosamente revisado del mundo](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-sel4-15.0.0.md#82-executive-summary) a través de nada más que C idiomático. Los más de 100 repositorios son una **muestra, no el límite**: el mismo error latente casi con seguridad llega a tu propio codebase.

El análisis de auditoría e impacto completo está en [observer-effect/](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect) y en su [REPORT.md](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/REPORT.md).

## Soluciones

> *No hay ninguna.*

Pero aquí hay algunas cosas que podemos intentar de todos modos.

La solución refleja es intentar fijar la carga — [`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), un [atómico](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-2--atomic--acquire-load), una [`barrier()` con clobber `"memory"`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). Esas son sólidas según la especificación y sobreviven a `-O3`, LTO e inlining; donde se *[encuentra](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)* una lectura desnuda, son [el parche correcto](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/README.md#confirmed-in-the-wild). Desafortunadamente, curan la herida, pero no la causa:

- **`volatile` se desvanece silenciosamente.** [Califica *el acceso al lvalue*](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#the-volatile-cat-state), no al objeto, al puntero ni a la región. Una lectura de `volatile T *p` a través de un lvalue simple [no ofrece ninguna protección](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/volatile_lvalue_launder.c), y el calificador se descarta **sin ningún diagnóstico** cuando [pasa a través del `const void *` de `memcpy`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/volatile_memcpy_overlap.c) — [no existe ningún `memcpy` que preserve `volatile`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#false-friends--look-like-barriers-but-are-not). La barrera que escribiste se evapora en la llamada que no escribiste.

- **`READ_ONCE` no escala.** "Usa `READ_ONCE`" significa en realidad: anota *cada* acceso alcanzable por el atacante de [*cada* campo](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), para siempre, y [coloca una valla entre la lectura y *todos* sus usos](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier). [Si se te escapa uno](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), la disciplina queda anulada. [No se puede imponer a escala](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-1--volatile--read_once-access-site-latch), y se erosiona silenciosamente.

- **La corrección de una `barrier()` se decide a muchos marcos de distancia de la línea de código fuente.** Decidir si un `copy_from_user(&local, uptr, n)` siquiera lleva un clobber `"memory"` significa [rastrear cinco capas inline y una llamada fuera de línea](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/README.md#analysis) desde C genérico hasta asm específico de la arquitectura, resolviendo un puñado de bifurcaciones de `CONFIG`/características de CPU/`__builtin`. E incluso una vez encontrado, el clobber [no nombra ninguna lectura](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier): un paso fuera de lugar [no fija nada](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#i-3--memory-clobber-compiler-barrier); un paso en la otra dirección [*fuerza* la misma recarga que debería detener](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#principles--shared-facts-the-cards-lean-on).

Pero lo más importante: el código fuente nunca pide una recarga en primer lugar. Este es el problema más profundo. El programador escribió `local.len` y *quería decir* `local.len`: un valor, leído una vez. Si decimos `x`, queremos decir `x`, no "`x`, pero `y` si al compilador le gusta más eso". La recarga se inventa por debajo de la máquina abstracta, por lo que el código que necesita la anotación [se ve idéntico al que no la necesita](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/README.md#same-source-different-outcome) — [no hay ninguna señal en el sitio de que se requiera una barrera](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md#proposed-levers-do-not-exist-in-usable-form-today). No puedes acordarte de proteger una lectura que nunca escribiste.

El catálogo completo de defensas — con sus [fortalezas](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-linux-binder-v7.0.md#executive-summary) y [fallos](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/audits/audit-libspdm-3.8.2.md#durability-assessment) — está en el [informe de barreras](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/BARRIERS.md).

## Abre la caja

> *El patrón de TOCTOU de la nada está en todas partes. Comprueba si tu código lo tiene.*

Comprueba tu propio código con [`observer-effect/AUDIT-PROMPT.md`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/observer-effect/AUDIT-PROMPT.md), que buscará los límites de confianza, rastreará el patrón de Schrödinger, podará según las barreras conformes a la especificación y evaluará probabilidad/impacto/riesgo. Entrégalo a tu agente de codificación preferido con tu código fuente en contexto y apúntalo a un subsistema:```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."

No depende de nada más en este repositorio: copia el único archivo y listo.

Futuro

Schrödinger's TOCTOU disecciona una instanciación concreta de una optimización aleatoria permitida por la especificación C de 500 páginas. Pero apenas está rascando la superficie: queda tanto terreno por explorar. Este repositorio seguirá investigando, capturando y catalogando las formas inesperadas en que tu compilador favorito te socava — en silencio, de forma legal y en todos los niveles de optimización.

"... si gcc hiciera eso, gran parte del kernel ardería en llamas."

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

"A la gente le encanta hablar de 'C seguro', pero los desarrolladores de compiladores han intentado activamente hacer C más inseguro durante décadas. El comité de estándares de C ha sido cómplice."

— Linus Torvalds, 2025-02-21 · lore

"Preferiría con creces una opción de compilador que le indique al compilador que no haga malditas estupideces como esta, en lugar de marcar cada dos por tres las cargas/almacenamientos del kernel con volatile."

— Peter Zijlstra, 2015-06-17 · lore

"La especificación no es más que papel higiénico. Lo ÚNICO que importa es lo que hace el hardware real."

— Linus Torvalds, 2006-12-04 · lore

"Con esa argumentación tendríamos que cubrir medio kernel con _ONCE() … ¿Podemos por fin plantar cara y decirles a los de los compiladores y al comité de estándares que detengan esta locura?

— Thomas Gleixner, 2019-08-16 · lore

"Los compiladores que 'optimizan' cosas para tocar campos que el código fuente no toca son, sencillamente, una mierda inherentemente defectuosa. No me interesa en absoluto hacerles el juego a su locura... Afirmar que hay que marcarlos con volatile es síntoma de un desarrollador de compiladores enfermo."

— Linus Torvalds, 2014-12-04 · lore

"¿Insano? Probablemente sí. Pero hay gente de compiladores que lo defiende a capa y espada."

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

"Está bien que hayan probado todas las rutas de código, pero invariablemente las han probado con un compilador que no se esfuerza por generar código "legal pero idiota". Así que las pruebas normalmente no encontrarán casos en los que el compilador haya estado autorizado a hacer otra cosa. ... Quienes se dedican a los compiladores y no se dan cuenta de esto no son gente de compiladores. Son académicos dedicados a la masturbación mental."

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

"Por supuesto, no me preocupan los compiladores estúpidos, sino los listos..."

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

"... hemos tenido desarrolladores de compiladores que dicen "si lees las especificaciones, está bien". No, no está bien. Porque la realidad supera cualquier lectura retorcida de las especificaciones."

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

"... la definición de 'compilador cuerdo' es cada vez más laxa."

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

Referencias

  • Whitepaper: (próximamente)
  • Diapositivas: (próximamente)
  • Presentación: (próximamente)

Autor

Schrödinger's TOCTOU es un trabajo de investigación de Christopher Domas (@xoreaxeaxeax)


Experimento


Descargar herramienta