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.

Heap-buffer-overflow OOB read in TensorRT-OSS FlattenConcat_TRT plugin deserialize constructor (attacker-controlled mNumInputs element count)

Target

  • Project: NVIDIA TensorRT (TensorRT-OSS plugin library, compiled into libnvinfer_plugin.so shipped with every TensorRT release)
  • Repo: https://github.com/NVIDIA/TensorRT
  • Commit: a892d22267d9cd2dedc1a0893e6892ac901f6d3d (2026-07-07), VERSION file 11.1.0.106, HEAD of main
  • Vulnerable file: plugin/flattenConcat/flattenConcat.cpp β€” FlattenConcat::FlattenConcat(void const* data, size_t length), lines 60–79
  • Supporting code: plugin/common/plugin.h read<>() (lines 100–108)
  • Class: CWE-125 Out-of-bounds Read (heap-buffer-overflow READ)

Trigger path

IRuntime::deserializeCudaEngine() parses an attacker-supplied .plan/.engine file that contains an IPluginV2Layer of type "FlattenConcat_TRT" (registered via REGISTER_TENSORRT_PLUGIN). The engine reader hands the plugin's inline serialized-data blob verbatim to the plugin creator:

FlattenConcatPluginCreator::deserializePlugin(name, serialData, serialLength)
  -> new FlattenConcat(serialData, serialLength)
     -> FlattenConcat::FlattenConcat(void const* data, size_t length)   <-- vulnerable

serialData / serialLength come verbatim from the attacker-controlled plan file.

Root cause

The constructor reads a 32-bit element count mNumInputs directly out of the attacker blob and then loops that many times pulling values via the unchecked read<> helper. The only length check runs at the very end of the constructor β€” after every read loop has already executed:

// plugin/flattenConcat/flattenConcat.cpp:60-79  (VERBATIM)
FlattenConcat::FlattenConcat(void const* data, size_t length)
{
    char const* d = static_cast<char const*>(data);
    char const* const a = d;
    mIgnoreBatch = read<bool>(d);
    mConcatAxisID = read<int32_t>(d);
    PLUGIN_VALIDATE(mConcatAxisID >= 1 && mConcatAxisID <= 3);
    mOutputConcatAxis = read<int32_t>(d);
    mNumInputs = read<int32_t>(d);                    // <-- attacker-controlled count

    mInputConcatAxis.resize(mNumInputs);
    std::for_each(mInputConcatAxis.begin(), mInputConcatAxis.end(),
                  [&](int32_t& inp) { inp = read<int32_t>(d); });   // OOB read loop

    mCHW = read<nvinfer1::Dims3>(d);

    mCopySize.resize(mNumInputs);
    std::for_each(mCopySize.begin(), mCopySize.end(),
                  [&](size_t& inp) { inp = read<size_t>(d); });     // 2nd OOB read loop

    PLUGIN_VALIDATE(d == a + length);                 // length check AFTER OOB reads
}
// plugin/common/plugin.h:100-108  (VERBATIM)
template <typename OutType, typename BufferType>
OutType read(BufferType const*& buffer)
{
    static_assert(sizeof(BufferType) == 1, "BufferType must be a 1 byte type.");
    OutType val{};
    std::memcpy(&val, static_cast<void const*>(buffer), sizeof(OutType));  // no bound
    buffer += sizeof(OutType);
    return val;
}

mNumInputs is never validated against the number of bytes physically remaining in the blob. read<int32_t>() is an unconditional 4-byte memcpy that advances the cursor with no bound check. The first std::for_each reads mNumInputs consecutive int32 values; a second std::for_each reads mNumInputs size_t values into mCopySize the same way. The sole PLUGIN_VALIDATE(d == a + length) runs only after both loops complete β€” i.e. after the out-of-bounds reads have already occurred. By inflating mNumInputs while supplying a blob that only contains the 13-byte header, the read<int32_t> loop walks off the end of the heap allocation holding the plugin blob β†’ heap-buffer-overflow READ.

PoC

A faithful standalone ASan harness reproduces the bug using the real, unmodified TensorRT-OSS code for read<> (plugin/common/plugin.h) and the verbatim body of the FlattenConcat deserialize constructor (flattenConcat.cpp lines 60–79). Field writes are retargeted to layout-compatible static mirrors so the verbatim constructor body compiles standalone; every read<> call is byte-for-byte the real code.

  • Harness: flattenConcat_harness.cpp
  • Negative control: flattenConcat_negctl.cpp

The harness heap-allocates a 13-byte blob containing only the header (mIgnoreBatch=0, mConcatAxisID=1 to pass the [1,3] validate, mOutputConcatAxis=3, mNumInputs=100000) with no per-input payload. Calling the constructor makes the mInputConcatAxis for_each read loop walk read<int32_t>() off the end of the 13-byte allocation.

Build & run

clang++ -std=c++17 -O0 -g -fsanitize=address -fno-omit-frame-pointer \
    flattenConcat_harness.cpp -o flattenConcat_asan
./flattenConcat_asan

clang++ -std=c++17 -O0 -g -fsanitize=address -fno-omit-frame-pointer \
    flattenConcat_negctl.cpp -o flattenConcat_negctl
./flattenConcat_negctl

Captured evidence (verbatim)

############ POSITIVE (attack) ############
[*] blob heap alloc = 13 bytes; mNumInputs (attacker) = 100000
[*] concat-axis loop will read 100000 int32 (400000 bytes) from a 13-byte blob
[*] calling FlattenConcat::FlattenConcat(serialData, serialLength) ...
=================================================================
==305646==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b84fbfe001d at pc 0x561d9a6de68f bp 0x7ffeed94b900 sp 0x7ffeed94b0c0
READ of size 4 at 0x7b84fbfe001d thread T0
    #0 0x561d9a6de68e in __asan_memcpy
    #1 0x561d9a727545 in int nvinfer1::plugin::read<int, char>(char const*&) flattenConcat_harness.cpp:68:5
    #2 0x561d9a727049 in deserializeFlattenConcat(void const*, unsigned long)::$_0::operator()(int&) const flattenConcat_harness.cpp:114:95
    #3 0x561d9a726c7a in deserializeFlattenConcat(void const*, unsigned long)::$_0 std::for_each<...>(...) stl_algo.h:3798:2
    #4 0x561d9a7268b8 in deserializeFlattenConcat(void const*, unsigned long) flattenConcat_harness.cpp:114:5
    #5 0x561d9a72632b in main flattenConcat_harness.cpp:159:9

0x7b84fbfe001d is located 0 bytes after 13-byte region [0x7b84fbfe0010,0x7b84fbfe001d)
allocated by thread T0 here:
    #0 0x561d9a6e0a38 in malloc
    #1 0x561d9a7261f4 in main flattenConcat_harness.cpp:141:43

SUMMARY: AddressSanitizer: heap-buffer-overflow in __asan_memcpy
==305646==ABORTING

############ NEGATIVE CONTROL ############
[*] well-formed blob = 73 bytes; mNumInputs = 2 (matches payload)
[*] deserialize completed cleanly, no ASan report (expected)

The negative control feeds a well-formed 73-byte blob whose mNumInputs=2 matches the actual payload and whose length is exact: the constructor completes cleanly, no ASan report, final PLUGIN_VALIDATE(d == a + length) passes. This isolates the defect to the count/length mismatch, not an artifact of the harness.

Impact

An attacker who supplies a crafted TensorRT engine/plan file (a common untrusted input: models are routinely downloaded, shared, and served) triggers an out-of-bounds heap read during deserializeCudaEngine(). Depending on heap layout this is a crash (DoS) or an information disclosure of adjacent heap memory into the plugin's mInputConcatAxis / mCopySize arrays, which may subsequently influence execution. The second loop reading mNumInputs size_t values compounds the over-read.

Dedup

  • No public CVE currently describes this FlattenConcat_TRT deserialize-constructor count/length mismatch.
  • Distinct plugin, file, and function from prior findings in this audit: the PriorBox_TRT (priorBoxPlugin.cpp), DecodeBbox3D, Region, and embLayerNormPlugin TensorRT findings. This is the FlattenConcat plugin's constructor, driven by an unchecked serialized mNumInputs count.
  • Same root-cause family as the other TensorRT plugin deserialize OOB reads (unbounded read<> loop driven by an attacker-controlled serialized count with the length check placed after the loops), but a separate plugin and code path.

Suggested fix

Before resize/loop, validate that the requested bytes remain in the blob, e.g. PLUGIN_VALIDATE(mNumInputs >= 0 && (d - a) + mNumInputs * (sizeof(int32_t) + sizeof(size_t)) + sizeof(nvinfer1::Dims3) <= length); so the length check precedes any read<>() in the loops.

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