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.

TensorRT-OSS β€” RPROIPlugin::deserialize out-of-bounds heap read (no upfront length validation)

Target: NVIDIA TensorRT OSS (NVIDIA/TensorRT) Component: plugin/nvFasterRCNN/nvFasterRCNNPlugin.cpp β€” RPROIPlugin::deserialize(int8_t const* data, size_t length) Version tested against: TensorRT-OSS 11.1.0.106 (git a892d22) Class: CWE-125 Out-of-bounds Read (heap) during plugin/engine deserialization Impact: Loading a crafted/truncated serialized engine or plugin field drives out-of-bounds heap reads before any length check runs β€” DoS (crash) and potential information disclosure into deserialized plugin state.


Root cause

RPROIPlugin::deserialize reads a full RPROIParams struct and then seven scalar fields and two variable-length anchor arrays before validating that the input buffer is large enough. The only guard, PLUGIN_VALIDATE(d == data + length), runs after every read has already dereferenced memory.

plugin/nvFasterRCNN/nvFasterRCNNPlugin.cpp (lines 84–100):

RPROIPlugin::RPROIPlugin(void const* data, size_t length)
{
    // ...
    auto const* d{reinterpret_cast<int8_t const*>(data)};
    params = *reinterpret_cast<RPROIParams const*>(d);   // <-- 40-byte read, NO length check
    d += sizeof(RPROIParams);
    A = read<int32_t>(d);
    C = read<int32_t>(d);
    H = read<int32_t>(d);
    W = read<int32_t>(d);
    mInFeatureType  = read<DataType>(d);
    mOutFeatureType = read<DataType>(d);
    mInFeatureLayout = read<DLayout_t>(d);
    copyToHost(d, params.anchorsRatioCount);   // <-- element count from attacker-controlled struct
    // ...
    copyToHost(d, params.anchorsScaleCount);   // <-- element count from attacker-controlled struct
    // ...
    PLUGIN_VALIDATE(d == data + length);        // <-- length checked TOO LATE
}

The very first statement, params = *reinterpret_cast<RPROIParams const*>(d), unconditionally reads sizeof(RPROIParams) (40 bytes) from data. If the serialized blob is shorter than 40 bytes, this reads past the allocation immediately. It then advances d and performs seven unbounded read<T>(d) scalar reads, followed by two copyToHost(d, params.anchorsRatioCount) / copyToHost(d, params.anchorsScaleCount) reads whose element counts come from the struct that was just deserialized from attacker-controlled bytes.

read<>() is a bare memcpy with no bounds check β€” plugin/common/plugin.h (lines 100–108):

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));
    buffer += sizeof(OutType);
    return val;
}

deserialize() is reachable through the RPROIPlugin(void const* data, size_t length) constructor and the creator's deserializePlugin(), both invoked with attacker-controlled bytes when a serialized engine / plugin field is loaded.

Contrast (correct pattern in a sibling plugin): the legacy ROIAlign plugin validates PLUGIN_VALIDATE(length == kSERIALIZATION_SIZE) up front before touching the buffer. RPROIPlugin has no equivalent guard.


PoC

Faithful host repro: poc/rproi_deser_oob.cpp, built with g++ -std=c++17 -fsanitize=address.

  • RPROIParams is copied verbatim from include/NvInferPluginUtils.h (lines 67–80).
  • The read<> template is copied verbatim from plugin/common/plugin.h (lines 100–108).
  • The deserialize body replays nvFasterRCNNPlugin.cpp lines 84–100 exactly.

A full CUDA/GPU shared-library build is infeasible on the test VM (no nvcc / CUDA toolkit), but the OOB read is pure host code hit before any CUDA call, so the repro exercises the identical read sequence, struct layout, and read<> primitive of the real target. copyToHost (a cudaMemcpy in the real code) is modeled as a host-side read of count * sizeof(float) bytes from d, exercising the same attacker-controlled length.

Attack input (mode 0, default): an 8-byte malloc'd buffer passed with length = 8 (a truncated serialized plugin blob). sizeof(RPROIParams) = 40, so the first line reads 32 bytes past the allocation.

Negative control (mode 1): a well-formed 88-byte buffer with valid small anchor counts deserializes cleanly and PLUGIN_VALIDATE passes.

Build & run:

g++ -std=c++17 -fsanitize=address -g -o rproi_deser_oob rproi_deser_oob.cpp
./rproi_deser_oob 1   # negative control (well-formed)
./rproi_deser_oob 0   # attack (truncated 8-byte blob)  -> ASan heap-buffer-overflow

Captured evidence (verbatim)

########## NEGATIVE CONTROL (well-formed) ##########
[negctrl] well-formed length=88
[negctrl] deserialize OK, no OOB

########## ATTACK (truncated 8-byte blob) ##########
[attack] sizeof(RPROIParams)=40, serialized length=8
=================================================================
==319357==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b99d0fe0010 at pc 0x5590bce16a7a bp 0x7ffe0fc0f520 sp 0x7ffe0fc0f518
READ of size 40 at 0x7b99d0fe0010 thread T0
    #0 0x5590bce16a79 in RPROIPluginRepro::deserialize(signed char const*, unsigned long) rproi_deser_oob.cpp:65
    #1 0x5590bce16514 in main rproi_deser_oob.cpp:103
0x7b99d0fe0018 is located 0 bytes after 8-byte region [0x7b99d0fe0010,0x7b99d0fe0018)
allocated by thread T0 here:
    #0 0x7f79d232435f in malloc
    #1 0x5590bce163fe in main rproi_deser_oob.cpp:99
SUMMARY: AddressSanitizer: heap-buffer-overflow rproi_deser_oob.cpp:65 in RPROIPluginRepro::deserialize(signed char const*, unsigned long)

(Re-run on this machine reproduces the same ASan heap-buffer-overflow READ of size 40 "0 bytes after 8-byte region" signature; the PC/address values differ per run/ASLR.)


Suggested fix

Validate the buffer length before the first read, mirroring the ROIAlign plugin:

// minimum fixed-size header = sizeof(RPROIParams) + 4*int32 + 2*DataType + DLayout_t
PLUGIN_VALIDATE(length >= sizeof(RPROIParams) + 4*sizeof(int32_t)
                          + 2*sizeof(DataType) + sizeof(DLayout_t));

and, after reading params, verify that params.anchorsRatioCount / params.anchorsScaleCount are non-negative and that the remaining length covers (anchorsRatioCount + anchorsScaleCount) * sizeof(float) before the copyToHost reads.


Dedup note

This is a distinct sink from other TensorRT plugin deserialize OOB findings already filed (EmbLayerNorm, DecodeBbox3D, PriorBox, Region, FlattenConcat, PillarScatter). Each is a separate plugin file with its own deserialize() body and its own missing-length-guard root cause; this report concerns RPROIPlugin in plugin/nvFasterRCNN/nvFasterRCNNPlugin.cpp specifically, characterized by an unchecked full-struct *reinterpret_cast<RPROIParams const*>(d) read as the first operation plus attacker-controlled anchor-count array reads. No CVE is known to cover this specific plugin's deserialize path at the tested commit.

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