mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 10:00:16 +02:00
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.
This commit is contained in:
3 files changed
+65
-1
No files matched your search
@@ -503,6 +503,24 @@ std::pair<int, const char*> 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<std::string_view> 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<std::string_view> 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;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
uint64_t Statfs(const char* path, void* buf);
|
||||
|
||||
std::optional<std::string_view> GetSelf(const char* Pathname);
|
||||
bool IsSelfNoFollow(const char* Pathname, int flags) const;
|
||||
|
||||
void UpdatePID(uint32_t PID) {
|
||||
CurrentPID = PID;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
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.
|
||||
}
|
||||
Reference in new issue
Block a user