Skip to content
Merged
Show file tree
Hide file tree
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
9 changes: 6 additions & 3 deletions src/backend_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,12 @@ TritonModel::GetBackendLibraryProperties(
model_config->name() +
"', searched: " + search_paths_str);
}
if (IsChildPathEscapingParentPath(
*backend_libpath /* child_path */,
*backend_libdir /* parent_path */)) {

bool is_escaped = false;
RETURN_IF_ERROR(IsChildPathEscapingParentPath(
*backend_libpath /* child_path */, *backend_libdir /* parent_path */,
&is_escaped));
if (is_escaped) {
return Status(
Status::Code::INVALID_ARG,
"backend library name '" + cpp_backend_libname +
Expand Down
36 changes: 26 additions & 10 deletions src/filesystem/api.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// Copyright 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
Expand Down Expand Up @@ -399,17 +399,33 @@ IsAbsolutePath(const std::string& path)
return !path.empty() && (path[0] == '/');
}

bool
Status
IsChildPathEscapingParentPath(
const std::string& child_path, const std::string& parent_path)
{
const std::string absolute_child_path =
std::filesystem::weakly_canonical(child_path).string();
const std::string absolute_parent_path =
std::filesystem::canonical(parent_path).string();
const std::string& child_path, const std::string& parent_path,
bool* is_escaped)
{
std::string absolute_child_path;
std::string absolute_parent_path;
try {
absolute_child_path =
std::filesystem::weakly_canonical(child_path).string();
}
catch (const std::exception& e) {
return Status(
Status::Code::INVALID_ARG,
"Invalid path '" + child_path + "': " + e.what());
}
try {
absolute_parent_path = std::filesystem::canonical(parent_path).string();
}
catch (const std::exception& e) {
return Status(
Status::Code::INVALID_ARG,
"Nonexistent path '" + parent_path + "': " + e.what());
}
// Can use starts_with() over rfind() in C++20.
bool is_escape = absolute_child_path.rfind(absolute_parent_path, 0) != 0;
return is_escape;
*is_escaped = absolute_child_path.rfind(absolute_parent_path, 0) != 0;
return Status::Success;
}

std::string
Expand Down
12 changes: 7 additions & 5 deletions src/filesystem/api.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// Copyright 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
Expand Down Expand Up @@ -131,10 +131,12 @@ bool IsAbsolutePath(const std::string& path);
/// Check if the child path escapes from its parent path.
/// \param child_path The child path.
/// \param parent_path The parent path. The path must exist.
/// \return true if the child path escapes from its parent path, false if the
/// child path is within its parent path.
bool IsChildPathEscapingParentPath(
const std::string& child_path, const std::string& parent_path);
/// \param is_escaped returns true if the child path escapes from its parent
/// path, false if the child path is within its parent path. \return Error
/// status
Status IsChildPathEscapingParentPath(
const std::string& child_path, const std::string& parent_path,
bool* is_escaped);

/// Join path segments into a longer path
/// \param segments The path segments.
Expand Down
5 changes: 4 additions & 1 deletion src/model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ Model::Init(const bool is_config_provided)

if (!io.label_filename().empty()) {
auto label_path = JoinPath({model_dir_, io.label_filename()});
if (IsChildPathEscapingParentPath(label_path, model_dir_)) {
bool is_escaped = false;
RETURN_IF_ERROR(
IsChildPathEscapingParentPath(label_path, model_dir_, &is_escaped));
if (is_escaped) {
return Status(
Status::Code::UNSUPPORTED,
"label file path '" + label_path + "' for output '" + io.name() +
Expand Down
43 changes: 31 additions & 12 deletions src/model_repository_manager/model_repository_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,16 @@ class LocalizeRepoAgent : public TritonRepoAgent {
RETURN_TRITONSERVER_ERROR_IF_ERROR(
agent_model->AcquireMutableLocation(
TRITONREPOAGENT_ARTIFACT_FILESYSTEM, &temp_dir_cstr));
const std::string temp_dir =
std::filesystem::canonical(temp_dir_cstr).string();
std::string temp_dir;
try {
temp_dir = std::filesystem::canonical(temp_dir_cstr).string();
}
catch (const std::exception& e) {
const std::string err_msg = std::string("Nonexistent path '") +
temp_dir_cstr + "': " + e.what();
return TRITONSERVER_ErrorNew(
TRITONSERVER_ERROR_INTERNAL, err_msg.c_str());
Comment thread
yinggeh marked this conversation as resolved.
}
const auto& files =
*reinterpret_cast<std::vector<const InferenceParameter*>*>(
agent_model->State());
Expand Down Expand Up @@ -136,20 +144,31 @@ class LocalizeRepoAgent : public TritonRepoAgent {
.c_str());
}

const std::string file_relpath =
file->Name().substr(file_prefix.size());
std::string file_path;
try {
file_path = std::filesystem::weakly_canonical(
JoinPath({temp_dir, file_relpath}))
.string();
}
catch (const std::exception& e) {
const std::string err_msg =
std::string("Invalid file parameter '") + file_relpath +
"': " + e.what();
return TRITONSERVER_ErrorNew(
TRITONSERVER_ERROR_INVALID_ARG, err_msg.c_str());
}

// Resolve any relative paths or symlinks, and enforce that target
// directory stays within model directory for security.
const std::string file_path =
std::filesystem::weakly_canonical(
JoinPath(
{temp_dir, file->Name().substr(file_prefix.size())}))
.string();
if (file_path.rfind(temp_dir, 0) != 0) {
const std::string msg =
std::string("Invalid file parameter '") + file->Name() +
"' with normalized path '" + file_path +
"' must stay within model directory.";
return TRITONSERVER_ErrorNew(
TRITONSERVER_ERROR_INVALID_ARG,
(std::string("Invalid file parameter '") + file->Name() +
"' with normalized path '" + file_path +
"' must stay within model directory.")
.c_str());
TRITONSERVER_ERROR_INVALID_ARG, msg.c_str());
}

// Save model override file to the instructed directory using the
Expand Down
Loading