Malicious ONNX PoC files — security research only
This repository contains deliberately malformed .onnx model files that trigger an out-of-bounds heap write in the NVIDIA TensorRT ONNX parser (onnx/onnx-tensorrt). Access is granted for coordinated vulnerability disclosure and triage purposes only.
Log in or Sign Up to review the conditions and access this model content.
PoC — CWE-787 out-of-bounds write in onnx/onnx-tensorrt (ONNX-TRT 11.1 GA)
huntr Model File Vulnerability submission · format: .onnx
Cyfra Tech Solutions — Roman Arce Brán (Costa Rica) · 2026-07-27
⚠️ Los archivos
.onnxde este repositorio son maliciosos a propósito. Abrirlos contrtexec/polygraphy/tensorrt.OnnxParsercorrompe el heap del proceso. Correlos solo en un entorno descartable.
Severidad: High — heap metadata corruption confirmada, exploit chain no demostrada. No se reclama RCE.
1. Qué es
convertAxis() (importerUtils.cpp:306-317) valida el eje de un nodo ONNX con una cota
superior inclusiva (axis <= nbDims). Un .onnx que declara axis == rank(input) pasa
la validación y después se usa como índice de escritura en un std::vector de
exactamente rank elementos ⇒ escritura fuera de rango de 8 bytes (one-past-the-end).
- Offset controlado por el atacante:
offset = rank × 8bytes (ventana real 8…64 B, porqueDims::MAX_DIMS == 8). - Valor controlado por el atacante: en la rama
Resize/sizesel qword escrito es elint64crudo del initializersizesdel archivo. - Silencioso en 5 de 8 casos:
parse()devuelveTrue,nerrors=0, la red se construye, el proceso sale conrc=0— y el heap quedó corrupto.
Sinks (misma causa raíz, mismo fix):
| sink | línea | tamaño del write | valor |
|---|---|---|---|
Resize / sizes |
onnxOpImporters.cpp:5345 |
8 B | int64 arbitrario del archivo |
Resize / scales |
onnxOpImporters.cpp:5417 |
4 B | float arbitrario del archivo |
Split |
onnxOpImporters.cpp:6354 |
8 B | fijo (= rank); solo offset controlado |
Versión afectada: onnx/onnx-tensorrt commit
7c51a63a719180eb5160c874c111746f3fb46a6b (ONNX-TRT 11.1 GA, HEAD de main al
2026-07-27) · binario libnvonnxparser.so.11 del wheel tensorrt_cu13==11.1.0.106.
2. Reproducción mínima — SIN NINGÚN TOOLING
Ni ASAN, ni LD_PRELOAD, ni build local. Wheel oficial de NVIDIA:
pip install tensorrt-cu13==11.1.0.106
python run_official.py sweep/s2_rank03.onnx
double free or corruption (out)
Aborted (core dumped)
$ echo $?
134
rc=134: glibc aborta dentro del binario oficial de NVIDIA al parsear un .onnx de
206 bytes. Requiere una GPU NVIDIA visible (TensorRT crea el builder antes de parsear).
Y el caso silencioso, mismo comando:
python run_official.py poc_s2_resize_sizes.onnx
# PARSE_OK=True nerrors=0 nlayers=1 errors=[] -> rc=0, con 8 B escritos fuera de rango
3. Barrido de los 8 ranks (evidencia primaria)
usable predicho desde la aritmética de chunks de glibc antes de medir, y después
medido con malloc_usable_size():
| rank | alloc (B) | offset OOB (B) | usable predicho |
usable medido |
resultado en el wheel oficial |
|---|---|---|---|---|---|
| 1 | 8 | 8 | 24 | 24 | rc=0 PARSE_OK=True — silencioso |
| 2 | 16 | 16 | 24 | 24 | rc=0 PARSE_OK=True — silencioso |
| 3 | 24 | 24 | 24 | (aborta antes del free) |
rc=134 double free or corruption (out) |
| 4 | 32 | 32 | 40 | 40 | rc=0 PARSE_OK=True — silencioso |
| 5 | 40 | 40 | 40 | 40 | rc=134 double free or corruption (out), tras PARSE_OK=True |
| 6 | 48 | 48 | 56 | 56 | rc=0 PARSE_OK=True — silencioso |
| 7 | 56 | 56 | 56 | (aborta antes del free) |
rc=134 malloc(): corrupted top size |
| 8 | 64 | 64 | 72 | 72 | rc=0 PARSE_OK=True — silencioso |
8/8 coinciden con la predicción. Salida cruda: usable-size-witness.txt.
4. Contenido del repositorio
| archivo | qué es |
|---|---|
poc_s2_resize_sizes.onnx (214 B) |
sink principal :5345, rank=4 — silencioso |
poc_s1_split_axis_eq_rank.onnx (179 B) |
sink Split :6354 — silencioso |
poc_s3_resize_scales.onnx (211 B) |
sink Resize/scales :5417 — silencioso |
sweep/s2_rank01..08.onnx |
barrido de offsets 8…64 B (§3) |
sweep/s2_rank09..16.onnx |
control negativo: TensorRT los rechaza en importInput (Dims::MAX_DIMS==8) |
valuectl/*.onnx |
mismo rank, distinto int64 — prueba el control de contenido |
forged/*.onnx |
rank=3 con el campo size del chunk vecino forjado (0x21 es aceptado, rc=0) |
gen_poc.py, gen_value_control.py |
generadores reproducibles (solo stdlib, protobuf a mano) |
verify_poc.py |
decodifica y muestra el contenido de un .onnx del bundle |
run_official.py |
runner sobre el wheel oficial de NVIDIA (sin instrumentación) |
oob_witness.c |
interpositor malloc/free (LD_PRELOAD) — prueba de contenido |
parse_onnx.cpp |
harness ASAN contra el clone de onnx-tensorrt en 7c51a63a |
ASAN-witness.txt |
trazas ASAN completas de los 3 sinks |
offset-content-control.txt |
bitácora de control de offset/contenido y forjado de metadata |
usable-size-witness.txt |
malloc_usable_size medido vs offset predicho |
RUN.md |
instrucciones completas de reproducción |
Regenerar los PoC desde cero
python gen_poc.py # los 3 sinks + sweep/
python gen_value_control.py # valuectl/ + forged/
Testigo de contenido (opcional, prueba qué byte quedó escrito)
gcc -O1 -fPIC -shared -o liboobwitness.so oob_witness.c -ldl
CYFRA_N=32 CYFRA_MARK=0x4141414141414141 LD_PRELOAD=./liboobwitness.so \
python run_official.py sweep/s2_rank04.onnx
[CYFRA-WITNESS] alloc=0x... size=32 B | malloc_usable_size=40 B | offset OOB=32 B
=> dentro del slack del chunk (silencioso) | in-bounds [3]=0x0000000000000001
| OOB @+32 B (indice 4) = 0x4141414141414141 == valor del .onnx
PARSE_OK=True nerrors=0 nlayers=1 errors=[]
5. Lo que NO se demostró (honestidad)
- No hay RCE ni cadena de explotación; no se groomeó una víctima de heap.
- No es un overflow "unbounded": son exactamente 8 bytes (4 en
scales), una vez por nodo. - No se demostró escritura sobre los datos de otro objeto vivo (el qword cae en el slack del propio chunk o sobre el header del vecino).
- Offset acotado a 8…64 B; valor de 64 bits completo solo en la rama
sizes. - Medido en glibc 2.42 / x86-64 Linux. Otros allocators repartirán distinto el silencio y el aborto; la escritura fuera de rango es independiente del allocator (probada con ASAN).
6. Divulgación
Reportado a huntr (Protect AI) — Model File Vulnerabilities, formato .onnx, bajo
divulgación coordinada. Acceso al repositorio concedido a protectai-bot.
Reporte a NVIDIA PSIRT a continuación del triage de huntr.
Crédito: Cyfra Tech Solutions (Roman Arce Brán), Costa Rica.