Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
63 changes: 30 additions & 33 deletions onnxruntime/core/framework/tensorprotoutils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,8 @@ static bool HasPathComponentPrefix(const std::filesystem::path& prefix, const st
return prefix_end == prefix.end();
}

Status ValidateExternalDataPath(const std::filesystem::path& model_path,
const std::filesystem::path& external_data_path) {
Status ValidateExternalDataPathFromDir(const std::filesystem::path& model_dir,
const std::filesystem::path& external_data_path) {
Comment thread
yuslepukhin marked this conversation as resolved.
Comment thread
yuslepukhin marked this conversation as resolved.
ORT_RETURN_IF(external_data_path.empty(), "Empty external data path not allowed");

// Note: Use !root_path().empty() to reject paths like '/some/path` even on Windows.
Expand All @@ -420,17 +420,14 @@ Status ValidateExternalDataPath(const std::filesystem::path& model_path,
}
#endif

// Determine the model directory: use model file's parent directory if provided,
// otherwise use the current working directory.
std::filesystem::path model_dir = model_path.empty() || model_path.parent_path().empty()
? std::filesystem::path{"."}
: model_path.parent_path();
// Use the provided directory, falling back to the current working directory if empty.
std::filesystem::path resolved_dir = model_dir.empty() ? std::filesystem::path{"."} : model_dir;

// Resolve the model directory and the external data path to their weakly canonical forms, which
// resolves symlinks but does not require that the paths actually exist yet.
std::filesystem::path model_dir_canonical;
std::filesystem::path external_data_path_canonical;
ORT_RETURN_IF_ERROR(WeaklyCanonicalPath(model_dir, model_dir_canonical));
ORT_RETURN_IF_ERROR(WeaklyCanonicalPath(resolved_dir, model_dir_canonical));
ORT_RETURN_IF_ERROR(WeaklyCanonicalPath(model_dir_canonical / external_data_path, external_data_path_canonical));

// Check that the external data path is contained by the model directory.
Expand All @@ -442,11 +439,30 @@ Status ValidateExternalDataPath(const std::filesystem::path& model_path,
return Status::OK();
}

return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"External data path escapes allowed directory. ",
"External data path: ", external_data_path, " resolved path: ",
external_data_path_canonical, " ", "allowed directory: ", resolved_dir);
}

Status ValidateExternalDataPath(const std::filesystem::path& model_path,
const std::filesystem::path& external_data_path) {
// Determine the model directory: use model file's parent directory if provided,
// otherwise use the current working directory.
std::filesystem::path model_dir = model_path.empty() || model_path.parent_path().empty()
? std::filesystem::path{"."}
: model_path.parent_path();

Status status = ValidateExternalDataPathFromDir(model_dir, external_data_path);
if (status.IsOK()) {
return status;
}

// If model_path is empty (model loaded from bytes), provide a specific error message.
if (model_path.empty()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
Comment thread
yuslepukhin marked this conversation as resolved.
"External data path for model loaded from bytes escapes working directory. ",
"External data path: ", external_data_path, " resolved path: ",
external_data_path_canonical, " ", "working directory: ", model_dir);
status.ErrorMessage());
}

// The model file itself may be a symlink. Therefore, check against the real/canonical directory of the model
Expand All @@ -457,35 +473,16 @@ Status ValidateExternalDataPath(const std::filesystem::path& model_path,
std::error_code ec;
if (!std::filesystem::is_symlink(model_path, ec)) {
// Note: is_symlink returns false if file is not a symlink, file does not exist, or an error
// occurred (e.g., permissions). In any of these cases, we just return an error.
std::string fs_error_msg;
if (ec) {
fs_error_msg = " filesystem::is_symlink error: " + ec.message();
}

return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"External data path for model escapes model directory. ",
"External data path: ", external_data_path, " resolved path: ",
external_data_path_canonical, " ", "model directory: ", model_dir, fs_error_msg);
// occurred (e.g., permissions). In any of these cases, we just return the original error.
return status;
}

std::filesystem::path real_model_path;
ORT_RETURN_IF_ERROR(WeaklyCanonicalPath(model_path, real_model_path));
auto real_model_dir = real_model_path.parent_path();

// Check that the external data path is contained by the real model directory.
// If it is, check if the external data file actually exists.
if (HasPathComponentPrefix(real_model_dir, external_data_path_canonical)) {
bool path_exists = false;
ORT_RETURN_IF_ERROR(PathExists(external_data_path_canonical, path_exists));
ORT_RETURN_IF(!path_exists, "External data path does not exist: ", external_data_path_canonical);
return Status::OK();
}

return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"External data path: ", external_data_path, " (resolved path: ",
external_data_path_canonical, ") escapes both model directory: ", model_dir,
" and real model directory: ", real_model_dir);
// Check against the real/canonical model directory.
return ValidateExternalDataPathFromDir(real_model_dir, external_data_path);
}

Status GetExternalDataInfo(const ONNX_NAMESPACE::TensorProto& tensor_proto,
Expand Down
13 changes: 13 additions & 0 deletions onnxruntime/core/framework/tensorprotoutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,19 @@ Status TensorProtoWithExternalDataToTensorProto(
Status ValidateExternalDataPath(const std::filesystem::path& model_path,
const std::filesystem::path& external_data_path);

/// <summary>
/// Validates that the given external data path is not an absolute path, is under the given directory
/// (after resolving symlinks), and exists.
///
/// Use this overload when you already have the directory that should contain the external data
/// (e.g., EP context model directories). Use ValidateExternalDataPath() when you have a model file path.
/// </summary>
/// <param name="model_dir">Directory that should contain the external data. Falls back to "." if empty.</param>
/// <param name="external_data_path">External data file path to be validated (must be relative).</param>
/// <returns>The function will fail if the resolved `external_data_path` path is not under model_dir</returns>
Status ValidateExternalDataPathFromDir(const std::filesystem::path& model_dir,
const std::filesystem::path& external_data_path);

#endif // !defined(SHARED_PROVIDER)

inline bool HasType(const ONNX_NAMESPACE::AttributeProto& at_proto) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2282,17 +2282,10 @@ common::Status NvExecutionProvider::RefitEngine(std::string onnx_model_filename,
"The ONNX model was not provided as path. "
"Please use provide an ONNX bytestream to enable refitting the weightless engine.");
} else {
// check if file path to ONNX is legal
if (path_check && IsAbsolutePath(onnx_model_path.string())) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"For security purpose, the ONNX model path should be set with "
"a relative path, but it is an absolute path: " +
onnx_model_path.string());
}
if (path_check && IsRelativePathToParentPath(onnx_model_path.string())) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"The ONNX model path has '..'. For security purpose, it's not "
"allowed to point outside the directory.");
// Validate that the ONNX model path does not escape the model directory.
if (path_check && !onnx_model_filename.empty()) {
ORT_RETURN_IF_ERROR(utils::ValidateExternalDataPathFromDir(
std::filesystem::path(onnx_model_folder_path), std::filesystem::path(onnx_model_filename)));
}

if (!(std::filesystem::exists(onnx_model_path) && std::filesystem::is_regular_file(onnx_model_path))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,18 +350,12 @@ Status TensorRTCacheModelHandler::GetEpContextFromGraph(const Node& node) {
// Get engine from cache file.
std::string cache_path = attrs.at(EP_CACHE_CONTEXT).s();

// For security purpose, in the case of running context model, TRT EP won't allow
// engine cache path to be the relative path like "../file_path" or the absolute path.
// It only allows the engine cache to be in the same directory or sub directory of the context model.
if (IsAbsolutePath(cache_path)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL, "For security purpose, the ep_cache_context attribute should be set with a relative path, but it is an absolute path: " + cache_path);
}
if (IsRelativePathToParentPath(cache_path)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL, "The file path in ep_cache_context attribute has '..'. For security purpose, it's not allowed to point outside the directory.");
}
// Validate that the cache path does not escape the model directory.
// Rejects absolute paths, ".." traversal, and symlink-based escapes.
std::filesystem::path ctx_model_dir(GetPathOrParentPathOfCtxModel(ep_context_model_path_));
ORT_RETURN_IF_ERROR(utils::ValidateExternalDataPathFromDir(ctx_model_dir, std::filesystem::path(cache_path)));

// The engine cache and context model (current model) should be in the same directory
std::filesystem::path ctx_model_dir(GetPathOrParentPathOfCtxModel(ep_context_model_path_));
auto engine_cache_path = ctx_model_dir.append(cache_path);
LOGS_DEFAULT(VERBOSE) << "[NvTensorRTRTX EP] GetEpContextFromGraph engine_cache_path: " + engine_cache_path.string();

Expand Down
3 changes: 3 additions & 0 deletions onnxruntime/core/providers/openvino/onnx_ctx_model_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ std::unique_ptr<ModelBlobWrapper> EPCtxHandler::GetModelBlobStream(const std::fi
if (blob_filepath.empty() && !graph_viewer.ModelPath().empty()) {
blob_filepath = graph_viewer.ModelPath();
}
ORT_THROW_IF_ERROR(utils::ValidateExternalDataPath(blob_filepath, std::filesystem::path(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 +243,8 @@ std::shared_ptr<SharedContext> EPCtxHandler::Initialize(const std::vector<IExecu
shared_context->Deserialize(ss);
}
} else {
ORT_THROW_IF_ERROR(utils::ValidateExternalDataPath(session_context.GetOutputModelPath(),
std::filesystem::path(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
27 changes: 8 additions & 19 deletions onnxruntime/core/providers/qnn/builder/onnx_ctx_model_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,27 +103,16 @@ Status GetEpContextFromMainNode(const onnxruntime::Node& main_context_node,
std::filesystem::path folder_path = std::filesystem::path(ctx_onnx_model_path).parent_path();
std::string external_qnn_ctx_binary_file_name = node_helper.Get(EP_CACHE_CONTEXT, "");
ORT_RETURN_IF(external_qnn_ctx_binary_file_name.empty(), "The file path in ep_cache_context should not be empty.");
#ifdef _WIN32
onnxruntime::PathString external_qnn_context_binary_path = onnxruntime::ToPathString(external_qnn_ctx_binary_file_name);
auto ctx_file_path = std::filesystem::path(external_qnn_context_binary_path.c_str());
ORT_RETURN_IF(ctx_file_path.is_absolute(), "External mode should set ep_cache_context field with a relative path, but it is an absolute path: ",
external_qnn_ctx_binary_file_name);
auto relative_path = ctx_file_path.lexically_normal().make_preferred().wstring();
if (relative_path.find(L"..", 0) != std::string::npos) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, "The file path in ep_cache_context field has '..'. It's not allowed to point outside the directory.");
}

std::filesystem::path context_binary_path = folder_path.append(relative_path);
#else
ORT_RETURN_IF(external_qnn_ctx_binary_file_name[0] == '/',
"External mode should set ep_cache_context field with a relative path, but it is an absolute path: ",
external_qnn_ctx_binary_file_name);
if (external_qnn_ctx_binary_file_name.find("..", 0) != std::string::npos) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, "The file path in ep_cache_context field has '..'. It's not allowed to point outside the directory.");
// Validate that the cache path does not escape the model directory.
// Rejects absolute paths, ".." traversal, and symlink-based escapes.
auto validate_status = ::onnxruntime::utils::ValidateExternalDataPath(
std::filesystem::path(ctx_onnx_model_path), std::filesystem::path(external_qnn_ctx_binary_file_name));
if (!validate_status.IsOK()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, validate_status.ErrorMessage());
}
std::filesystem::path context_binary_path = folder_path.append(external_qnn_ctx_binary_file_name);
std::string file_full_path = context_binary_path.string();
#endif

std::filesystem::path context_binary_path = folder_path / external_qnn_ctx_binary_file_name;
if (!std::filesystem::is_regular_file(context_binary_path)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, "The file path in ep_cache_context does not exist or is not accessible.");
}
Expand Down
11 changes: 10 additions & 1 deletion onnxruntime/core/providers/qnn/qnn_execution_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1007,8 +1007,17 @@ QNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_viewer

for (auto& ep_ctx_node : ep_ctx_nodes) {
NodeAttrHelper node_helper(*ep_ctx_node);
std::string ep_cache_context_value = node_helper.Get(qnn::EP_CACHE_CONTEXT, "");
if (ep_cache_context_value.empty()) {
Comment thread
yuslepukhin marked this conversation as resolved.
continue;
}
auto validate_status = utils::ValidateExternalDataPath(
std::filesystem::path(context_model_path), std::filesystem::path(ep_cache_context_value));
if (!validate_status.IsOK()) {
ORT_THROW_IF_ERROR(ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, validate_status.ErrorMessage()));
}
Comment thread
yuslepukhin marked this conversation as resolved.
std::string context_bin_filepath(parent_path.string());
context_bin_filepath.append("/").append(node_helper.Get(qnn::EP_CACHE_CONTEXT, ""));
context_bin_filepath.append("/").append(ep_cache_context_value);
if (context_bin_map.find(context_bin_filepath) == context_bin_map.end()) {
context_bin_map.emplace(context_bin_filepath, std::make_unique<std::vector<std::string>>());
// Push context bin filepath for lookup between sessions
Expand Down
10 changes: 10 additions & 0 deletions onnxruntime/core/providers/shared_library/provider_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,16 @@ inline bool HasExternalDataInMemory(const ONNX_NAMESPACE::TensorProto& ten_proto
return g_host->Utils__HasExternalDataInMemory(ten_proto);
}

inline Status ValidateExternalDataPath(const std::filesystem::path& model_path,
const std::filesystem::path& external_data_path) {
return g_host->Utils__ValidateExternalDataPath(model_path, external_data_path);
}

inline Status ValidateExternalDataPathFromDir(const std::filesystem::path& model_dir,
const std::filesystem::path& external_data_path) {
return g_host->Utils__ValidateExternalDataPathFromDir(model_dir, external_data_path);
}

} // namespace utils

namespace graph_utils {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,11 @@ struct ProviderHost {

virtual bool Utils__HasExternalDataInMemory(const ONNX_NAMESPACE::TensorProto& ten_proto) = 0;

virtual Status Utils__ValidateExternalDataPath(const std::filesystem::path& model_path,
const std::filesystem::path& external_data_path) = 0;
virtual Status Utils__ValidateExternalDataPathFromDir(const std::filesystem::path& model_dir,
const std::filesystem::path& external_data_path) = 0;

// Model
virtual std::unique_ptr<Model> Model__construct(ONNX_NAMESPACE::ModelProto&& model_proto, const PathString& model_path,
const IOnnxRuntimeOpSchemaRegistryList* local_registries,
Expand Down
14 changes: 4 additions & 10 deletions onnxruntime/core/providers/tensorrt/onnx_ctx_model_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,12 @@ Status TensorRTCacheModelHandler::GetEpContextFromGraph(const GraphViewer& graph
// Get engine from cache file.
std::string cache_path = attrs.at(EP_CACHE_CONTEXT).s();

// For security purpose, in the case of running context model, TRT EP won't allow
// engine cache path to be the relative path like "../file_path" or the absolute path.
// It only allows the engine cache to be in the same directory or sub directory of the context model.
if (IsAbsolutePath(cache_path)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL, "For security purpose, the ep_cache_context attribute should be set with a relative path, but it is an absolute path: " + cache_path);
}
if (IsRelativePathToParentPath(cache_path)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL, "The file path in ep_cache_context attribute has '..'. For security purpose, it's not allowed to point outside the directory.");
}
// Validate that the cache path does not escape the model directory.
// Rejects absolute paths, ".." traversal, and symlink-based escapes.
std::filesystem::path ctx_model_dir(GetPathOrParentPathOfCtxModel(ep_context_model_path_));
ORT_RETURN_IF_ERROR(utils::ValidateExternalDataPathFromDir(ctx_model_dir, std::filesystem::path(cache_path)));

// The engine cache and context model (current model) should be in the same directory
std::filesystem::path ctx_model_dir(GetPathOrParentPathOfCtxModel(ep_context_model_path_));
auto engine_cache_path = ctx_model_dir.append(cache_path);
LOGS_DEFAULT(VERBOSE) << "[TensorRT EP] GetEpContextFromGraph engine_cache_path: " + engine_cache_path.string();

Expand Down
15 changes: 4 additions & 11 deletions onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2895,17 +2895,10 @@ common::Status TensorrtExecutionProvider::RefitEngine(std::string onnx_model_fil
"Please use provide an ONNX bytestream to enable refitting the weightless engine."
"When providing a bytestream during session initialization, it should also be set as trt_onnx_bytes_stream");
} else {
// check if file path to ONNX is legal
if (path_check && IsAbsolutePath(onnx_model_path.string())) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"For security purpose, the ONNX model path should be set with "
"a relative path, but it is an absolute path: " +
onnx_model_path.string());
}
if (path_check && IsRelativePathToParentPath(onnx_model_path.string())) {
return ORT_MAKE_STATUS(ONNXRUNTIME, EP_FAIL,
"The ONNX model path has '..'. For security purpose, it's not "
"allowed to point outside the directory.");
// Validate that the ONNX model path does not escape the model directory.
if (path_check && !onnx_model_filename.empty()) {
ORT_RETURN_IF_ERROR(utils::ValidateExternalDataPathFromDir(
std::filesystem::path(onnx_model_folder_path), std::filesystem::path(onnx_model_filename)));
}

if (!(std::filesystem::exists(onnx_model_path) && std::filesystem::is_regular_file(onnx_model_path))) {
Expand Down
Loading
Loading