@@ -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 (
0 commit comments