You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

onnx-tensorrt external-data weight loader: bare ".." bypasses the parent-directory traversal check (CWE-22)

Target: onnx/onnx-tensorrt β€” WeightsContext::parseExternalWeights() (WeightsContext.cpp) and normalizePath() (weightUtils.cpp).

Tested against: onnx-tensorrt main @ commit 7c51a63a719180eb5160c874c111746f3fb46a6b ("ONNX-TRT 11.1 GA Release"), cloned fresh 2026-07-10. This is the current HEAD; the bug is present in the code as of today, not a historical/already-fixed issue.

Summary

When onnx-tensorrt loads an ONNX model whose initializer stores its weight data externally (data_location: EXTERNAL), it resolves the external file's path relative to the main .onnx file's own directory and explicitly tries to forbid parent-directory escapes. The code comment says it plainly:

// Accessing parent directories (i.e. ../) is not allowed. Normalize path first.

The enforcement, however, only rejects the substring "../" (with a trailing slash) inside the normalized path:

// WeightsContext.cpp:166-176 (verbatim)
auto path = mOnnxFileLocation;
std::string normalizedFile = normalizePath(file);
bool illegalDir{false};
#ifdef _MSC_VER
illegalDir |= normalizedFile.find("..\\") != std::string::npos;
#endif
illegalDir |= normalizedFile.find("../") != std::string::npos;

if (illegalDir)
{
    LOG_ERROR("Relative paths to parent (../) are not allowed in ONNX external weights! ...");
    return false;
}

normalizePath() (weightUtils.cpp:103-153) tokenizes the input on / and treats a token as a "go up one directory" marker only if it is exactly "../" (with the trailing slash):

// weightUtils.cpp: addToPath lambda, verbatim
if (s != "../" || normPath.empty() || (!normPath.empty() && normPath.back() == "../"))
{
    normPath.push_back(s);   // <-- pushed as an ordinary literal component
}
else
{
    normPath.pop_back();     // <-- only reached when s == "../"
}

Because the tokenizer's final token (whatever follows the last /, or the entire string if there is no / at all) is taken verbatim via path.substr(...) without a trailing slash, a path whose last (or only) component is a bare ".." β€” i.e. ".." itself, or anything ending in "/.." β€” is pushed into normPath as an ordinary path segment instead of being recognized as a parent-directory reference. The resulting normalized string therefore never contains the substring "../", so illegalDir stays false and the check is bypassed, even though the string still means "go up one directory" once handed to any real filesystem API.

The PoC

traversal.onnx contains a single initializer, weights_traversal, with data_location: EXTERNAL and:

external_data { key: "location" value: ".." }
external_data { key: "offset"   value: "0"  }
external_data { key: "length"   value: "4"  }

(Confirmed by loading the file with the reference onnx==1.19 Python/C++ library with load_external_data=False β€” see traversal_evidence.log.)

mOnnxFileLocation in WeightsContext is set to the path of the main .onnx file being parsed (see WeightsContext.hpp: setOnnxFileLocation). parseExternalWeights builds the candidate path by replacing everything after the last path separator in mOnnxFileLocation with normalizePath(location):

size_t slash = path.find_last_of("\\/");
if (slash != std::string::npos)
{
    path.replace(slash + 1, path.size() - (slash + 1), normalizedFile);
}

For location = ".." this resolves to the parent directory of the folder containing the ONNX model (e.g. model at /data/models/traversal.onnx β†’ resolved path /data/models/.., i.e. /data), directly violating the function's own stated invariant. The bug generalizes to any path whose last component is a bare .. (e.g. "sub/.."), not just the single-token case used in this minimal PoC.

Verification performed

Because the full nvonnxparser/TensorRT stack requires the proprietary NVIDIA TensorRT SDK (closed-source, GPU/driver-gated) which is not installable in this environment, this PoC follows the same approach as the sibling onnx-tensorrt ConvTranspose PoC in this account (huntr-poc-onnxtrt-convtranspose-oob-read-style verbatim extraction): the exact, unmodified normalizePath() function and the exact illegalDir sanitization logic from parseExternalWeights() are copied character-for-character into a small standalone harness (traversal_harness.cpp) with zero TensorRT/CUDA dependency (both functions are pure string/vector logic with no nvinfer1 runtime calls). The harness is compiled with -fsanitize=address,undefined and executed directly against the real PoC value.

Run output (traversal_evidence.log, reproduced in full):

location=".."                     -> normalizePath()=".."             illegalDir-check says: SAFE(bug!) resolved path=/data/models/..
location="../etc/passwd"          -> normalizePath()="../etc/passwd"  illegalDir-check says: BLOCKED   resolved path=/data/models/../etc/passwd
location="../../etc/passwd"       -> normalizePath()="../../etc/passwd" illegalDir-check says: BLOCKED   resolved path=/data/models/../../etc/passwd
location="sub/../../../etc/passwd" -> normalizePath()="../../etc/passwd" illegalDir-check says: BLOCKED   resolved path=/data/models/../../etc/passwd
location="sub/.."                 -> normalizePath()="sub/.."         illegalDir-check says: SAFE(bug!) resolved path=/data/models/sub/..

The exact PoC value ("..") and the "sub/.." variant both bypass the check (SAFE(bug!)); every multi-level traversal string that ends with a real / before the final segment is correctly caught (BLOCKED), confirming the flaw is specific to a final path component consisting of a bare .. with no trailing separator.

As independent confirmation that this is a real gap (and not, say, a misreading of the ONNX spec), the reference onnx Python/C++ library's own external-data loader β€” which does a proper containment check based on resolving the real filesystem path rather than substring matching β€” correctly refuses to load this exact file:

ValidationError: Data of TensorProto ( tensor name: weights_traversal) should be file inside
'<model dir>', but '..' points outside the directory.

This shows onnx-tensorrt reimplements its own, weaker, ad hoc version of this check instead of relying on (or matching the rigor of) the upstream ONNX library's containment logic.

Impact

A crafted .onnx file with an external-data location of ".." (or ending in "/..") escapes the directory-containment invariant that parseExternalWeights explicitly claims to enforce. On its own this single-token PoC resolves to a directory one level above the model's own folder rather than a specific file, so the minimal reproduction here demonstrates the sanitizer bypass rather than a one-shot arbitrary-file read; services that unpack untrusted, user-uploaded ONNX models into per-model directories and rely on onnx-tensorrt's built-in "no ../" guard to keep external-data reads confined to that directory can have that assumption silently broken at the directory-boundary level (CWE-22, Path Traversal), and the fact that the two independent substring/token checks disagree (deep multi-segment traversal is blocked while a bare-.. final segment is not) indicates the guard is not a principled containment check to begin with.

Files

  • traversal.onnx β€” the exact PoC file (unmodified, from the original submission).
  • traversal_harness.cpp β€” standalone harness: verbatim normalizePath() + parseExternalWeights() sanitization logic from onnx-tensorrt WeightsContext.cpp / weightUtils.cpp @ commit 7c51a63, plus the test driver.
  • traversal_evidence.log β€” full build + run transcript, including the reference onnx library's independent confirmation of the file's contents and its own (correct) rejection.

Notes on scope

A companion draft target in this account, huntr-tensorrt-volume-int-overflow (overflow.onnx, dims [2^31, 2^31, 2]), was also audited this session using the same verbatim-extraction methodology against getInitializerVol() / validateOnnxInitializer() (WeightsContext.cpp). That specific PoC does not reproduce: those exact dimensions are correctly rejected by onnx-tensorrt's existing size_t-based bounds check before any weight data is touched, confirmed by compiling and running the real, unmodified validation functions. It is not filed here or elsewhere as a confirmed finding.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support