| # TensorRT regionPlugin β NULL Pointer Dereference |
|
|
|  |
|  |
|  |
|  |
|
|
| ## Target |
|
|
| | Field | Value | |
| |---|---| |
| | **Repository** | https://github.com/NVIDIA/TensorRT | |
| | **Platform** | huntr.com | |
| | **Max payout** | $4,000 | |
| | **Affected file** | `plugin/regionPlugin/regionPlugin.cpp` | |
| | **Affected lines** | `allocateChunk` template (38β42), deserialization ctor (93β227), loop (152β168) | |
|
|
| ## Vulnerability |
|
|
| `allocateChunk<T>()` calls `malloc(count * sizeof(T))` with **no NULL check** on the return value. The `count` parameter (`smTreeTemp->n`) is read directly from an attacker-controlled deserialization buffer with no bounds validation. |
|
|
| Crafting a `.trt` engine file with `softmaxTree.n = INT32_MAX` causes: |
| 1. `malloc(8,589,934,588)` β fails β returns `NULL` |
| 2. Loop: `smTreeTemp->leaf[0]` β WRITE to address `0x0` β **SIGSEGV** |
|
|
| ## Root Cause Snippet |
|
|
| ```cpp |
| // regionPlugin.cpp:38-42 β BUG |
| template <typename T> |
| void allocateChunk(T*& ptr, int32_t count) |
| { |
| ptr = static_cast<T*>(malloc(count * sizeof(T))); |
| // β no NULL check here |
| } |
| |
| // regionPlugin.cpp:117, 121 β attacker-controlled n flows into allocateChunk |
| smTreeTemp->n = read<int32_t>(d); // β 0x7FFFFFFF from crafted buffer |
| allocateChunk(smTreeTemp->leaf, smTreeTemp->n); // malloc(8.5 GB) β NULL |
| |
| // regionPlugin.cpp:156 β NULL dereference |
| smTreeTemp->leaf[i] = read<int32_t>(d); // WRITE to 0x0 β SIGSEGV |
| ``` |
|
|
| ## Trigger Path |
|
|
| ``` |
| crafted .trt file |
| β Runtime.deserialize_cuda_engine(data) |
| β RegionPluginCreator::deserializePlugin(name, serialData, serialLength) |
| β new Region(serialData, serialLength) [regionPlugin.cpp:93] |
| β allocateChunk(smTreeTemp->leaf, INT32_MAX) [regionPlugin.cpp:121] |
| β malloc(8,589,934,588) returns NULL |
| β smTreeTemp->leaf[0] = read<int32_t>(d) [regionPlugin.cpp:156] |
| β SIGSEGV |
| ``` |
|
|
| ## Reproduction |
|
|
| ```bash |
| # Compile (no CUDA, no GPU, no TensorRT required) |
| g++ -std=c++17 -O0 -o poc_tensorrt poc_tensorrt.cpp |
| |
| # Run |
| ./poc_tensorrt |
| ``` |
|
|
| **Expected output:** |
| ``` |
| [*] n = 2147483647 (0x7FFFFFFF) |
| [*] malloc requested: 8589934588 bytes (8.0 GB) |
| [*] leaf ptr after allocateChunk: (nil) |
| [!] malloc FAILED β leaf is NULL |
| [!] Entering loop β crash on first iteration... |
| Segmentation fault (core dumped) [exit 139 / SIGSEGV] |
| ``` |
|
|
| **ASAN confirmation:** |
| ```bash |
| g++ -std=c++17 -O0 -g -fsanitize=address -o poc_tensorrt_asan poc_tensorrt.cpp |
| ASAN_OPTIONS=allocator_may_return_null=1 ./poc_tensorrt_asan |
| # ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 |
| # The signal is caused by a WRITE memory access. |
| # Hint: address points to the zero page. |
| # #0 in trigger_deserialize poc_tensorrt.cpp:187 |
| ``` |
|
|
| ## Suggested Fix |
|
|
| ```cpp |
| template <typename T> |
| void allocateChunk(T*& ptr, int32_t count) |
| { |
| ptr = static_cast<T*>(malloc(count * sizeof(T))); |
| PLUGIN_VALIDATE(ptr != nullptr); // ADD THIS |
| } |
| ``` |
|
|
| All 9 call sites in the deserialization constructor are fixed by patching the template. |
|
|
| ## Files |
|
|
| | File | Description | |
| |---|---| |
| | `poc_tensorrt.cpp` | Self-contained C++ PoC β compiles with g++, no CUDA | |
| | `report.md` | Full huntr-format vulnerability report | |
| | `poc-evidence.html` | HTML evidence page with terminal output and ASAN backtrace | |
| | `README.md` | This file | |
|
|