Instructions to use EnigmaConsultant/huntr-poc-tensorrt-onnx-path-traversal with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- TensorRT
How to use EnigmaConsultant/huntr-poc-tensorrt-onnx-path-traversal with TensorRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
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: verbatimnormalizePath()+parseExternalWeights()sanitization logic from onnx-tensorrtWeightsContext.cpp/weightUtils.cpp@ commit7c51a63, plus the test driver.traversal_evidence.logβ full build + run transcript, including the referenceonnxlibrary'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
- -