From 37e13cf073f3ece0ea5ca61b0294ab526f7028ba Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Tue, 28 May 2024 03:04:51 -0700 Subject: [PATCH] FileManagement: Fix fstatat with self and NOFOLLOW When asked to not follow the symlink, FEX needs to return data about the symlink itself rather than following to the target executable. In that case we need to return symlink information otherwise games that sanity check can break. This is what happened with Darwinia in #3662. We return the FEXInterpreter symlink information in this case since it doesn't return any information that is relevent to leaking emulator state. Once the application asks to follow through to the symlink target is when we will replace. Also adds a unit test to ensure we don't break it. --- .../LinuxSyscalls/FileManagement.cpp | 36 ++++++++++++++++++- .../LinuxSyscalls/FileManagement.h | 1 + .../FEXLinuxTests/tests/fs/self_symlink.cpp | 29 +++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 unittests/FEXLinuxTests/tests/fs/self_symlink.cpp diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp index bd74e64d7..1842705af 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp @@ -503,6 +503,24 @@ std::pair FileManager::GetEmulatedFDPath(int dirfd, const char return std::make_pair(RootFSFD, &SubPath[1]); } +///< Returns true if the pathname is self and symlink flags are set NOFOLLOW. +bool FileManager::IsSelfNoFollow(const char* Pathname, int flags) const { + const bool Follow = (flags & AT_SYMLINK_NOFOLLOW) == 0; + if (Follow) { + // If we are following the self symlink then we don't care about this. + return false; + } + + if (!Pathname) { + return false; + } + + char PidSelfPath[50]; + snprintf(PidSelfPath, sizeof(PidSelfPath), "/proc/%i/exe", CurrentPID); + + return strcmp(Pathname, "/proc/self/exe") == 0 || strcmp(Pathname, "/proc/thread-self/exe") == 0 || strcmp(Pathname, PidSelfPath) == 0; +} + std::optional FileManager::GetSelf(const char* Pathname) { if (SupportsProcFSInterpreter) { // FEX doesn't need to track procfs/exe if this is supported. @@ -514,7 +532,7 @@ std::optional FileManager::GetSelf(const char* Pathname) { } char PidSelfPath[50]; - snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID); + snprintf(PidSelfPath, sizeof(PidSelfPath), "/proc/%i/exe", CurrentPID); if (strcmp(Pathname, "/proc/self/exe") == 0 || strcmp(Pathname, "/proc/thread-self/exe") == 0 || strcmp(Pathname, PidSelfPath) == 0) { return Filename(); @@ -829,6 +847,12 @@ uint64_t FileManager::Openat2(int dirfs, const char* pathname, FEX::HLE::open_ho } uint64_t FileManager::Statx(int dirfd, const char* pathname, int flags, uint32_t mask, struct statx* statxbuf) { + if (IsSelfNoFollow(pathname, flags)) { + // If we aren't following the symlink for self then we need to return data about the symlink itself. + // Let's just /actually/ return FEXInterpreter symlink information in this case. + return FHU::Syscalls::statx(dirfd, pathname, flags, mask, statxbuf); + } + auto NewPath = GetSelf(pathname); const char* SelfPath = NewPath ? NewPath->data() : nullptr; @@ -870,6 +894,11 @@ uint64_t FileManager::Statfs(const char* path, void* buf) { } uint64_t FileManager::NewFSStatAt(int dirfd, const char* pathname, struct stat* buf, int flag) { + if (IsSelfNoFollow(pathname, flag)) { + // See Statx + return ::fstatat(dirfd, pathname, buf, flag); + } + auto NewPath = GetSelf(pathname); const char* SelfPath = NewPath ? NewPath->data() : nullptr; @@ -885,6 +914,11 @@ uint64_t FileManager::NewFSStatAt(int dirfd, const char* pathname, struct stat* } uint64_t FileManager::NewFSStatAt64(int dirfd, const char* pathname, struct stat64* buf, int flag) { + if (IsSelfNoFollow(pathname, flag)) { + // See Statx + return ::fstatat64(dirfd, pathname, buf, flag); + } + auto NewPath = GetSelf(pathname); const char* SelfPath = NewPath ? NewPath->data() : nullptr; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h index 0180e3bb2..1ac10fc54 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h @@ -79,6 +79,7 @@ public: uint64_t Statfs(const char* path, void* buf); std::optional GetSelf(const char* Pathname); + bool IsSelfNoFollow(const char* Pathname, int flags) const; void UpdatePID(uint32_t PID) { CurrentPID = PID; diff --git a/unittests/FEXLinuxTests/tests/fs/self_symlink.cpp b/unittests/FEXLinuxTests/tests/fs/self_symlink.cpp new file mode 100644 index 000000000..8b71c7987 --- /dev/null +++ b/unittests/FEXLinuxTests/tests/fs/self_symlink.cpp @@ -0,0 +1,29 @@ +#include + +#include + +TEST_CASE("proc-self symlink") { + // Saw with the Darwinia Linux game port. + // It sanity checks that `/proc/self/exe` is a symlink and also that it points to a regular file. + // This uses newfsstatat or statx behind the scenes which FEX didn't handle this edge-case correctly. + + // Create a path with /proc/self/exe + std::filesystem::path path {"/proc/self/exe"}; + + // Check the status of the file with status first. + std::error_code ec; + auto status = std::filesystem::status(path, ec); + + // No error + REQUIRE(!ec); + CHECK(status.type() == std::filesystem::file_type::regular); + + // Now check the status with symlink_status. + status = std::filesystem::symlink_status(path, ec); + + // No error + REQUIRE(!ec); + CHECK(status.type() == std::filesystem::file_type::symlink); + + // The game would then continue to read std::filesystem::read_symlink. +}