Skip to content

Validate sparse tensor external file paths#28408

Merged
adrianlizarraga merged 14 commits into
mainfrom
adrianl/SparseTensor_ExternalDataFilePathValidation
May 19, 2026
Merged

Validate sparse tensor external file paths#28408
adrianlizarraga merged 14 commits into
mainfrom
adrianl/SparseTensor_ExternalDataFilePathValidation

Conversation

@adrianlizarraga

@adrianlizarraga adrianlizarraga commented May 8, 2026

Copy link
Copy Markdown
Contributor

Description

Adds path traversal validation for sparse tensors with external data, closing a gap where SparseTensorProtoToDenseTensorProto would read external files without checking whether the path escapes the model directory.

Bug fix (pre-existing)

  • CopySparseData indices size check: The raw_data().size() check was wrong for external data (where raw_data is empty). Fixed by adding a pre-unpack raw_data size guard for inline data and a post-unpack unpack_buffer size check for all data sources.

Tests

  • Security tests (tensorutils_test.cc): Path traversal blocked (values, indices), absolute path blocked (values, indices), zero-element regression (zero dense elements, zero NNZ). All create escaping files and assert specifically for "escapes" error.
  • Positive tests (sparse_kernels_test.cc): 7 end-to-end tests for legitimate sparse tensors with external data — external values, external indices (INT64/INT32/INT16/INT8), both external (rank-1 and rank-2 COO).

Known limitation (deferred)

ORT_MEM_ADDR in-memory external data for sparse tensors can trigger arbitrary memory reads. This is a separate issue from path validation — LoadSparseInitializerOrtFormat legitimately uses in-memory markers for ORT-format models, so blanket rejection would break functionality. Should be addressed in a separate PR.

Motivation and Context

A malicious ONNX model could use ../ path traversal in sparse tensor external data locations to read arbitrary files outside the model directory. Dense tensors already had this validation; sparse tensors did not.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens security around ONNX sparse tensor loading by validating that external_data file locations referenced by sparse tensor values/indices cannot escape the model directory (preventing path traversal).

Changes:

  • Add external-data path validation for sparse tensor values and indices during SparseTensorProtoToDenseTensorProto conversion.
  • Add regression tests that attempt ../ traversal via sparse tensor external data locations and expect failure.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
onnxruntime/test/framework/tensorutils_test.cc Adds regression tests covering path traversal attempts via sparse tensor external data (values and indices).
onnxruntime/core/framework/tensorprotoutils.cc Adds a helper to validate sparse tensor external-data locations and calls it for both values and indices before reading external data.
Comments suppressed due to low confidence (1)

onnxruntime/core/framework/tensorprotoutils.cc:2092

  • In CopySparseData, the needs_unpack branch checks indices.raw_data().size() even when indices uses data_location == EXTERNAL. For typical external-data protos raw_data will be empty, so this path rejects valid sparse indices stored in an external file. Consider validating the size of the unpacked buffer returned by UnpackInitializerData (or the external_data.length) instead of raw_data().size(), and apply the same fix to the INT32/INT16/INT8 branches below.
    case ONNX_NAMESPACE::TensorProto_DataType_INT64:
      if (needs_unpack) {
        ORT_RETURN_IF_NOT(indices.raw_data().size() == SafeInt<size_t>(indices_elements) * sizeof(int64_t),
                          "Sparse tensor: ", name, " indices raw data size does not match expected: ",
                          indices_elements * sizeof(int64_t));

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@adrianlizarraga
adrianlizarraga requested a review from Copilot May 8, 2026 18:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread onnxruntime/core/framework/tensorprotoutils.cc Outdated
Comment thread onnxruntime/test/framework/tensorutils_test.cc

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@adrianlizarraga
adrianlizarraga marked this pull request as ready for review May 8, 2026 20:56
@yuslepukhin

Copy link
Copy Markdown
Contributor

Please, see #28509 to check how it may affect your changes.

@yuslepukhin yuslepukhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT Covered (Missing Tests):

  • In-memory address attack (/ORT_MEM_ADDR/ location in sparse tensor values/indices) — no test exists for Vector B
  • Absolute path in sparse tensor (/etc/passwd or C:\Windows...) — relies on ValidateExternalDataPath's existing absolute-path rejection, but not tested specifically through the sparse tensor path
  • No positive test — no test that a legitimate sparse tensor with external data within the model directory succeeds

Comment thread onnxruntime/core/framework/tensorprotoutils.cc
adrianlizarraga and others added 5 commits May 15, 2026 09:59
The CopySparseData function checked indices.raw_data().size() before
unpacking, which is always 0 when data_location is EXTERNAL (data is
in an external file, not in raw_data). This caused legitimate sparse
tensors with external indices to always fail validation.

Fix by moving UnpackInitializerData before the size check and
validating unpack_buffer.size() instead, matching the pattern already
used for sparse values. This works correctly for both inline raw_data
and external data sources.

Also remove the workaround in the indices path-traversal test that
stuffed raw_data to bypass the broken check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread onnxruntime/core/framework/tensorprotoutils.cc Outdated
Comment thread onnxruntime/core/framework/tensorprotoutils.cc Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread onnxruntime/core/framework/tensorprotoutils.cc Outdated
Comment thread onnxruntime/core/framework/tensorprotoutils.cc
@adrianlizarraga

adrianlizarraga commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

@yuslepukhin thanks for the review. I believe I addressed your comments.

In-memory address attack (/ORT_MEM_ADDR/ location in sparse tensor values/indices) — no test exists for Vector B

This is valid existing issue. ORT does not currently validate sparse tensors with in-memory external data. This is separate from the reported issue about path validation and requires more careful thought and implementation. It is not clear to me how to properly validate in-memory address for sparse tensors, so I would recommend handling this separately as to not block this fix.

Absolute path in sparse tensor (/etc/passwd or C:\Windows...) — relies on ValidateExternalDataPath's existing absolute-path rejection, but not tested specifically through the sparse tensor path

Sure. I added some more unit tests here.

No positive test — no test that a legitimate sparse tensor with external data within the model directory succeeds

Positive test actually does exist here:

CreateTensorWithExternalData<float>(TensorProto_DataType_FLOAT, values, tensor_filename, tp);

However, I added more positive tests now that I also updated this PR to handle the other pre-existing bug related to external indices.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants