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.

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

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

onnx-tensorrt staticSliceImporter out-of-bounds heap READ (CWE-125)

ASan-confirmed proof of concept for an out-of-bounds heap read in the staticSliceImporter function of onnx-tensorrt when parsing a crafted .onnx model whose Slice node has a STARTS initializer longer than its ENDS / AXES / STEPS initializers.

  • Target: github.com/onnx/onnx-tensorrt
  • Pinned commit: 7c51a63a719180eb5160c874c111746f3fb46a6b (ONNX-TRT 11.1 GA, 2026-06-22)
  • File: importerUtils.cpp, function staticSliceImporter
  • Class: CWE-125 Out-of-bounds Read (heap)
  • Impact: heap information disclosure / crash (DoS) at model-parse time

Root cause

In staticSliceImporter (importerUtils.cpp), the loop trip count nbValues is derived from the STARTS tensor length ONLY:

// Get int32 pointer representation of ONNX provided values
int32_t* startVals = static_cast<int32_t*>(inputs.at(1).weights().values);
int32_t* endVals   = static_cast<int32_t*>(inputs.at(2).weights().values);
int32_t* axesVals  = nbInputs > 3 ? static_cast<int32_t*>(inputs.at(3).weights().values) : defaultAxes.data();
int32_t* stepVals  = nbInputs > 4 ? static_cast<int32_t*>(inputs.at(4).weights().values) : defaultSteps.data();
...
auto const nbValues = inputs.at(1).shape().d[0];   // STARTS length only

for (int32_t i = 0; i < nbValues; i++)
{
    auto axesIndex = convertAxes(axesVals[i]);     // reads axesVals[i]
    ...
    int32_t stepSign = stepVals[i] < 0 ? -1 : 0;   // reads stepVals[i]
    starts.d[axesIndex] = convertStarts(startVals[i], ...);
    int32_t modifiedEnds = convertEnds(endVals[i], ...);   // reads endVals[i]
    ...
}

The loop indexes the SEPARATE endVals, axesVals, stepVals heap buffers by the same i up to nbValues - 1. Nothing checks that ENDS / AXES / STEPS each have at least nbValues elements. The only in-loop ONNXTRT_CHECK validates the axis VALUE (axesIndex within nbDims), not buffer LENGTH.

Why the upstream equal-length check does NOT protect this path

DEFINE_BUILTIN_OP_IMPORTER(Slice) (onnxOpImporters.cpp) does contain starts.size() == axes.size() and ends.size() == axes.size() checks, but they sit AFTER the static fast-path early return:

if (isInt32 && isWeightsOrEmpty(1) && isWeightsOrEmpty(2) && isWeightsOrEmpty(3)
    && isWeightsOrEmpty(4) && !isDynamic(data.getDimensions()))
{
    return staticSliceImporter(ctx, node, nodeIdx, inputs, data);  // returns BEFORE the size checks
}
... ONNXTRT_CHECK_NODE(starts.size() == axes.size(), ...)   // never reached on the static path

So when all of starts/ends/axes/steps are INT32 constant initializers and the data tensor has a static shape, execution enters staticSliceImporter with NO equal-length guard. The vulnerability is reachable via a plain crafted model.

Files

  • harness.cpp - faithful standalone ASan harness copying the vulnerable loop verbatim
  • run.sh - builds with clang ASan, runs control (clean) then vulnerable
  • craft_onnx.py- generates slice_oob_read.onnx
  • slice_oob_read.onnx - crafted model: STARTS len 64, ENDS/AXES/STEPS len 1
  • asan.log - captured AddressSanitizer heap-buffer-overflow READ
  • control.out - control run (equal-length buffers) exits cleanly

Reproduce

# clang + AddressSanitizer required
bash run.sh

Control run (starts=ends=axes=steps len 4) completes cleanly, exit 0. Vulnerable run (starts len 64, ends/axes/steps len 1) aborts with:

==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 4 ...
    #0 ... in main harness.cpp:117 (convertAxes(axesVals[i]))
0x... is located 0 bytes after 4-byte region [...]  (the 1-element axes buffer)
SUMMARY: AddressSanitizer: heap-buffer-overflow ... in main

The read fires the moment i exceeds the length of the short ends/axes/steps buffer while nbValues (from STARTS) keeps the loop going.

The crafted model

slice_oob_read.onnx is a single-node graph:

  • input data: FLOAT [8,8,8,8] (static shape -> !isDynamic)
  • Slice node, opset 13, inputs data/starts/ends/axes/steps
  • initializers (all INT32 -> isInt32 fast path):
    • starts shape [64]
    • ends shape [1]
    • axes shape [1]
    • steps shape [1]

Loading this model with onnx-tensorrt takes the static fast path into staticSliceImporter, which loops 64 times over 1-element ends/axes/steps buffers -> OOB heap read.

Remediation

In staticSliceImporter, before the loop, require that STARTS, ENDS, AXES and STEPS all have at least nbValues elements (equivalently, validate equal element counts), e.g.:

auto const nbValues = inputs.at(1).shape().d[0];
ONNXTRT_CHECK_NODE(inputs.at(2).shape().d[0] == nbValues
    && (nbInputs <= 3 || inputs.at(3).shape().d[0] == nbValues)
    && (nbInputs <= 4 || inputs.at(4).shape().d[0] == nbValues),
    "Slice starts/ends/axes/steps must have equal length.",
    node, nodeIdx, ErrorCode::kINVALID_NODE);

Alternatively, move the existing starts.size() == axes.size() / ends.size() == axes.size() checks so they run BEFORE the static fast-path return.

Note on the harness

Building the full onnx-tensorrt importer requires TensorRT, which is not redistributable. This harness copies the staticSliceImporter loop and its helper lambdas VERBATIM and drives them with heap buffers sized exactly to the ONNX initializer lengths (as TensorRT sizes weights().values), so the OOB read is the same one the real importer performs on the crafted model.

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