From c22654102b5c7d981602fd1a1b22d25722ca807f Mon Sep 17 00:00:00 2001 From: David Date: Mon, 12 Oct 2026 19:47:45 +0400 Subject: [PATCH] fix(libc): keep the host standard handles when a guest replaces descriptor 0, 1 or 2 on Windows --- core/libs/prx/libc/include/FileStream.hpp | 7 +++++- core/libs/prx/libc/include/General.hpp | 2 ++ core/libs/prx/libc/src/General.cpp | 23 ++++++++++++++++++ core/libs/prx/libkernel/File/src/Stdio.cpp | 5 +++- core/libs/tests/GuestFilesystem.cpp | 28 ++++++++++++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index 88cee1647..ea5b3e0a1 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -13,6 +13,8 @@ #ifdef _WIN32 #include #include "prx/libc/include/FileLock.hpp" +extern "C" void* HostStandardHandle_nid_no_patch(int descriptor); +extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle); #endif struct GuestFilePrefix { @@ -77,8 +79,11 @@ public: #ifdef _WIN32 std::wstring wideMode; for (const char character : std::string_view(mode)) wideMode.push_back(static_cast(character)); - GuestFileLockForget_nid_no_patch(::_fileno(previous)); + const int replaced = previous ? _fileno(previous) : -1; + auto* const standard = HostStandardHandle_nid_no_patch(replaced); + GuestFileLockForget_nid_no_patch(replaced); _handle = ::_wfreopen(filename.c_str(), wideMode.c_str(), previous); + KeepStandardHandle_nid_no_patch(replaced, standard); #else _handle = std::freopen(filename.c_str(), mode, previous); #endif diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 7de9ed967..11aed033b 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -26,6 +26,8 @@ extern "C" void SyncWrittenPaths_nid_no_patch(); extern "C" void BlockPathAlias_nid_no_patch(const char* guestPrefix); #ifdef _WIN32 extern "C" int WindowsOpenError_nid_no_patch(unsigned long error); +extern "C" void* HostStandardHandle_nid_no_patch(int descriptor); +extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle); #endif #define APS5_INVALID_ARG_EX throw std::invalid_argument(std::string(__func__) + ": invalid argument") diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index a2a57370b..5ad6906e9 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -20,6 +20,7 @@ #define WIN32_LEAN_AND_MEAN #endif #include +#include #else #include #include @@ -434,6 +435,28 @@ extern "C" int WindowsOpenError_nid_no_patch(unsigned long error) { return EINVAL; } } + +extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); +static DWORD StandardHandleId(int descriptor) { + return descriptor == 0 ? STD_INPUT_HANDLE : descriptor == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE; +} +extern "C" void* HostStandardHandle_nid_no_patch(int descriptor) { + if (descriptor < 0 || descriptor > 2) return nullptr; + const HANDLE current = GetStdHandle(StandardHandleId(descriptor)); + if (current == nullptr || current == INVALID_HANDLE_VALUE) return nullptr; + const auto previous = _set_thread_local_invalid_parameter_handler([](const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {}); + const auto owned = ::_get_osfhandle(descriptor); + _set_thread_local_invalid_parameter_handler(previous); + if (reinterpret_cast(owned) != current) return current; + HANDLE kept = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), current, GetCurrentProcess(), &kept, 0, TRUE, DUPLICATE_SAME_ACCESS)) + throw std::runtime_error("Cannot keep the host standard handle of descriptor " + std::to_string(descriptor)); + return kept; +} +extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle) { + if (handle != nullptr && !SetStdHandle(StandardHandleId(descriptor), handle)) + throw std::runtime_error("Cannot keep the host standard handle of descriptor " + std::to_string(descriptor)); +} #endif extern "C" void NotImplemented_nid_no_patch(const char* funcName) { diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 71550eb1c..37bde44cc 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -581,7 +581,10 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { if (WithoutParameterHandler([from] { return ::_get_osfhandle(from) == -1 ? -1 : 0; }) != 0) return PosixFailure(GUEST_EBADF); if (from == to) return to; if (to == 2) PreserveHostStandardError(); - if (WithoutParameterHandler([from, to] { return ::_dup2(from, to); }) != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); + auto* const standard = HostStandardHandle_nid_no_patch(to); + const int duplicated = WithoutParameterHandler([from, to] { return ::_dup2(from, to); }); + KeepStandardHandle_nid_no_patch(to, standard); + if (duplicated != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); EqueueDescriptorClosed(to); File::ForgetDirectoryDescriptor(to); File::DuplicateDirectoryDescriptor(from, to); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index d32843235..b7d2dece8 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -250,6 +250,34 @@ int main() { std::rewind(hostCapture); Require(std::fread(debugText, 1, sizeof(debugText), hostCapture) == 15 && std::memcmp(debugText, "[debug:1] kept\n", 15) == 0); Require(std::fclose(hostCapture) == 0); +#ifdef _WIN32 + for (const bool reopen : {false, true}) { + auto* hostCapture = std::tmpfile(); + const int hostError = ::_dup(2); + Require(hostCapture != nullptr && hostError >= 0); + int redirect[2] = {-1, -1}; + Require(reopen || pipe_nid_postfix(redirect) == 0); + const bool captured = ::_dup2(::_fileno(hostCapture), 2) == 0 && SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast(::_get_osfhandle(2))); + const bool redirected = reopen ? freopen_nid_postfix("stderr_probe.txt", "w", &_Stderr_nid_postfix) == &_Stderr_nid_postfix : dup2_nid_postfix(redirect[1], 2) == 2; + const HANDLE kept = GetStdHandle(STD_ERROR_HANDLE); + DWORD count = 0; + const bool separate = kept != reinterpret_cast(::_get_osfhandle(2)) && WriteFile(kept, "kept", 4, &count, nullptr) && count == 4; + const bool written = write_nid_postfix(2, "G", 1) == 1; + const bool restored = ::_dup2(hostError, 2) == 0 && ::_close(hostError) == 0 && SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast(::_get_osfhandle(2))); + Require(captured && redirected && separate && written && restored && CloseHandle(kept)); + std::rewind(hostCapture); + char keptText[8]{}; + Require(std::fread(keptText, 1, sizeof(keptText), hostCapture) == 4 && std::memcmp(keptText, "kept", 4) == 0); + Require(std::fclose(hostCapture) == 0); + char guestText = 0; + if (reopen) { + Require(std::filesystem::file_size("stderr_probe.txt") == 1 && std::filesystem::remove("stderr_probe.txt")); + } else { + Require(close_nid_postfix(redirect[1]) == 0 && read_nid_postfix(redirect[0], &guestText, 1) == 1 && guestText == 'G'); + Require(close_nid_postfix(redirect[0]) == 0); + } + } +#endif const auto root = std::filesystem::path("anyps5-filesystem-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(mkdir_nid_postfix(root.string().c_str(), 0700) == 0);