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 .onnx de este repositorio son maliciosos a propósito. Abrirlos con trtexec / polygraphy / tensorrt.OnnxParser corrompe 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 × 8 bytes (ventana real 8…64 B, porque Dims::MAX_DIMS == 8).
  • Valor controlado por el atacante: en la rama Resize/sizes el qword escrito es el int64 crudo del initializer sizes del archivo.
  • Silencioso en 5 de 8 casos: parse() devuelve True, nerrors=0, la red se construye, el proceso sale con rc=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=4silencioso
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.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support