diff --git a/onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc b/onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc index 80ce4de4a6a19..662222bead3a8 100644 --- a/onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc +++ b/onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include "core/providers/openvino/onnx_ctx_model_helper.h" #include "core/providers/openvino/backend_utils.h" @@ -12,6 +13,77 @@ 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 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."); @@ -114,6 +186,7 @@ std::unique_ptr EPCtxHandler::GetModelBlobStream(const std::fi 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)); @@ -242,6 +315,7 @@ std::shared_ptr EPCtxHandler::Initialize(const std::vectorDeserialize(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);