You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

These files are a security-research proof-of-concept that triggers memory corruption in NVIDIA TensorRT's ONNX parser. They are NOT functional models. Do not load them outside an isolated, disposable sandbox. Access is granted for coordinated-disclosure review only.

Log in or Sign Up to review the conditions and access this model content.

Security research PoC β€” gated; access granted to protectai-bot for review

This repository contains malicious ONNX model files used as a proof-of-concept for a memory-corruption vulnerability in NVIDIA TensorRT's ONNX parser. It is uploaded as a gated repo for coordinated vulnerability disclosure through the huntr.com (ProtectAI) AI/ML bounty program. Review access has been granted to protectai-bot.

This is not a usable machine-learning model. Loading these files is the exploit.


⚠️ What these files are β€” read before doing anything

The two .onnx files in this repository are crafted to corrupt memory inside TensorRT's ONNX import path. When TensorRT's OnnxParser (or trtexec) parses one of these files with the standard plugin library registered (initLibNvInferPlugins), control reaches the deprecated-but-still-registered RPROI_TRT plugin's createPlugin routine, which reads attacker-controlled element counts with no upper bound and walks off the end of a heap buffer.

Do NOT load these files outside an isolated, disposable sandbox. Treat them as live malware against the TensorRT process:

  • Parsing the OOB-read file reliably reads past a heap allocation. Under a normal release build this is an over-read that may segfault the process or silently leak adjacent heap contents; under an AddressSanitizer build it aborts immediately.
  • The integer-overflow file is designed to drive an undersized allocation followed by an out-of-bounds write (analytical leg β€” see caveats). Do not run it against anything you care about.

Run only inside a throwaway container/VM with no sensitive data and no network access you care about.


The two model files

File Crafted attributes What it triggers
rproi_oob_read.onnx RPROI_TRT node with anchorsRatioCount = 64 (INT) but anchorsRatios = [0.5] (1 float) Heap out-of-bounds READ (CWE-125) in RPROIPluginCreator::createPlugin. The loop runs 64 iterations over a 1-element float buffer. This is the dynamically ASAN-confirmed leg, crashing at nvFasterRCNNPlugin.cpp:482.
rproi_intoverflow.onnx RPROI_TRT node with anchorsRatioCount = 1, anchorsScaleCount = 1073741825 (2³⁰+1) Integer-overflow β†’ out-of-bounds WRITE (CWE-190 β†’ CWE-787). The unbounded counts feed 4 * anchorsRatioCount * anchorsScaleCount * sizeof(float) into the device allocation as a signed-int product that overflows to a small positive value, yielding an undersized buffer that is then overrun. This leg is static/analytical β€” it is NOT dynamically reproduced in this research (see caveats).

Both files were produced by poc/craft_rproi_onnx.py (included). The core trick is that an ONNX FLOATS attribute's element count and the separate INT count attribute are set independently by the attacker, and the parser never reconciles them.


Root cause (source, with file:line)

Component: plugin/nvFasterRCNN/nvFasterRCNNPlugin.cpp, function RPROIPluginCreator::createPlugin.

The scalar count is read with only a type check, no range check:

// nvFasterRCNNPlugin.cpp:453
PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
params.anchorsRatioCount = *(static_cast<int32_t const*>(fields[i].data)); // attacker INT, unbounded

It is then used as the loop bound over an independently-sized float buffer:

// nvFasterRCNNPlugin.cpp:478-484
anchorsRatios.reserve(params.anchorsRatioCount);
float const* ratios = static_cast<float const*>(fields[i].data); // buffer holds fields[i].length floats
for (int32_t j = 0; j < params.anchorsRatioCount; ++j)            // bound = attacker INT
{
    anchorsRatios.push_back(*ratios);   // line 482: OOB READ when anchorsRatioCount > fields[i].length
    ratios++;
}

fields[i].length is the real ONNX element count (set by copyField, onnxOpImporters.cpp:7263-7270), and the FallbackPluginImporter path that builds the PluginFieldCollection never clamps it against the creator-declared length of 1. So anchorsRatioCount = 64 over a 1-float buffer reads 63 floats past the allocation. The identical pattern exists for anchorsScaleCount / anchorsScales (lines 486-496), and both unbounded counts feed the unchecked device-allocation multiplication (4 * anchorsRatioCount * anchorsScaleCount * sizeof(float), near line 43) that overflows.

A TODO in the same file (lines 384-386) notes the size information for the float arrays was "not available at this point" β€” the bound was never enforced.


Reproduction

There are two ways to reproduce. (B) is the cleanest deterministic evidence and is what produced the quoted ASAN crash below.

(A) End-to-end through the real TensorRT ONNX parser

createPlugin runs during the CPU-side ONNX import, before any GPU kernel is launched, so reaching the bug does not require a full engine build.

pip install tensorrt          # provides the real OnnxParser + plugin library
# For a deterministic ASAN stack trace, build TensorRT OSS with -DSANITIZE=address
# and use that libnvinfer_plugin / trtexec.
python poc/reproduce_tensorrt.py rproi_oob_read.onnx

reproduce_tensorrt.py calls trt.init_libnvinfer_plugins(logger, ""), builds an OnnxParser, and calls parser.parse() on the crafted file β€” createPlugin is invoked during import. Under an ASAN-instrumented OSS build you get a heap-buffer-overflow READ originating in nvFasterRCNNPlugin.cpp. Under a stock release build, a small over-read (e.g. anchorsRatioCount=64) may silently read adjacent heap; a large count makes the loop walk into unmapped memory and reproducibly SIGSEGV the TensorRT process. Equivalently: trtexec --onnx=rproi_oob_read.onnx.

(B) Source-level ASAN reproduction of the genuine translation unit

This compiles NVIDIA's own nvFasterRCNNPlugin.cpp under AddressSanitizer using small CUDA-header shims (CUDA is only needed to satisfy includes; every stubbed symbol is reached only after the OOB, so the vulnerable loop is unmodified NVIDIA code). It is provided under poc/source_asan/:

cd poc/source_asan
./build_realtu.sh      # compiles the genuine nvFasterRCNNPlugin.cpp + checkMacros TUs
                       # under -fsanitize=address, then runs the attack + negative control

Expected output (attack case): an AddressSanitizer heap-buffer-overflow READ of size 4 whose crashing frame is:

#3 nvinfer1::plugin::RPROIPluginCreator::createPlugin(...)
       refs/tensorrt/plugin/nvFasterRCNN/nvFasterRCNNPlugin.cpp:482

The negative control (anchorsRatioCount == anchorsRatios length == 1) exits cleanly with no ASAN error, proving the crash is specific to the count-vs-length mismatch and not a build artifact.


Quoted excerpt β€” genuine-TU ASAN crash (verbatim)

From the real-translation-unit attack run (anchorsRatioCount=64, buffer_len=1):

==779==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000014 ...
READ of size 4 at 0x502000000014 thread T0
    #2 std::vector<float, std::allocator<float> >::push_back(float const&)
    #3 nvinfer1::plugin::RPROIPluginCreator::createPlugin(char const*, nvinfer1::PluginFieldCollection const*)
        refs/tensorrt/plugin/nvFasterRCNN/nvFasterRCNNPlugin.cpp:482

0x502000000014 is located 0 bytes to the right of 4-byte region [0x502000000010,0x502000000014)
allocated by thread T0 here:
    #0 operator new[](unsigned long)
==779==ABORTING

The read lands exactly 0 bytes past the new float[1] (4-byte) allocation β€” the first float beyond the buffer β€” at the genuine NVIDIA source line.


Affected versions

TensorRT builds that ship and register the RPROI_TRT plugin. The missing bound was confirmed present across at least TRT 10.13 through 11.0 (HEAD as of 2026-06-02) and is likely present in earlier releases. nvFasterRCNN is marked DEPRECATED in plugin/README.md but remains compiled and unconditionally registered by initLibNvInferPlugins (inferPlugin.cpp:203); no removal has been implemented.

CVSS (CVSS 3.1)

  • OOB read (CWE-125) β€” primary, local model-loading: AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H = 6.1 (Medium). Serving-endpoint vector (auto-parsing untrusted uploads): AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H = 8.2 (High). C:L is an upper bound for potential info-leak and is not demonstrated end-to-end; expect C:N.
  • Integer-overflow OOB write (CWE-190 β†’ CWE-787) β€” primary: AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H = 7.1 (High). Serving-endpoint vector: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H = 9.1 (Critical). Written content is computed anchor geometry, not attacker-controlled bytes β€” a reliable heap-corruption/DoS primitive whose RCE potential is unproven.

CWE

  • CWE-125 β€” Out-of-bounds Read (dynamically confirmed)
  • CWE-190 β€” Integer Overflow or Wraparound β†’ CWE-787 β€” Out-of-bounds Write (static/analytical)

Responsible disclosure

This research follows coordinated disclosure. These files contain no weaponized payload beyond the minimum needed to demonstrate the memory-safety defect, and no exploit chain for code execution is provided. The package is shared with ProtectAI/huntr for triage and onward coordination with the NVIDIA vendor security team. Please do not redistribute the crafted models outside the disclosure process.

Contents of this repo:

README.md                       this model card
poc/
β”œβ”€β”€ craft_rproi_onnx.py         builds the two malicious .onnx files
β”œβ”€β”€ reproduce_tensorrt.py       end-to-end repro via the real TensorRT OnnxParser
└── source_asan/
    └── build_realtu.sh         genuine-TU ASAN reproduction (crash at nvFasterRCNNPlugin.cpp:482)
rproi_oob_read.onnx             OOB read PoC (count=64 vs 1-float buffer)
rproi_intoverflow.onnx          integer-overflow PoC (count = 2^30+1)
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support