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's native-engine plugin deserialization

Plugin: CustomEmbLayerNormPluginDynamic (legacy versions "2" / "3") β€” embLayerNormVarSeqlenPluginLegacy.cpp

Target: NVIDIA/TensorRT (TensorRT-OSS plugin library, plugin/embLayerNormPlugin/) Compiled into libnvinfer_plugin.so shipped with every TensorRT release.

Verified commit: a892d22267d9cd2dedc1a0893e6892ac901f6d3d (2026-07-07, current HEAD at time of testing)

Dedup note: This is a distinct bug from the recently patched CVE-2026-24188 (a vaguely described "out-of-bounds write" in "tensor processing routines," fixed in TensorRT 10.16.1). CVE-2026-24188's description does not mention this plugin or this code path, and this bug is present in the current HEAD, which post-dates that patch. This finding is also independent of and unrelated to any prior onnxtrt-* / ONNX-importer findings β€” it is in the native engine (.plan/.engine) plugin deserializer, not the ONNX importer.

Root cause

TensorRT's native engine format (.plan/.engine) supports custom IPluginV2 layers whose plugin-specific serialized data is deserialized via IPluginCreator::deserializePlugin(name, serialData, serialLength) during IRuntime::deserializeCudaEngine().

For the legacy BERT plugin CustomEmbLayerNormPluginDynamic, the deserializing constructor

EmbLayerNormVarSeqlenPluginLegacyBase::EmbLayerNormVarSeqlenPluginLegacyBase(
    std::string const& name, void const* data, size_t length)

(plugin/embLayerNormPlugin/embLayerNormVarSeqlenPluginLegacy.cpp:77-108) first reads 6 fixed scalar fields (mType, mLd, mWordVocabSize, mPosVocabSize, mTokVocabSize, mMaskType) via deserialize_value() (plugin/common/serialize.hpp). Each of these scalar reads is correctly bounds-checked against the remaining length and throws std::runtime_error if too short.

Immediately afterward, the constructor calls WeightsWithOwnership::convertAndCopy(char const*& srcBuf, size_t count, DataType type) five times β€” for mBeta, mGamma, mWordEmb, mPosEmb, mTokEmb β€” passing count derived directly from the just-deserialized, fully attacker-controlled size_t fields (mLd, mLd*mWordVocabSize, mLd*mPosVocabSize, mLd*mTokVocabSize).

// plugin/common/bertCommon.h:434-444
void convertAndCopy(char const*& srcBuf, size_t count, DataType type)
{
    ...
    auto const nbBytes = count * getElementSize(type);
    ...
    std::copy_n(srcBuf, nbBytes, destBuf);   // <-- no check against remaining `length`
    srcBuf += nbBytes;
}

convertAndCopy computes nbBytes = count * getElementSize(type) and unconditionally does std::copy_n(srcBuf, nbBytes, destBuf). It never consults the remaining length value that deserialize_value was tracking, so there is no check that nbBytes bytes are actually present in the plugin's serialized blob.

An attacker who crafts a .plan/.engine file containing a CustomEmbLayerNormPluginDynamic layer whose plugin-data blob declares an inflated mLd/mWordVocabSize/etc. but supplies a short physical byte length triggers a massive out-of-bounds heap read on the very first convertAndCopy call β€” before any CUDA/GPU code runs.

The try/catch wrapped around deserializePlugin() by callers only catches C++ exceptions (i.e. the earlier, correctly bounds-checked scalar reads); it does not protect against this raw OOB memory access. In a non-instrumented production build this manifests as a crash (SIGSEGV, unhandled by the try/catch) or as disclosure of adjacent heap memory copied into the plugin's weight buffers β€” buffers that are subsequently uploaded to the GPU and used in inference, i.e. exposure of unrelated process memory content to whatever consumes the plugin's outputs.

PoC

Standalone C++ harness β€” no NVIDIA GPU, no CUDA toolkit, no proprietary libnvinfer.so required β€” built entirely from verbatim, unmodified source extracted from the Apache-2.0-licensed plugin/ tree of github.com/NVIDIA/TensorRT:

  1. plugin/common/serialize.hpp copied unmodified (deserialize_value / Serializer<T>).
  2. getElementSize / getWeightsSize copied verbatim from plugin/common/bertCommon.h.
  3. WeightsWithOwnership::convertAndCopy(char const*&, size_t, DataType) copied verbatim from bertCommon.h:434-444.
  4. The deserializing constructor body copied verbatim from embLayerNormVarSeqlenPluginLegacy.cpp:77-108 β€” only the trailing CUDA copyToDevice() calls (which run strictly after the vulnerable reads, and are unreachable once ASan aborts) are stubbed out.

main() crafts a malicious 40-byte plugin blob β€” exactly what would be embedded inline in a crafted .plan/.engine file's IPluginV2Layer entry for a CustomEmbLayerNormPluginDynamic layer β€” setting mLd = 2^20 while supplying zero payload bytes, then invokes the real constructor exactly as IPluginCreator::deserializePlugin() does when IRuntime::deserializeCudaEngine() loads such an engine.

Built with clang++ -std=c++17 -O0 -g -fsanitize=address,undefined.

A negative control (negative_control.cpp, exercising the same real code paths) feeds a well-formed 168-byte blob whose header fields accurately describe the physically-present payload. It deserializes cleanly with zero ASan reports and exit code 0, confirming the crash is specific to the header/length mismatch an attacker controls, not general harness instability.

Files

  • poc/harness.cpp β€” malicious-blob PoC
  • poc/negative_control.cpp β€” well-formed-blob negative control
  • poc/serialize.hpp β€” verbatim upstream header used by both
  • Upstream clone used for extraction: TensorRT-OSS commit a892d22267d9cd2dedc1a0893e6892ac901f6d3d (2026-07-07)

Captured evidence (verbatim)

Malicious blob β€” triggers heap-buffer-overflow

==240688==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c03debe0078 at pc 0x55c221b43b57 bp 0x7fff7116b4f0 sp 0x7fff7116acb0
READ of size 4194304 at 0x7c03debe0078 thread T0
    #0 0x55c221b43b56 in __asan_memmove
    #1 0x55c221b8d4ee in char* std::__copy_n_a<char const*, long, char*>(...) stl_algobase.h:551:8
    #2 0x55c221b8d30c in char* std::copy_n<char const*, long, char*>(...) stl_algo.h:698:20
    #3 0x55c221b8cfa0 in WeightsWithOwnership::convertAndCopy(char const*&, unsigned long, nvinfer1::DataType) harness.cpp:183:9
    #4 0x55c221b8c61e in EmbLayerNormVarSeqlenPluginLegacyBase::EmbLayerNormVarSeqlenPluginLegacyBase(std::string const&, void const*, unsigned long) harness.cpp:222:15
    #5 0x55c221b8b499 in main harness.cpp:280:43
0x7c03debe0078 is located 0 bytes after 40-byte region [0x7c03debe0050,0x7c03debe0078)
allocated by thread T0 here:
    #0 ... operator new[](unsigned long)
    #1 ... in main harness.cpp:260:18
SUMMARY: AddressSanitizer: heap-buffer-overflow ... in __asan_memmove
==240688==ABORTING

Exit code 1; reproduced identically on a second independent run.

Negative control β€” well-formed 168-byte blob (header=40 + payload=128)

[*] NEGATIVE CONTROL: well-formed plugin blob, 168 bytes total (header=40 + payload=128),
[*] header fields accurately describe the physically-present payload. Expect NO ASan report.
[+] Negative control PASSED: deserialized 168-byte well-formed blob with no memory-safety error.

Exit code 0, no ASan report.

Impact

Loading an attacker-supplied/attacker-modified .plan/.engine file containing a crafted CustomEmbLayerNormPluginDynamic layer causes TensorRT's engine deserialization to perform a large out-of-bounds heap read immediately upon IRuntime::deserializeCudaEngine(), prior to any GPU/CUDA execution and unprotected by the surrounding exception handling. Depending on heap layout this results in a crash (denial of service) or in adjacent process heap memory being copied into the plugin's weight tensors, which are then used in subsequent inference β€” i.e., a potential information-disclosure primitive via a nominally "just a model file" input.

Suggested fix

convertAndCopy (and the calling constructor) must validate that nbBytes (and the cumulative total across all five weight copies) does not exceed the remaining unconsumed bytes of the original length passed to the plugin's deserializing constructor, throwing (as the scalar deserialize_value reads already do) rather than performing an unchecked std::copy_n.

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