From 67f700b4bf015b36e1efcb9942c75d46d28c9e6a Mon Sep 17 00:00:00 2001 From: cadenasynyster <8373765+cadenasynyster@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:52:35 +0200 Subject: [PATCH 1/3] fix(libc): report ELOOP from opendir of a Windows file-type link loop OpenNative returned ENOTDIR for any path whose own attributes lack FILE_ATTRIBUTE_DIRECTORY. A symbolic link created without SYMBOLIC_LINK_FLAG_DIRECTORY that points to itself is such a path, so opendir reported ENOTDIR where FreeBSD's lookup fails with ELOOP. A reparse point without the directory flag is now followed with CreateFileW, and ERROR_CANT_RESOLVE_FILENAME gives ELOOP. Any other result keeps ENOTDIR, including a file-type link whose target is a directory. The guest_directory symbolic link loop check, skipped on Windows, now runs there with a file-type link and a directory-type link. MinGW's std::filesystem::create_symlink does not work on Windows, so the links are created with CreateSymbolicLinkW. --- core/libs/prx/libc/src/Directory.cpp | 11 ++++++++++- core/libs/tests/GuestDirectory.cpp | 13 ++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libc/src/Directory.cpp b/core/libs/prx/libc/src/Directory.cpp index 425669cdcf..b979422103 100644 --- a/core/libs/prx/libc/src/Directory.cpp +++ b/core/libs/prx/libc/src/Directory.cpp @@ -53,7 +53,16 @@ int NativeOpenError(DWORD error) { NativeDirectory* OpenNative(const std::filesystem::path& path) { const DWORD attributes = GetFileAttributesW(path.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES) { errno = NativeOpenError(GetLastError()); return nullptr; } - if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { errno = ENOTDIR; return nullptr; } + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + errno = ENOTDIR; + if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + const HANDLE followed = CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (followed != INVALID_HANDLE_VALUE) CloseHandle(followed); + else if (GetLastError() == ERROR_CANT_RESOLVE_FILENAME) errno = ELOOP; + } + return nullptr; + } auto native = std::make_unique_for_overwrite(); native->handle = CreateFileW(path.c_str(), FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); diff --git a/core/libs/tests/GuestDirectory.cpp b/core/libs/tests/GuestDirectory.cpp index 4df4394622..65fadc4bf8 100644 --- a/core/libs/tests/GuestDirectory.cpp +++ b/core/libs/tests/GuestDirectory.cpp @@ -172,11 +172,22 @@ int main() { Require(closedir_nid_postfix(directory) == 0); Require(opendir_nid_postfix((root / "missing").string().c_str()) == nullptr); Require(*__error_nid_postfix() == 2); -#ifndef _WIN32 const auto symlinkLoop = root / "symlink-loop"; +#ifdef _WIN32 + Require(CreateSymbolicLinkW(symlinkLoop.c_str(), L"symlink-loop", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); +#else std::filesystem::create_symlink("symlink-loop", symlinkLoop); +#endif Require(opendir_nid_postfix(symlinkLoop.string().c_str()) == nullptr); Require(*__error_nid_postfix() == 62); +#ifdef _WIN32 + Require(DeleteFileW(symlinkLoop.c_str()) != 0); + const auto directoryLoop = root / "directory-loop"; + Require(CreateSymbolicLinkW(directoryLoop.c_str(), L"directory-loop", SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + Require(opendir_nid_postfix(directoryLoop.string().c_str()) == nullptr); + Require(*__error_nid_postfix() == 62); + Require(RemoveDirectoryW(directoryLoop.c_str()) != 0); +#else Require(std::filesystem::remove(symlinkLoop)); #endif Require(opendir_nid_postfix((root / "sample.txt").string().c_str()) == nullptr); From 72a3b3095796621e94946d74073b8c82bd43eb02 Mon Sep 17 00:00:00 2001 From: cadenasynyster <8373765+cadenasynyster@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:15:00 +0200 Subject: [PATCH 2/3] test(libc): skip the opendir Windows loop checks when links cannot be created The new Windows loop cases in guest_directory required CreateSymbolicLinkW to succeed, so the test aborted on a host without Developer Mode or administrator rights. They now run only when CreateLinks created its links, which is how the other link checks in the file already skip, and keep their ELOOP assertions whenever link creation is allowed. --- core/libs/tests/GuestDirectory.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/core/libs/tests/GuestDirectory.cpp b/core/libs/tests/GuestDirectory.cpp index 65fadc4bf8..95ada5d261 100644 --- a/core/libs/tests/GuestDirectory.cpp +++ b/core/libs/tests/GuestDirectory.cpp @@ -174,20 +174,22 @@ int main() { Require(*__error_nid_postfix() == 2); const auto symlinkLoop = root / "symlink-loop"; #ifdef _WIN32 - Require(CreateSymbolicLinkW(symlinkLoop.c_str(), L"symlink-loop", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + // CreateLinks already found out whether this host may create symbolic links; skip the loop checks the same way when it may not. + if (links) { + Require(CreateSymbolicLinkW(symlinkLoop.c_str(), L"symlink-loop", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + Require(opendir_nid_postfix(symlinkLoop.string().c_str()) == nullptr); + Require(*__error_nid_postfix() == 62); + Require(DeleteFileW(symlinkLoop.c_str()) != 0); + const auto directoryLoop = root / "directory-loop"; + Require(CreateSymbolicLinkW(directoryLoop.c_str(), L"directory-loop", SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + Require(opendir_nid_postfix(directoryLoop.string().c_str()) == nullptr); + Require(*__error_nid_postfix() == 62); + Require(RemoveDirectoryW(directoryLoop.c_str()) != 0); + } #else std::filesystem::create_symlink("symlink-loop", symlinkLoop); -#endif Require(opendir_nid_postfix(symlinkLoop.string().c_str()) == nullptr); Require(*__error_nid_postfix() == 62); -#ifdef _WIN32 - Require(DeleteFileW(symlinkLoop.c_str()) != 0); - const auto directoryLoop = root / "directory-loop"; - Require(CreateSymbolicLinkW(directoryLoop.c_str(), L"directory-loop", SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); - Require(opendir_nid_postfix(directoryLoop.string().c_str()) == nullptr); - Require(*__error_nid_postfix() == 62); - Require(RemoveDirectoryW(directoryLoop.c_str()) != 0); -#else Require(std::filesystem::remove(symlinkLoop)); #endif Require(opendir_nid_postfix((root / "sample.txt").string().c_str()) == nullptr); From a5896a447cf29c276d75040a5fa371bc01623c50 Mon Sep 17 00:00:00 2001 From: cadenasynyster <8373765+cadenasynyster@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:39:32 +0200 Subject: [PATCH 3/3] test(libc): drop the comment from the opendir Windows loop skip The conventions check allows comments only for technical debt, so the explanatory comment added with the skip is removed. The code is unchanged. --- core/libs/tests/GuestDirectory.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/libs/tests/GuestDirectory.cpp b/core/libs/tests/GuestDirectory.cpp index 95ada5d261..d68838cb35 100644 --- a/core/libs/tests/GuestDirectory.cpp +++ b/core/libs/tests/GuestDirectory.cpp @@ -174,7 +174,6 @@ int main() { Require(*__error_nid_postfix() == 2); const auto symlinkLoop = root / "symlink-loop"; #ifdef _WIN32 - // CreateLinks already found out whether this host may create symbolic links; skip the loop checks the same way when it may not. if (links) { Require(CreateSymbolicLinkW(symlinkLoop.c_str(), L"symlink-loop", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); Require(opendir_nid_postfix(symlinkLoop.string().c_str()) == nullptr);