From e39201147eb659cd8542116bda7ba0a66c8058ec Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 26 Jan 2026 17:07:57 -0800 Subject: [PATCH 1/3] FEXLinuxTests: Adds execveat with mfd_cloexec test This just ensures that we have this behaviour working correctly. binfmt_misc in the kernel has a bug that this doesn't work which is kind of funny. --- .../tests/syscalls/execveat_memfd.cpp | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 unittests/FEXLinuxTests/tests/syscalls/execveat_memfd.cpp diff --git a/unittests/FEXLinuxTests/tests/syscalls/execveat_memfd.cpp b/unittests/FEXLinuxTests/tests/syscalls/execveat_memfd.cpp new file mode 100644 index 000000000..f5f41d76a --- /dev/null +++ b/unittests/FEXLinuxTests/tests/syscalls/execveat_memfd.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include +#include +#include +#include +#include + +static std::vector LoadFile(const char* Path) { + int fd = open(Path, O_RDONLY); + REQUIRE(fd != -1); + + struct stat st {}; + REQUIRE(fstat(fd, &st) != -1); + + std::vector Result {}; + Result.resize(st.st_size); + + size_t DidRead {}; + do { + auto Read = read(fd, Result.data() + DidRead, Result.size() - DidRead); + + if (Read == -1) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + REQUIRE(errno != 0); + } + + DidRead += Read; + } while (DidRead != st.st_size); + + return Result; +} + +TEST_CASE("execveat - memfd - MFD_CLOEXEC") { + auto MapsFile = LoadFile("/usr/bin/true"); + REQUIRE(MapsFile.size() != 0); + + int fd = memfd_create("Anonymous", MFD_CLOEXEC | MFD_ALLOW_SEALING); + REQUIRE(fd != -1); + + size_t Written {}; + do { + auto Wrote = write(fd, MapsFile.data() + Written, MapsFile.size() - Written); + if (Wrote == -1) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + REQUIRE(errno != 0); + } + Written += Wrote; + } while (Written != MapsFile.size()); + + const char* argv[] = {"tmp", nullptr}; + auto Res = ::syscall(SYS_execveat, fd, "", argv, nullptr, AT_EMPTY_PATH); + + // Will only get here if execveat fails. + close(fd); + REQUIRE(Res == 0); +} From da235144242802cc989b85613dc15686d73e3c5c Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 26 Jan 2026 17:33:14 -0800 Subject: [PATCH 2/3] Common: Disable glibc fault checking for `getpwuid` No way to workaround this without completely reimplementing the nss database implementation in glibc. --- Source/Common/Config.cpp | 4 ++++ docs/ProgrammingConcerns.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Source/Common/Config.cpp b/Source/Common/Config.cpp index 2e44fbabf..7ea6f6b64 100644 --- a/Source/Common/Config.cpp +++ b/Source/Common/Config.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -495,6 +496,9 @@ void LoadConfig(fextl::string ProgramName, char** const envp, const PortableInfo #ifndef _WIN32 const char* FindUserHomeThroughUID() { + // `getpwuid` allocates memory. + FEXCore::Allocator::YesIKnowImNotSupposedToUseTheGlibcAllocator glibc; + auto passwd = getpwuid(geteuid()); if (passwd) { return passwd->pw_dir; diff --git a/docs/ProgrammingConcerns.md b/docs/ProgrammingConcerns.md index 4c878a366..0fbf90b21 100644 --- a/docs/ProgrammingConcerns.md +++ b/docs/ProgrammingConcerns.md @@ -110,6 +110,10 @@ These all consume a `std::string` as their first argument. Use the equivalent fu ### `fmt::format` Use `fextl::fmt::` instead +### `getpwuid` and `getpwuid_r` +Allocates memory for parsing passwd and other files. One would assume `getpwuid_r` would use the buffer passed in, but nope glibc nss_database_get +allocates memory. + ### APIs that FEX doesn't have a replacement for Don't use any of these APIs in FEXLoader/FEXInterpreter. Shoutout to [this](https://stackoverflow.com/questions/43056338/standard-library-facilities-which-allocate-but-dont-use-an-allocator) StackOverflow post for this From 7806ad8ac838fd0c77474c439e6daa336a82cc3f Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 26 Jan 2026 18:23:35 -0800 Subject: [PATCH 3/3] Config: Stop using `getpwuid` Instead manually parse /etc/passwd in this rare case. --- Source/Common/Config.cpp | 72 +++++++++++++++---- Source/Common/Config.h | 2 +- .../LinuxSyscalls/FileManagement.cpp | 2 +- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/Source/Common/Config.cpp b/Source/Common/Config.cpp index 7ea6f6b64..7c4353a0b 100644 --- a/Source/Common/Config.cpp +++ b/Source/Common/Config.cpp @@ -23,6 +23,9 @@ #include #include +#include +#include + namespace FEX::Config { namespace JSON { static void LoadJSonConfig(const fextl::string& Config, std::function Func) { @@ -495,23 +498,66 @@ void LoadConfig(fextl::string ProgramName, char** const envp, const PortableInfo } #ifndef _WIN32 -const char* FindUserHomeThroughUID() { - // `getpwuid` allocates memory. - FEXCore::Allocator::YesIKnowImNotSupposedToUseTheGlibcAllocator glibc; +fextl::string FindUserHomeThroughUID() { + // `getpwuid` allocates memory, parse `/etc/passwd` manually. + // Format is trivial: `::::::` - auto passwd = getpwuid(geteuid()); - if (passwd) { - return passwd->pw_dir; + fextl::vector Data; + if (!FEXCore::FileLoading::LoadFile(Data, "/etc/passwd")) { + return {}; } - return nullptr; + + auto to_string_view = [](auto rng) { + return std::string_view(&*rng.begin(), ranges::distance(rng)); + }; + + const auto uid = geteuid(); + + for (const auto entry : ranges::views::split(Data, '\n') | ranges::views::transform(to_string_view)) { + const auto elements = ranges::views::split(entry, ':') | ranges::views::transform(to_string_view); + // Reject bad entries. + if (std::distance(elements.begin(), elements.end()) != 7) { + continue; + } + + auto iter = elements.begin(); + ++iter; // name + ++iter; // password hash + ++iter; // comment + // uid + const auto uid_s = *iter; + ++iter; + ++iter; // gid + // home + const auto home_s = *iter; + ++iter; + ++iter; // shell + + uint64_t element_uid; + auto Results = std::from_chars(uid_s.begin(), uid_s.end(), element_uid, 10); + + // Error parsing. + if (Results.ptr == uid_s.begin()) { + continue; + } + + if (element_uid == uid) { + return fextl::string(home_s); + } + } + + return {}; } -const char* GetHomeDirectory() { +fextl::string GetHomeDirectory() { const char* HomeDir = getenv("HOME"); // Try to get home directory from uid if (!HomeDir || !FHU::Filesystem::Exists(HomeDir)) { - HomeDir = FindUserHomeThroughUID(); + auto UIDHome = FindUserHomeThroughUID(); + if (!UIDHome.empty() && FHU::Filesystem::Exists(UIDHome)) { + return UIDHome; + } } // try the PWD @@ -527,7 +573,7 @@ const char* GetHomeDirectory() { return HomeDir; } #else -const char* GetHomeDirectory() { +fextl::string GetHomeDirectory() { const char* HomeDir = getenv("WINEHOMEDIR"); if (HomeDir) { // Skip over the \??\ prefix in the NT path since we want a DOS path @@ -564,7 +610,7 @@ fextl::string GetDataDirectory(bool Global, const PortableInformation& PortableI return GLOBAL_DATA_DIRECTORY; } - const char* HomeDir = GetHomeDirectory(); + auto HomeDir = GetHomeDirectory(); const char* DataXDG = getenv("XDG_DATA_HOME"); const fextl::string LegacyDir = fextl::string {HomeDir} + "/.fex-emu/"; @@ -611,7 +657,7 @@ fextl::string GetConfigDirectory(bool Global, const PortableInformation& Portabl return GLOBAL_DATA_DIRECTORY; } - const char* HomeDir = GetHomeDirectory(); + auto HomeDir = GetHomeDirectory(); const char* ConfigXDG = getenv("XDG_CONFIG_HOME"); const fextl::string LegacyDir = fextl::string {HomeDir} + "/.fex-emu/"; @@ -648,7 +694,7 @@ fextl::string GetCacheDirectory() { } #endif - const char* HomeDir = GetHomeDirectory(); + auto HomeDir = GetHomeDirectory(); const char* CacheXDG = getenv("XDG_CACHE_HOME"); return (CacheXDG ? fextl::string {CacheXDG} : (fextl::string {HomeDir} + "/.cache")) + "/fex-emu/"; #else diff --git a/Source/Common/Config.h b/Source/Common/Config.h index f988400e0..3ba689315 100644 --- a/Source/Common/Config.h +++ b/Source/Common/Config.h @@ -57,7 +57,7 @@ ApplicationNames GetApplicationNames(const fextl::vector& Args, b */ void LoadConfig(fextl::string ProgramName = {}, char** const envp = nullptr, const PortableInformation& PortableInfo = {}); -const char* GetHomeDirectory(); +fextl::string GetHomeDirectory(); fextl::string GetDataDirectory(const PortableInformation& PortableInfo); fextl::string GetConfigDirectory(bool Global, const PortableInformation& PortableInfo); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp index 0f11aa6a2..8cc6ce4aa 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp @@ -106,7 +106,7 @@ void FileManager::LoadThunkDatabase(fextl::unordered_map