Skip to content

EPContext sample helper follow-ups: reject .. leaf names and zero-copy read#29294

Open
GopalakrishnanN wants to merge 3 commits into
mainfrom
GopalakrishnanN/EpContextHelperUtilities-followups
Open

EPContext sample helper follow-ups: reject .. leaf names and zero-copy read#29294
GopalakrishnanN wants to merge 3 commits into
mainfrom
GopalakrishnanN/EpContextHelperUtilities-followups

Conversation

@GopalakrishnanN

@GopalakrishnanN GopalakrishnanN commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #28624 (now merged) addressing review comments on the sample-only EPContext helper (onnxruntime/test/autoep/library/ep_context_data_utils.h). Targets main; the diff is the three changes below.

Changes

1. Reject a leaf .. in IsDirectoryOrEmptyName

IsDirectoryOrEmptyName() previously treated only . and a trailing separator as directory-like. An untrusted, model-derived name with a leaf .. (e.g. sub/..) would resolve to the parent/model directory and only fail later as a confusing file I/O error. It is now rejected up front. A non-leaf .. anywhere in the path is still handled by ContainsPathTraversal() / the model-directory containment check.

2. Avoid the std::vector<char> copy on read

The app read-callback path previously copied the allocator-provided buffer into a std::vector<char>. Added a move-only EpContextData owning buffer plus ReadEpContextData(...) overloads:

  • Callback path adopts the allocator-provided buffer (no copy) and frees it via the same allocator on destruction.
  • File path reads straight into the owned vector.

The std::vector<char> ReadEpContextDataWithFileFallback(...) overloads remain as convenience wrappers (file path reads directly into the caller's vector; the callback path delegates to the zero-copy reader and copies once). The example EP now uses the zero-copy ReadEpContextData path.

3. Drop the coarse path-traversal guard from trusted path resolution

Now that IsResolvedPathWithinBase() does real model-directory containment for untrusted model-relative names, the lexical ContainsPathTraversal() guard is no longer applied on the trusted graph == nullptr branch of ResolveEpContextDataPath() — trusted callers already may pass absolute paths and own their paths, so there is no model directory to contain against. ContainsPathTraversal() is now used solely by ValidateEpContextDataName(), which validates the logical callback-namespace name written into the model ep_cache_context attribute (never resolved against a filesystem base). Untrusted model-relative names remain constrained by IsResolvedPathWithinBase().

Tests

  • Added EpContextDataUtils_ReadEpContextDataAdoptsCallbackBufferZeroCopy covering the callback-adopt and file paths.
  • Added a sub/.. rejection assertion to EpContextDataUtils_ResolvePathRejectsUnsafeNames.
  • Updated the two trusted-branch (graph == nullptr) assertions in EpContextDataUtils_ResolvePathRejectsUnsafeNames: ../escape.ctx is now accepted by the resolver and surfaces a normal file-open failure rather than a traversal rejection. Logical-name .. rejection (ValidateEpContextDataName) and the symlink/containment coverage are unchanged.
  • Local validation (Windows RelWithDebInfo): onnxruntime_autoep_test builds clean; *EpContext* 18 passed + 1 skipped (symlink test, expected on Windows).

All three changed files are clang-format clean.

Base automatically changed from gokrishnan/EpContextHelperUtilities to main June 27, 2026 00:26
Gopalakrishnan Nallasamy added 2 commits June 26, 2026 17:48
IsDirectoryOrEmptyName now also rejects a leaf .. (e.g. sub/..) so untrusted model-derived directory-like names are rejected up front instead of failing later as a confusing file I/O error. Non-leaf .. stays handled by ContainsPathTraversal and the model-directory containment check.

Add a move-only EpContextData owning buffer plus ReadEpContextData overloads: the app read-callback path now adopts the allocator-provided buffer instead of copying it into a std::vector, and the file path reads straight into the owned vector. The std::vector ReadEpContextDataWithFileFallback overloads remain as convenience wrappers. The example EP uses the zero-copy path. Adds a zero-copy unit test and a sub/.. rejection test.
…h resolution

Addresses #28624 review feedback. Now that IsResolvedPathWithinBase() does real model-directory containment for untrusted model-relative names, the lexical ContainsPathTraversal() guard is no longer applied on the trusted graph==nullptr branch of ResolveEpContextDataPath(): trusted callers already may pass absolute paths and own their paths, so there is no model directory to contain against. ContainsPathTraversal() is kept solely for ValidateEpContextDataName(), which validates the logical callback-namespace name written into the model ep_cache_context attribute (never resolved against a filesystem base). Updates the two trusted-branch tests; logical-name and model-directory containment coverage is unchanged.
@GopalakrishnanN GopalakrishnanN force-pushed the GopalakrishnanN/EpContextHelperUtilities-followups branch from abe505c to 4369676 Compare June 27, 2026 00:49
@GopalakrishnanN GopalakrishnanN requested a review from Copilot June 27, 2026 01:19

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

Follow-up improvements to the AutoEP sample EPContext data helper (ep_context_data_utils.h) to tighten untrusted-name validation, reduce read-path copying by adopting callback-allocated buffers, and relax trusted-path traversal guarding where no model-directory containment applies.

Changes:

  • Reject directory-like model-derived EPContext names including a leaf .. (e.g., sub/..) earlier during path resolution.
  • Introduce move-only EpContextData and new ReadEpContextData(...) overloads to enable zero-copy adoption of callback-provided buffers.
  • Update the example EP and unit tests to use/cover the new read API and trusted-path semantics.

Reviewed changes

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

File Description
onnxruntime/test/autoep/library/example_plugin_ep/ep.cc Switches sample EP to the new zero-copy ReadEpContextData API.
onnxruntime/test/autoep/library/ep_context_data_utils.h Adds EpContextData + new read overloads; adjusts traversal/directory validation behavior and related comments.
onnxruntime/test/autoep/ep_context_data_utils_test.cc Updates resolver expectations and adds a new test covering zero-copy adoption + file fallback.

Comment thread onnxruntime/test/autoep/library/ep_context_data_utils.h
Comment thread onnxruntime/test/autoep/library/ep_context_data_utils.h Outdated
Clear the vector-output convenience wrapper before invoking the callback reader and avoid pointer arithmetic for empty EpContextData buffers by assigning only when non-empty. Update traversal comments to describe model-relative non-leaf .. as canonicalized and containment-checked rather than always rejected. Add focused tests for empty callback payloads and stale-output clearing on callback failure.
@GopalakrishnanN GopalakrishnanN marked this pull request as ready for review June 27, 2026 02:58
@GopalakrishnanN GopalakrishnanN requested a review from Copilot June 27, 2026 02:58
@GopalakrishnanN GopalakrishnanN requested a review from edgchen1 June 27, 2026 02:59

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 1 comment.

Comment on lines 457 to 469
void* ep_context_data = nullptr;
size_t ep_context_data_size = 0;
OrtStatus* status = read_func(read_state, file_name, allocator, &ep_context_data, &ep_context_data_size);
auto buffer_deleter = [&api, allocator](void* buffer_to_free) {
if (buffer_to_free != nullptr) {
// Best-effort free during cleanup; release any returned status without throwing.
Ort::Status free_status{api.AllocatorFree(allocator, buffer_to_free)};
static_cast<void>(free_status);
}
};
std::unique_ptr<void, decltype(buffer_deleter)> ep_context_data_guard(ep_context_data, buffer_deleter);
// Adopt whatever the callback allocated so `out` frees it via the allocator on destruction, even on the error paths
// below. Ownership transfers without copying the (potentially large) buffer.
out.api_ = &api;
out.allocator_ = allocator;
out.buffer_ = ep_context_data;
out.buffer_size_ = ep_context_data_size;

if (status != nullptr) {
return status;
}
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.

2 participants