Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,85 @@
#include <fstream>
#include <vector>
#include <algorithm>
#include <filesystem>

Check warning on line 8 in onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 <filesystem> is an unapproved C++17 header. [build/c++17] [5] Raw Output: onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc:8: <filesystem> is an unapproved C++17 header. [build/c++17] [5]

#include "core/providers/openvino/onnx_ctx_model_helper.h"
#include "core/providers/openvino/backend_utils.h"

namespace onnxruntime {
namespace openvino_ep {

namespace {

// Checks whether `path` starts with the given directory prefix (component-wise).
bool HasPathComponentPrefix(const std::filesystem::path& prefix, const std::filesystem::path& path) {
auto [prefix_end, path_it] = std::mismatch(prefix.begin(), prefix.end(), path.begin(), path.end());
return prefix_end == prefix.end();
}

// Validates that ep_cache_context resolves to a path within the model directory.
// Resolves symlinks via weakly_canonical to prevent traversal attacks.
Status ValidateEpCacheContextPath(const std::filesystem::path& model_path,
const std::string& ep_cache_context) {
if (ep_cache_context.empty()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL, "ep_cache_context must not be empty");
}

std::filesystem::path cache_path(ep_cache_context);

// Reject absolute paths (covers both Unix '/' and Windows 'C:\' style).
if (!cache_path.root_path().empty()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"ep_cache_context must be a relative path, but got: ", ep_cache_context);
}

// Determine model directory.
std::filesystem::path model_dir = model_path.empty() || model_path.parent_path().empty()
? std::filesystem::path{"."}
: model_path.parent_path();

// Resolve both paths to their weakly canonical forms (resolves symlinks for existing
// path components, normalizes the rest).
std::error_code ec;
auto model_dir_canonical = std::filesystem::weakly_canonical(model_dir, ec);
if (ec) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"Failed to resolve model directory: ", model_dir.string(), " - ", ec.message());
}

auto cache_path_canonical = std::filesystem::weakly_canonical(model_dir / cache_path, ec);
if (ec) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"Failed to resolve ep_cache_context path: ", (model_dir / cache_path).string(),
" - ", ec.message());
}

// Verify the resolved cache path is contained within the model directory.
if (HasPathComponentPrefix(model_dir_canonical, cache_path_canonical)) {
return Status::OK();
}

// The model file itself may be a symlink (e.g., Hugging Face Hub cache).
// Check against the real model directory after resolving all symlinks.
if (!model_path.empty() && std::filesystem::is_symlink(model_path, ec) && !ec) {
auto real_model_path = std::filesystem::weakly_canonical(model_path, ec);
if (!ec) {
auto real_model_dir = real_model_path.parent_path();
if (HasPathComponentPrefix(real_model_dir, cache_path_canonical)) {
return Status::OK();
}
}
}

return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"ep_cache_context path escapes model directory. ",
"ep_cache_context: ", ep_cache_context,
", resolved path: ", cache_path_canonical.string(),
", model directory: ", model_dir_canonical.string());
}

} // namespace

EPCtxHandler::EPCtxHandler(std::string ov_sdk_version, const logging::Logger& logger, std::shared_ptr<SharedContextManager> shared_context_manager)
: openvino_sdk_version_(std::move(ov_sdk_version)), logger_(logger), shared_context_manager_(std::move(shared_context_manager)) {
ORT_ENFORCE(shared_context_manager_ != nullptr, "SharedContextManager pointer is null in EPCtxHandler constructor.");
Expand Down Expand Up @@ -114,6 +186,7 @@
if (blob_filepath.empty() && !graph_viewer.ModelPath().empty()) {
blob_filepath = graph_viewer.ModelPath();
}
ORT_THROW_IF_ERROR(ValidateEpCacheContextPath(blob_filepath, ep_cache_context));
blob_filepath = blob_filepath.parent_path() / ep_cache_context;
ORT_ENFORCE(std::filesystem::exists(blob_filepath), "Blob file not found: ", blob_filepath.string());
result.reset((std::istream*)new std::ifstream(blob_filepath, std::ios_base::binary | std::ios_base::in));
Expand Down Expand Up @@ -242,6 +315,7 @@
shared_context->Deserialize(ss);
}
} else {
ORT_THROW_IF_ERROR(ValidateEpCacheContextPath(session_context.GetOutputModelPath(), ep_cache_context));
std::filesystem::path ep_context_path = session_context.GetOutputModelPath().parent_path() / ep_cache_context;
if (ep_context_path.extension() != ".xml") {
shared_context = shared_context_manager_->GetOrCreateSharedContext(ep_context_path);
Expand Down
Loading