Oct 29, 2026·9 min read·3 visits
Unvalidated retrieval and unchecked unsigned casting of the 'num_scan_inputs' attribute in ONNX Scan shape inference routines allow untrusted models to crash the host application via null pointer dereference (CWE-476) or oversized memory allocation exceptions (CWE-195), bypassing pre-inference model validation.
Open Neural Network Exchange (ONNX) versions 1.4.0 prior to 1.22.0 contain a null pointer dereference (CWE-476) and a signed-to-unsigned integer conversion error (CWE-195) in the shape inference functions for the Scan operator. When processing an untrusted ONNX model, the functions ScanInferenceFunction, ScanInferenceFunction_opset8, and ScanInferenceFunction_opset9 fail to validate the presence and value of the required num_scan_inputs attribute. Omitting the attribute results in a direct null pointer dereference that crashes the host process via SIGSEGV, while supplying a negative value triggers an integer wrap-around during type narrowing that leads to an unhandled allocation failure. Notably, models containing a negative num_scan_inputs value bypass standard validation via onnx.checker.check_model().
Open Neural Network Exchange (ONNX) provides an open standard format and C++ library (with Python bindings) for representing machine learning models across frameworks. A core component of the ONNX library is its shape inference engine (onnx::shape_inference), which statically analyzes computation graphs to determine the tensor dimensions and data types of intermediate and output nodes before model execution or compilation.
In ONNX versions >= 1.4.0 and < 1.22.0, the shape inference routines responsible for handling the Scan control-flow operator fail to safely retrieve and validate the num_scan_inputs operator attribute. Specifically, the vulnerability affects ScanInferenceFunction in onnx/defs/controlflow/utils.cc, as well as the legacy operator set handlers ScanInferenceFunction_opset8 and ScanInferenceFunction_opset9 in onnx/defs/controlflow/old.cc.
This implementation flaw manifests as two distinct weakness classes depending on how an untrusted ONNX model is constructed: a NULL Pointer Dereference (CWE-476) when num_scan_inputs is omitted from the node definition, and a Signed to Unsigned Conversion Error (CWE-195) when num_scan_inputs is populated with a negative integer. Both conditions allow a crafted ONNX model file to terminate the process executing shape inference.
The attack surface encompasses any machine learning pipeline, model registry, compilation backend, or inference server that ingests external ONNX models and invokes onnx.shape_inference.infer_shapes(). Because a model with a negative num_scan_inputs value passes structural verification via onnx.checker.check_model(), standard pre-processing validation gates do not prevent the denial-of-service condition.
The ONNX operator specification for Scan defines num_scan_inputs as a required integer attribute that indicates the number of scan input tensors passed to the loop body subgraph. Within the C++ shape inference engine, operator attributes are stored in Protocol Buffer (AttributeProto) structures and accessed via the InferenceContext::getAttribute(const std::string& name) method, which returns a raw pointer (const AttributeProto*).
When a Scan node in a serialized model omits the num_scan_inputs attribute, ctx.getAttribute("num_scan_inputs") returns nullptr. In all three affected inference functions (ScanInferenceFunction, ScanInferenceFunction_opset8, and ScanInferenceFunction_opset9), the code immediately dereferenced this return value via ->i() without verifying that the pointer was non-null or that the attribute contained an integer field (has_i()). Executing this code path triggers an immediate segmentation fault (SIGSEGV) at the C++ layer, terminating the host interpreter or native binary.
The second failure mode stems from how the retrieved 64-bit signed protobuf integer (int64_t) is converted into an unsigned size_t variable. Prior to version 1.22.0, the codebase relied on a helper template named narrow_cast<T> defined in onnx/defs/shape_inference.h. Despite its name, narrow_cast<T> performed an unconditional static_cast<T> marked noexcept, performing no runtime bounds checking or sign validation.
> [!NOTE]
> In the ONNX Protocol Buffer schema (onnx.proto), all single integer attributes (AttributeProto::i) are stored as signed 64-bit integers (int64). Consequently, schema validation alone cannot restrict an attribute to non-negative values unless explicit semantic checks are implemented in the operator's verification or inference routines.
When a model specifies a negative integer such as -1 for num_scan_inputs, narrow_cast<size_t>(-1) wraps the signed value to 18446744073709551615 (SIZE_MAX on 64-bit platforms). The inference functions then compute the number of loop-state variables using unsigned arithmetic (auto num_loop_state_vars = num_inputs - num_scan_inputs;). Because num_scan_inputs has wrapped to a massive unsigned value, subsequent arithmetic and vector sizing operations attempt to allocate or index out-of-bounds memory capacities, throwing an unhandled std::length_error or std::bad_alloc exception that aborts normal execution.
The vulnerability resided across onnx/defs/controlflow/old.cc, onnx/defs/controlflow/utils.cc, and the header onnx/defs/shape_inference.h. In the vulnerable versions, attribute retrieval and type casting were combined into a single unchecked expression:
// Vulnerable pattern in ScanInferenceFunction (utils.cc) and opset8/opset9 (old.cc)
void ScanInferenceFunction(InferenceContext& ctx) {
auto num_inputs = ctx.getNumInputs();
// BUG 1: ctx.getAttribute() returns nullptr if 'num_scan_inputs' is missing -> SIGSEGV on ->i()
// BUG 2: narrow_cast<size_t> performs an unchecked static_cast, wrapping negative int64_t values
auto num_scan_inputs = narrow_cast<size_t>(ctx.getAttribute("num_scan_inputs")->i());
auto num_loop_state_vars = num_inputs - num_scan_inputs;
auto num_outputs = ctx.getNumOutputs();
auto num_scan_outputs = num_outputs - num_loop_state_vars;
// ...
}In onnx/defs/shape_inference.h, the underlying narrow_cast utility was implemented as a wrapper around static_cast without value verification:
// Vulnerable implementation in onnx/defs/shape_inference.h
template <class T, class U>
static constexpr T narrow_cast(U&& u) noexcept {
return static_cast<T>(std::forward<U>(u));
}Commit dd6bc92ea39fd2274fd671f0734b0b8b558356f5 (merged via PR #7916) resolves both flaws by introducing safe attribute accessors and replacing narrow_cast with a checked narrow<T> template in onnx/defs/shape_inference.h:
// Patched implementation in onnx/defs/shape_inference.h
inline const AttributeProto& getRequiredAttribute(const InferenceContext& ctx, const std::string& name) {
const auto* attr = ctx.getAttribute(name);
if (attr == nullptr) {
// Raises ONNX InferenceError instead of dereferencing a null pointer
fail_shape_inference("Required attribute '", name, "' is missing");
}
return *attr;
}
inline int64_t getRequiredAttributeInt(const InferenceContext& ctx, const std::string& name) {
const auto& attr = getRequiredAttribute(ctx, name);
if (!attr.has_i()) {
// Ensures the attribute is actually populated as an integer type
fail_shape_inference("Required attribute '", name, "' must be an integer");
}
return attr.i();
}
template <class T, class U>
static constexpr T narrow(U&& u) {
const U original = u;
const T result = static_cast<T>(std::forward<U>(u));
// Detects truncation (value mismatch) and sign changes across signed/unsigned boundaries
if (static_cast<U>(result) != original || ((result < T{}) != (original < U{}))) {
fail_shape_inference("narrow: value ", original, " cannot be represented in target type");
}
return result;
}All three Scan inference functions were updated to use narrow<size_t>(getRequiredAttributeInt(ctx, "num_scan_inputs")). Regarding fix completeness, returning const AttributeProto& by reference in getRequiredAttribute prevents callers from skipping the null check, and replacing narrow_cast with narrow across the header ensures that any other callers inside shape_inference.h also gain protection against lossy or sign-changing integer conversions. However, note that downstream arithmetic (auto num_loop_state_vars = num_inputs - num_scan_inputs;) still operates on size_t values; if num_scan_inputs is positive but exceeds num_inputs, unsigned underflow in num_loop_state_vars remains a separate logical boundary to verify.
Exploiting CVE-2026-115921 requires no special privileges or complex memory layout manipulation. An attacker only needs to supply a crafted ONNX model (either as a binary .onnx protobuf file or via the ONNX text representation parser) to an application that runs onnx.shape_inference.infer_shapes().
Two distinct attack vectors exist. In the first vector, the attacker constructs a graph containing a Scan operator node with a valid body subgraph (body attribute) but omits the num_scan_inputs attribute entirely. When the target application invokes shape inference, the C++ backend attempts to read memory at offset 0x0 (plus the struct offset of AttributeProto::i_), immediately crashing the process with SIGSEGV.
In the second vector, the attacker includes num_scan_inputs = -1 in the Scan node attributes. Because the attribute is present and conforms to the int64 type required by AttributeProto, this model passes structural checks when the victim application runs onnx.checker.check_model(model). Once the validated model reaches onnx.shape_inference.infer_shapes(model), the -1 value wraps to SIZE_MAX, causing an uncaught exception or allocation crash.
import onnx
# Regression PoC from onnx/test/shape_inference_test.py
def reproduce_cve_2026_115921():
scan_body = (
"body = b (float[1] si, float[1] xi) => (float[1] so, float[1] xo) "
"{ so = Identity(si) xo = Identity(xi) }"
)
# Vector 1: Missing 'num_scan_inputs' -> Triggers NULL pointer dereference (SIGSEGV)
model_missing_attr = onnx.parser.parse_model(
f"""
<ir_version: 8, opset_import: [ "" : 9 ]>
g (float[1] s, float[3,1] x) => (float[1] so, float[3,1] xo) {{
so, xo = Scan <{scan_body}> (s, x)
}}
"""
)
# Vector 2: Negative 'num_scan_inputs' (-1) -> Passes check_model(), fails in infer_shapes()
model_negative_attr = onnx.parser.parse_model(
f"""
<ir_version: 8, opset_import: [ "" : 9 ]>
g (float[1] s, float[3,1] x) => (float[1] so, float[3,1] xo) {{
so, xo = Scan <num_scan_inputs = -1, {scan_body}> (s, x)
}}
"""
)
for model in (model_missing_attr, model_negative_attr):
try:
onnx.shape_inference.infer_shapes(model, strict_mode=True)
except onnx.shape_inference.InferenceError as err:
print(f"Handled safely in >= 1.22.0: {err}")The primary security impact of CVE-2026-115921 is an application-level Denial of Service (MITRE ATT&CK T1499: Endpoint Denial of Service). The vulnerability does not grant confidentiality disclosure (C:N) or arbitrary memory write primitives (I:N), as the null pointer dereference is strictly a read operation at a fixed near-null offset, and the integer conversion error leads to an immediate allocation failure rather than a controllable heap buffer overflow.
The CVSS v3.1 base score is 6.2 (Medium) with the vector CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. The Local attack vector (AV:L) reflects that the vulnerable library processes local model files or in-memory model structures, while User Interaction is marked None (UI:N) because automated MLOps pipelines, model conversion services, and inference servers routinely ingest and validate uploaded models without manual intervention.
In multi-tenant machine learning platforms or automated model evaluation services where Python worker processes call into the native onnx C++ extension, a SIGSEGV bypasses Python try...except blocks entirely. Consequently, a single malformed model submission terminates the entire worker process, disrupting concurrent tasks sharing that execution environment.
Threat intelligence metrics indicate a low likelihood of widespread wild exploitation. The Exploit Prediction Scoring System (EPSS) assigns CVE-2026-115921 a score of 0.00131 (0.131%, ~2.68th percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
The definitive remediation is to upgrade the onnx package to version 1.22.0 or later, which includes commit dd6bc92ea39fd2274fd671f0734b0b8b558356f5. In version 1.22.0, both missing and negative num_scan_inputs attributes are caught during shape inference and converted into catchable onnx.shape_inference.InferenceError exceptions.
pip install --upgrade "onnx>=1.22.0"For environments maintaining custom C++ builds of ONNX from source that cannot immediately rebase onto v1.22.0, cherry-picking commit dd6bc92ea39fd2274fd671f0734b0b8b558356f5 and 2bb50465112feca9003e1ed654d77f01ff1415ca resolves the vulnerability across onnx/defs/shape_inference.h, onnx/defs/controlflow/old.cc, and onnx/defs/controlflow/utils.cc.
> [!IMPORTANT]
> Do not rely solely on onnx.checker.check_model() to filter untrusted models prior to shape inference on unpatched versions (< 1.22.0), as models with negative num_scan_inputs values pass the model checker.
If an immediate upgrade is not feasible in an environment that processes untrusted models, implement a pre-inference graph inspection step in Python before calling onnx.shape_inference.infer_shapes(). Iterate over all nodes in model.graph.node (and any nested subgraphs), locate nodes where op_type == "Scan", and verify that an attribute named num_scan_inputs exists, has type == onnx.AttributeProto.INT, and has an integer value strictly greater than 0 and less than or equal to the node's input count. Additionally, execute shape inference of untrusted models within isolated, ephemeral subprocesses or containers so that a native crash does not terminate the parent service.
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
onnx onnx | >= 1.4.0, < 1.22.0 | 1.22.0 |
| Attribute | Detail |
|---|---|
| CVE / GHSA ID | CVE-2026-115921 / GHSA-m4pr-6v3c-6fgr |
| Weakness Class | CWE-476 (NULL Pointer Dereference) & CWE-195 (Signed to Unsigned Conversion Error) |
| Affected Versions | ONNX >= 1.4.0, < 1.22.0 (Patched in 1.22.0) |
| Attack Vector | Local (AV:L) via untrusted ONNX model ingestion |
| CVSS v3.1 Score | 6.2 Medium (CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| EPSS Score | 0.131% (2.68th Percentile) |
| Impact | Process Crash / Denial of Service (SIGSEGV or unhandled allocation exception) |
| CISA KEV Status | Not Listed |
The software dereferences a pointer that it expects to be valid, but is NULL, alongside a Signed to Unsigned Conversion Error (CWE-195) where a negative signed primitive is cast to a large unsigned value.
An inefficient algorithmic complexity vulnerability exists in the npm package decode-uri-component prior to version 0.5.1. When processing malformed URI strings containing invalid percent-escape sequences, the library falls back to a custom decoder that collects distinct percent-encoded runs into a dictionary and executes a full-string regular expression replacement for each entry. Supplying a payload with thousands of distinct percent-encoded sequences alongside a malformed escape triggers quadratic O(K * L) execution time, starving the single-threaded Node.js event loop and resulting in an unauthenticated Denial of Service.
CVE-2026-91403 is a critical cryptographic trust validation flaw (CWE-327) in the Java Secure Socket Extension (JSSE) component of Oracle Java SE, Oracle GraalVM, and OpenJDK builds. Triggered by the compromise of private signing keys for eight intermediate Certificate Authorities operated by eMudhra (emSign), unpatched Java runtime environments implicitly trust fraudulent TLS and code-signing certificates issued by these compromised intermediates. Because Java disables online certificate revocation checking (OCSP/CRL) by default, an unauthenticated network attacker in an Adversary-in-the-Middle (AiTM) position can intercept, decrypt, and modify encrypted TLS communications or bypass code signature verification without triggering security exceptions.
Open Neural Network Exchange (ONNX) prior to version 1.22.0 contains a memory exhaustion vulnerability (CWE-789) in its Python tensor unpacking helpers and C++ model checker. Specifically, the _unpack_2bit() and _unpack_4bit() functions in onnx.numpy_helper unconditionally resize unpacked NumPy arrays to match attacker-controlled TensorProto.dims metadata without verifying that the underlying raw_data or int32_data buffers contain a corresponding number of packed bits. Furthermore, onnx.checker.check_tensor() previously bypassed length verification for sub-byte data types. Processing an untrusted ONNX model with packed sub-byte tensors (UINT2, INT2, UINT4, INT4, or FLOAT4E2M1) allows an unauthenticated attacker to trigger disproportionate memory allocations from minimal payloads, resulting in an out-of-memory (OOM) crash and denial of service.
An out-of-bounds read vulnerability (CWE-125) exists in the Open Neural Network Exchange (ONNX) library across versions 1.2.1 prior to 1.22.0. The flaw occurs within the shape inference routines for the ConvTranspose operator when processing a model with a weight tensor rank less than three and an omitted kernel_shape attribute. Unchecked indexing into an empty vector triggers a memory segmentation fault, resulting in a Denial of Service (DoS) against applications inspecting or loading untrusted ONNX models.
A session-binding vulnerability in Python Social Auth (social-core) versions prior to 6.2.0 allows an attacker to perform authorization code injection against the OpenID Connect authentication backend. By storing generated OIDC nonces in a global database table without binding them to the initiating browser session, the library accepts any valid, unexpired nonce returned in an ID token regardless of which session originated the authentication transaction. When combined with the default disablement of Proof Key for Code Exchange (PKCE), an attacker who intercepts an unused authorization code can inject it into their own authentication flow to achieve full account takeover.
CVE-2026-111497, tracked in threat intelligence under the aliases Node2Shell, Broccoli2Shell, and HTTP2Shell, is a critical out-of-bounds heap write and denial-of-service vulnerability in the reference Google Brotli decompression library (c/dec/decode.c). The flaw originates from an unchecked return value and error-code conflation in DecodeBlockTypeAndLength() when processing malformed streams with single-block-type declarations (max_block_type <= 1). On the fast decoding path, the ignored error return causes the 32-bit unsigned block_length counter to underflow from 0 to UINT32_MAX (0xFFFFFFFF), corrupting internal decoder tables and enabling arbitrary heap memory overwrites via memmove16(). On the streaming path, the error return is misinterpreted as an incomplete bit-stream, trapping the worker process in an infinite 100% CPU loop. Because Google Brotli is embedded across major runtimes and infrastructure components—including Node.js, Envoy Proxy, Microsoft .NET, Istio, and Python—the vulnerability exposes a broad attack surface via standard HTTP Content-Encoding: br request and response bodies.