Security Model for External Data in onnx-irΒΆ
This document describes the threat model, implemented defenses, known
limitations, and design rationale for how onnx-ir handles external tensor
data (the ExternalTensor class).
Threat modelΒΆ
ONNX models can reference external data files via relative paths stored in the
location field of a TensorProto. A malicious model could abuse this to
read arbitrary files on the host. The main attack vectors are:
Vector |
Example |
|---|---|
Path traversal |
|
Absolute path |
|
Symlink escape |
|
Hardlink smuggling |
Hard-linking a sensitive file into the model directory |
Implemented defensesΒΆ
ExternalTensor._check_path_containment() enforces a three-layer check
whenever a non-empty base_dir is set:
Layer |
What it does |
|---|---|
1. String-based containment |
Normalizes the path (without resolving symlinks) and verifies it stays within |
2. Realpath containment |
Resolves all symlinks via |
3. Hardlink detection |
Rejects files with more than one hard link ( |
All three checks run at load time β when numpy(), tofile(), or
tobytes() (indirectly, via _load()) is called β not at construction time.
This allows safe deserialization of untrusted protos without triggering I/O.
base_dir="" bypass (by design)ΒΆ
When base_dir is empty (the default), all security checks are skipped.
This is intentional for two reasons:
Programmatic construction β when a developer creates an
ExternalTensorin code, they control the paths directly and do not need containment checks.Deserialization safety β the IR deserializer may create
ExternalTensorobjects before abase_diris known. Containment is only meaningful when the caller setsbase_dirto the modelβs directory.
If you are loading an untrusted model, always set base_dir to the directory
containing the model file.
Divergence from onnx/onnxΒΆ
The reference ONNX runtime (onnx/onnx#7717) implements a four-layer defense:
Layer |
onnx/onnx |
onnx-ir |
Notes |
|---|---|---|---|
1. Canonical path containment |
β |
β |
Equivalent |
2. Symlink handling |
β reject all |
β allow within base |
Different policy β see rationale below |
3. |
β |
β |
Planned β see Future hardening |
4. Hardlink count check |
β |
β |
Equivalent (added in this PR) |
Why the differences?ΒΆ
onnx-ir is a library, not a runtime. It focuses on safe loading of model data for inspection and transformation, not sandboxed execution.
Symlink policy β onnx/onnx rejects all final-component symlinks. onnx-ir allows symlinks whose resolved target stays within
base_dir. This is more permissive but still prevents escape from the containment boundary, and avoids breaking legitimate workflows that use symlinks within the model directory (e.g. shared weight files).O_NOFOLLOWcloses a TOCTOU (time-of-check-to-time-of-use) race between the containment check and theopen()call. This is a valuable defense-in-depth measure but requires platform-specific code (os.O_NOFOLLOWis not available on Windows). It is planned for a future release.
Known limitationsΒΆ
TOCTOU window β A small race exists between
_check_path_containment()and the subsequentopen(). An attacker who can modify the filesystem concurrently could swap a safe file for a symlink after the check passes. Mitigation: useO_NOFOLLOW(planned).base_dir=""bypass β As described above, an emptybase_dirdisables all checks. Callers loading untrusted models must setbase_dir.Hardlink detection is best-effort β The
st_nlinkcheck only detects hard links at the time of the check. It cannot prevent hard links created after the check. On some filesystems or operating systems,st_nlinkmay not accurately reflect the number of hard links.Hardlink collateral β When an attacker creates a hard link to a legitimate data file, both the original and the link get
st_nlink=2. This means the original file also becomes un-loadable until the extra link is removed. This is fail-closed behavior (safe by default), but operators should be aware of it when diagnosing unexpected load failures.
Future hardeningΒΆ
O_NOFOLLOWon file open β Useos.open()withO_NOFOLLOWto close the TOCTOU window at the kernel level (Linux/macOS)._open_validated()wrapper β Centralize file-open + security checks so future code paths cannot accidentally bypass containment.