Skip to content
Open
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
129 changes: 85 additions & 44 deletions src/models/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
#include <climits>
#include <filesystem>
#include <random>
#include <set>
#include <string>
Expand Down Expand Up @@ -680,58 +681,98 @@

std::string custom_library_file_prefix = config_session_options.custom_ops_library.value();

// If relative path, try to resolve using multiple search locations
fs::path custom_library_path{custom_library_file_prefix};
if (custom_library_path.is_relative()) {
bool resolved = false;
std::filesystem::path custom_library_path{custom_library_file_prefix};

// First try: resolve relative to GenAI model folder (most intuitive for users)
fs::path model_relative_path = config_->config_path / custom_library_path;
if (fs::exists(model_relative_path)) {
custom_library_file_prefix = model_relative_path.string();
resolved = true;
}
// Reject path traversal components regardless of absolute/relative
if (custom_library_file_prefix.find("..") != std::string::npos) {
throw std::runtime_error("custom_ops_library must not contain path traversal (..): " + custom_library_file_prefix);
}

// Second try: resolve relative to EP library directory (for system-wide installations)
if (!resolved) {
size_t num_devices = 0;
const OrtEpDevice* const* device_ptrs = nullptr;
Ort::GetEpDevices(&GetOrtEnv(), &device_ptrs, &num_devices);

for (size_t i = 0; i < num_devices && !resolved; ++i) {
const OrtKeyValuePairs* keyvals = Ort::GetEpDeviceMetadata(device_ptrs[i]);
size_t num_entries = 0;
const char* const* keys = nullptr;
const char* const* values = nullptr;
Ort::GetKeyValuePairs(keyvals, &keys, &values, &num_entries);

for (size_t kvi = 0; kvi < num_entries; kvi++) {
const std::string key = keys[kvi];
const std::string val = values[kvi];
if (key == library_path_metadata_key_name) {
fs::path ep_library_dir = fs::path(val).parent_path();
fs::path resolved_path = ep_library_dir / custom_library_path;
if (fs::exists(resolved_path)) {
custom_library_file_prefix = resolved_path.string();
resolved = true;
break;
}
}
// Build the set of allowed directories. The resolved library path must fall
// within one of these to prevent loading arbitrary libraries from disk.
std::vector<std::filesystem::path> allowed_dirs;

// 1. GenAI model folder
std::error_code ec;
auto model_dir = std::filesystem::canonical(config_->config_path, ec);

Check failure on line 697 in src/models/model.cpp

View workflow job for this annotation

GitHub Actions / windows-cuda-x64-build

'std::filesystem::canonical': no overloaded function could convert all the argument types

Check failure on line 697 in src/models/model.cpp

View workflow job for this annotation

GitHub Actions / windows-cuda-x64-build

'std::filesystem::canonical': no overloaded function could convert all the argument types

Check failure on line 697 in src/models/model.cpp

View workflow job for this annotation

GitHub Actions / windows-cpu-x64-build

'std::filesystem::canonical': no overloaded function could convert all the argument types

Check failure on line 697 in src/models/model.cpp

View workflow job for this annotation

GitHub Actions / windows-webgpu-x64-build

'std::filesystem::canonical': no overloaded function could convert all the argument types
if (!ec) allowed_dirs.push_back(model_dir);

// 2. EP library directories
{
size_t num_devices = 0;
const OrtEpDevice* const* device_ptrs = nullptr;
Ort::GetEpDevices(&GetOrtEnv(), &device_ptrs, &num_devices);

for (size_t i = 0; i < num_devices; ++i) {
const OrtKeyValuePairs* keyvals = Ort::GetEpDeviceMetadata(device_ptrs[i]);
size_t num_entries = 0;
const char* const* keys = nullptr;
const char* const* values = nullptr;
Ort::GetKeyValuePairs(keyvals, &keys, &values, &num_entries);

for (size_t kvi = 0; kvi < num_entries; kvi++) {
if (std::string(keys[kvi]) == library_path_metadata_key_name) {
auto ep_dir = std::filesystem::canonical(std::filesystem::path(values[kvi]).parent_path(), ec);
if (!ec) allowed_dirs.push_back(ep_dir);
}
}
}
}

// 3. Current working directory
{
char cwd_buffer[PATH_MAX];
if (GETCWD(cwd_buffer, sizeof(cwd_buffer))) {
auto cwd_dir = std::filesystem::canonical(std::filesystem::path(cwd_buffer), ec);
if (!ec) allowed_dirs.push_back(cwd_dir);
}
}

// Third try: resolve relative to current working directory (for development/portable apps)
if (!resolved) {
char cwd_buffer[PATH_MAX];
if (GETCWD(cwd_buffer, sizeof(cwd_buffer))) {
fs::path cwd_relative_path = fs::path(cwd_buffer) / custom_library_path;
if (fs::exists(cwd_relative_path)) {
custom_library_file_prefix = cwd_relative_path.string();
resolved = true;
}
// Resolve the library path. For relative paths, search allowed directories in order.
// For absolute paths, use as-is and validate against the allowlist.
bool resolved = false;
if (custom_library_path.is_relative()) {
for (const auto& dir : allowed_dirs) {
std::filesystem::path candidate = dir / custom_library_path;
if (std::filesystem::exists(candidate, ec)) {
custom_library_file_prefix = std::filesystem::canonical(candidate).string();
resolved = true;
break;
}
}
} else {
// Absolute path — canonicalize and validate below
auto canonical_path = std::filesystem::canonical(custom_library_path, ec);
if (!ec && std::filesystem::exists(canonical_path, ec)) {
custom_library_file_prefix = canonical_path.string();
resolved = true;
}
}

// Validate that the resolved path is under one of the allowed directories
if (resolved) {
std::filesystem::path resolved_canonical =
std::filesystem::canonical(std::filesystem::path(custom_library_file_prefix), ec);
if (ec) {
throw std::runtime_error("Failed to canonicalize custom_ops_library path: " + custom_library_file_prefix);
}
bool in_allowed_dir = false;
auto resolved_str = resolved_canonical.string();
for (const auto& dir : allowed_dirs) {
auto dir_str = dir.string();
// Check that resolved path starts with the allowed directory prefix
if (resolved_str.size() >= dir_str.size() &&
resolved_str.compare(0, dir_str.size(), dir_str) == 0 &&
(resolved_str.size() == dir_str.size() ||
resolved_str[dir_str.size()] == std::filesystem::path::preferred_separator)) {
in_allowed_dir = true;
break;
}
}
if (!in_allowed_dir) {
throw std::runtime_error(
"custom_ops_library path is not within an allowed directory: " + custom_library_file_prefix);
}
}

// Convert to fs::path for proper wide string handling on Windows
Expand Down
Loading