Skip to content

Commit 4f70b56

Browse files
committed
Properly detect symlinks
1 parent 0853aa8 commit 4f70b56

File tree

2 files changed

+55
-11
lines changed

2 files changed

+55
-11
lines changed

onnxruntime/core/framework/tensorprotoutils.cc

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -341,24 +341,46 @@ Status ParseWhiteListedPaths(const PathString& paths_str,
341341
if (p_str.empty()) return Status::OK();
342342
std::filesystem::path path(p_str);
343343
std::error_code ec;
344-
// Validate: absolute, exists, not a symlink, and is a directory
345344
if (!path.is_absolute()) {
346345
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
347346
"Whitelisted data path is not absolute: ", path.string());
348347
}
349-
if (!std::filesystem::exists(path, ec) || ec) {
348+
// canonical() resolves all symlinks and requires the path to exist.
349+
// If it fails, the path either doesn't exist or can't be resolved.
350+
auto canonical_path = std::filesystem::canonical(path, ec);
351+
if (ec) {
350352
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
351-
"Whitelisted data path does not exist: ", path.string());
353+
"Whitelisted data path does not exist or cannot be resolved: ", path.string());
352354
}
353-
if (std::filesystem::is_symlink(path, ec) || ec) {
354-
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
355-
"Whitelisted data path is a symlink: ", path.string());
355+
// Walk each component of the canonical path and check for symlinks.
356+
// We choose with approach because both canonical() and weakly_canonical() on Windows
357+
// (MSVC's <filesystem> implementation) resolve symlinks for existing path components
358+
// using the same underlying Win32 API (GetFinalPathNameByHandle).
359+
// So comparing them always produces an equal result, making symlink detection impossible via comparison.
360+
// We check the canonical path (not the original) so that normalization differences
361+
// (trailing slashes, "..", ".") don't interfere, while still detecting symlinks
362+
// that may exist along the resolved path.
363+
{
364+
auto normalized = path.lexically_normal();
365+
std::filesystem::path accumulated;
366+
for (const auto& component : normalized) {
367+
accumulated /= component;
368+
// Skip checking the root (e.g. "C:\" or "/") since is_symlink would fail or be meaningless.
369+
if (accumulated == normalized.root_path()) {
370+
continue;
371+
}
372+
if (std::filesystem::is_symlink(accumulated, ec)) {
373+
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
374+
"Whitelisted data path contains a symlink: ", path.string());
375+
}
376+
}
356377
}
357-
if (!std::filesystem::is_directory(path, ec) || ec) {
378+
379+
if (!std::filesystem::is_directory(canonical_path, ec) || ec) {
358380
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
359381
"Whitelisted data path is not a directory: ", path.string());
360382
}
361-
result.push_back(path);
383+
result.push_back(canonical_path);
362384
return Status::OK();
363385
};
364386

@@ -375,7 +397,6 @@ Status ParseWhiteListedPaths(const PathString& paths_str,
375397
ORT_RETURN_IF_ERROR(process_path(paths_str.substr(start)));
376398

377399
paths = std::move(result);
378-
379400
return Status::OK();
380401
}
381402

@@ -388,7 +409,7 @@ Status ValidateExternalDataPath(const std::filesystem::path& base_dir,
388409
// Resolve and verify the path stays within model directory
389410
auto base_canonical = std::filesystem::weakly_canonical(base_dir);
390411
// If the symlink exists, it resolves to the target path;
391-
// so if the symllink is outside the directory it would be caught here.
412+
// so if the symlink is outside the directory it would be caught here.
392413
auto resolved = std::filesystem::weakly_canonical(base_dir / location);
393414
// Check that resolved path starts with base directory
394415
auto [base_end, resolved_it] = std::mismatch(

onnxruntime/test/framework/tensorutils_test.cc

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,11 @@ class ParseWhiteListedPathsTest : public ::testing::Test {
596596
std::filesystem::create_directories(sub_dir_a_);
597597
std::filesystem::create_directories(sub_dir_b_);
598598

599+
// Canonicalize the paths so that tests can compare against what ParseWhiteListedPaths stores.
600+
test_dir_ = std::filesystem::canonical(test_dir_);
601+
sub_dir_a_ = std::filesystem::canonical(sub_dir_a_);
602+
sub_dir_b_ = std::filesystem::canonical(sub_dir_b_);
603+
599604
// Create a regular file (not a directory)
600605
regular_file_ = test_dir_ / "file.txt";
601606
std::ofstream{regular_file_};
@@ -710,7 +715,25 @@ TEST_F(ParseWhiteListedPathsTest, SymlinkDirectoryReturnsError) {
710715
InlinedVector<std::filesystem::path> paths;
711716
ASSERT_STATUS_NOT_OK_AND_HAS_SUBSTR(
712717
utils::ParseWhiteListedPaths(ToOrtPath(link_path), paths),
713-
"symlink");
718+
"contains a symlink");
719+
}
720+
721+
TEST_F(ParseWhiteListedPathsTest, SymlinkInIntermediateComponentReturnsError) {
722+
// Create: test_dir_/link_to_dir_a -> sub_dir_a_, then use test_dir_/link_to_dir_a/nested as the path.
723+
// Even though the final target is a real directory, the path has a symlink component.
724+
auto nested_dir = sub_dir_a_ / "nested";
725+
std::filesystem::create_directories(nested_dir);
726+
auto link_in_path = test_dir_ / "link_to_dir_a";
727+
try {
728+
std::filesystem::create_directory_symlink(sub_dir_a_, link_in_path);
729+
} catch (const std::exception& e) {
730+
GTEST_SKIP() << "Skipping symlink test: symlink creation not supported. " << e.what();
731+
}
732+
auto path_through_link = link_in_path / "nested";
733+
InlinedVector<std::filesystem::path> paths;
734+
ASSERT_STATUS_NOT_OK_AND_HAS_SUBSTR(
735+
utils::ParseWhiteListedPaths(ToOrtPath(path_through_link), paths),
736+
"contains a symlink");
714737
}
715738

716739
TEST_F(ParseWhiteListedPathsTest, OnlySeparatorsReturnsEmptyVector) {

0 commit comments

Comments
 (0)