From 09a49a34208953c907fa6787ecb3b9064a99d8c2 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 4 Sep 2023 22:52:57 -0700 Subject: [PATCH] FEXInterpreter: Supports procfs/interpreter This is a new procfs symlink path that changes behaviour of binfmt_misc when exposed. We need to check both procfs/exe and procfs/interpreter and see if they exist AND also differ. Once/if they do then we can disable a bunch of checking of paths once they do. The fallback when none of this is supported has the same behaviour has previously where it still does all the regular checking. During binfmt_misc install cmake will check the kernel version for the raw binfmt_misc writing. Which will never pass until we have a real kernel version that it is upstreamed in. For update-binfmts we add a new optional argument where the tool will drop the flag if the host kernel version isn't new enough to handle the option. --- Data/binfmts/FEX-x86.in | 1 + Data/binfmts/FEX-x86_64.in | 1 + Source/Tools/FEXLoader/CMakeLists.txt | 15 +++- .../LinuxSyscalls/FileManagement.cpp | 72 ++++++++++++++----- .../FEXLoader/LinuxSyscalls/FileManagement.h | 3 + .../FEXLoader/LinuxSyscalls/Syscalls.cpp | 28 ++++---- 6 files changed, 88 insertions(+), 32 deletions(-) diff --git a/Data/binfmts/FEX-x86.in b/Data/binfmts/FEX-x86.in index c76259dca..a728b4bef 100644 --- a/Data/binfmts/FEX-x86.in +++ b/Data/binfmts/FEX-x86.in @@ -6,3 +6,4 @@ mask \xff\xff\xff\xff\xff\xfe\xfe\x00\x00\x00\x00\xff\xff\xff\xff\xff\xfe\xff\xf credentials yes fix_binary yes preserve yes +expose_interpreter optional diff --git a/Data/binfmts/FEX-x86_64.in b/Data/binfmts/FEX-x86_64.in index fa0e195f4..4e13c3ff7 100644 --- a/Data/binfmts/FEX-x86_64.in +++ b/Data/binfmts/FEX-x86_64.in @@ -6,3 +6,4 @@ mask \xff\xff\xff\xff\xff\xfe\xfe\x00\x00\x00\x00\xff\xff\xff\xff\xff\xfe\xff\xf credentials yes fix_binary yes preserve yes +expose_interpreter optional diff --git a/Source/Tools/FEXLoader/CMakeLists.txt b/Source/Tools/FEXLoader/CMakeLists.txt index bd83366ba..504be93c0 100644 --- a/Source/Tools/FEXLoader/CMakeLists.txt +++ b/Source/Tools/FEXLoader/CMakeLists.txt @@ -91,6 +91,17 @@ if (NOT MINGW_BUILD) add_dependencies(uninstall uninstall_binfmt_misc_64) endif() else() + set (SUPPORTED_BINFMT_MISC_FLAGS "POCF") + execute_process(COMMAND uname -r OUTPUT_VARIABLE UNAME_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX MATCH "[0-9]+.[0-9]+" KERNEL_VERSION ${UNAME_VERSION}) + message(STATUS "Kernel version: ${KERNEL_VERSION}") + + if (KERNEL_VERSION VERSION_GREATER_EQUAL 9999.0) + # New binfmt_misc flag for exposing the interpreter was added in version '9999.0' + # Only enable it if the host kernel is at least that. + set (SUPPORTED_BINFMT_MISC_FLAGS "POCFI") + endif() + # In the case of update-binfmts not being available (Arch for example) then we need to install manually add_custom_target(binfmt_misc_32 COMMAND ${CMAKE_COMMAND} -E @@ -101,7 +112,7 @@ if (NOT MINGW_BUILD) echo "Attempting to install FEX-x86 misc now." COMMAND ${CMAKE_COMMAND} -E echo - ':FEX-x86:M:0:\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x03\\x00:\\xff\\xff\\xff\\xff\\xff\\xfe\\xfe\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:${CMAKE_INSTALL_PREFIX}/bin/FEXInterpreter:POCF' > /proc/sys/fs/binfmt_misc/register + ':FEX-x86:M:0:\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x03\\x00:\\xff\\xff\\xff\\xff\\xff\\xfe\\xfe\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:${CMAKE_INSTALL_PREFIX}/bin/FEXInterpreter:${SUPPORTED_BINFMT_MISC_FLAGS}' > /proc/sys/fs/binfmt_misc/register COMMAND ${CMAKE_COMMAND} -E echo "binfmt_misc FEX-x86 installed" ) @@ -114,7 +125,7 @@ if (NOT MINGW_BUILD) echo "Attempting to install FEX-x86_64 misc now." COMMAND ${CMAKE_COMMAND} -E echo - ':FEX-x86_64:M:0:\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x3e\\x00:\\xff\\xff\\xff\\xff\\xff\\xfe\\xfe\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:${CMAKE_INSTALL_PREFIX}/bin/FEXInterpreter:POCF' > /proc/sys/fs/binfmt_misc/register + ':FEX-x86_64:M:0:\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x3e\\x00:\\xff\\xff\\xff\\xff\\xff\\xfe\\xfe\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:${CMAKE_INSTALL_PREFIX}/bin/FEXInterpreter:${SUPPORTED_BINFMT_MISC_FLAGS}' > /proc/sys/fs/binfmt_misc/register COMMAND ${CMAKE_COMMAND} -E echo "binfmt_misc FEX-x86_64 installed" ) diff --git a/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.cpp b/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.cpp index f9f5ef8a3..98a624793 100644 --- a/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.cpp +++ b/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.cpp @@ -21,6 +21,7 @@ $end_info$ #include #include #include +#include #include #include @@ -344,6 +345,32 @@ FileManager::FileManager(FEXCore::Context::Context *ctx) } } + // Check to see if this kernel exposes `/proc/self/interpreter`. + // In the case that it does then behaviour is different than without. + // + // When procfs/interpreter is supported (binfmt_misc flag enabled): + // - procfs/exe -> symlink to the correct executable just like when executing natively. + // - procfs/interpreter -> symlink to FEXInterpreter. + // + // FEX no longer needs to track accesses to procfs/exe which improves performance and also improves correctness. + // + // When procfs/interpreter is supported (binfmt_misc flag not enabled): + // When procfs/interpreter is NOT supported: + // - procfs/exe -> symlink to FEXInterpreter. + // - procfs/interpreter -> symlink doesn't exist. + // + // In either of these two cases, FEX still needs to track procfs/exe so we can't completely get away from it. + // This happens in a few edge cases + // - binfmt_misc not installed + // - binfmt_misc doesn't support enabling the new flag + // - executable called through FEXInterpreter directly + // - Can happen because of directly executing the process through FEXIntepreter or through FEXBash. + char FilenameExe[PATH_MAX]; + char FilenameInterpreter[PATH_MAX]; + const auto ExeSymlinkPath = FHU::Symlinks::ResolveSymlink("/proc/self/exe", FilenameExe); + const auto InterpreterSymlinkPath = FHU::Symlinks::ResolveSymlink("/proc/self/interpreter", FilenameInterpreter); + SupportsProcFSInterpreter = !InterpreterSymlinkPath.empty() && ExeSymlinkPath != InterpreterSymlinkPath; + UpdatePID(::getpid()); } @@ -470,6 +497,11 @@ std::pair FileManager::GetEmulatedFDPath(int dirfd, const char } std::optional FileManager::GetSelf(const char *Pathname) { + if (SupportsProcFSInterpreter) { + // FEX doesn't need to track procfs/exe if this is supported. + return Pathname; + } + if (!Pathname) { return std::nullopt; } @@ -616,17 +648,19 @@ uint64_t FileManager::FAccessat2(int dirfd, const char *pathname, int mode, int } uint64_t FileManager::Readlink(const char *pathname, char *buf, size_t bufsiz) { - // calculate the non-self link to exe - // Some executables do getpid, stat("/proc/$pid/exe") - char PidSelfPath[50]; - snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID); + if (!SupportsProcFSInterpreter) { + // calculate the non-self link to exe + // Some executables do getpid, stat("/proc/$pid/exe") + char PidSelfPath[50]; + snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID); - if (strcmp(pathname, "/proc/self/exe") == 0 || - strcmp(pathname, "/proc/thread-self/exe") == 0 || - strcmp(pathname, PidSelfPath) == 0) { - auto App = Filename(); - strncpy(buf, App.c_str(), bufsiz); - return std::min(bufsiz, App.size()); + if (strcmp(pathname, "/proc/self/exe") == 0 || + strcmp(pathname, "/proc/thread-self/exe") == 0 || + strcmp(pathname, PidSelfPath) == 0) { + auto App = Filename(); + strncpy(buf, App.c_str(), bufsiz); + return std::min(bufsiz, App.size()); + } } FDPathTmpData TmpFilename; @@ -696,15 +730,17 @@ uint64_t FileManager::Readlinkat(int dirfd, const char *pathname, char *buf, siz } } - char PidSelfPath[50]; - snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID); + if (SupportsProcFSInterpreter) { + char PidSelfPath[50]; + snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID); - if (Path == "/proc/self/exe" || - Path == "/proc/thread-self/exe" || - Path == PidSelfPath) { - auto App = Filename(); - strncpy(buf, App.c_str(), bufsiz); - return std::min(bufsiz, App.size()); + if (Path == "/proc/self/exe" || + Path == "/proc/thread-self/exe" || + Path == PidSelfPath) { + auto App = Filename(); + strncpy(buf, App.c_str(), bufsiz); + return std::min(bufsiz, App.size()); + } } FDPathTmpData TmpFilename; diff --git a/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.h b/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.h index e5b9c3ce5..12b95654c 100644 --- a/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.h +++ b/Source/Tools/FEXLoader/LinuxSyscalls/FileManagement.h @@ -85,6 +85,8 @@ public: using FDPathTmpData = std::array; std::pair GetEmulatedFDPath(int dirfd, const char *pathname, bool FollowSymlink, FDPathTmpData &TmpFilename); + bool SupportsProcFSInterpreterPath() const { return SupportsProcFSInterpreter; } + private: bool RootFSPathExists(const char* Filepath); @@ -108,5 +110,6 @@ private: FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE); uint32_t CurrentPID{}; int RootFSFD{AT_FDCWD}; + bool SupportsProcFSInterpreter{}; }; } diff --git a/Source/Tools/FEXLoader/LinuxSyscalls/Syscalls.cpp b/Source/Tools/FEXLoader/LinuxSyscalls/Syscalls.cpp index 011a2cd51..bf6c014fb 100644 --- a/Source/Tools/FEXLoader/LinuxSyscalls/Syscalls.cpp +++ b/Source/Tools/FEXLoader/LinuxSyscalls/Syscalls.cpp @@ -209,6 +209,7 @@ uint64_t ExecveHandler(const char *pathname, char* const* argv, char* const* env // AT_EMPTY_PATH is only used if the pathname is empty. const bool IsFDExec = (Args.flags & AT_EMPTY_PATH) && strlen(pathname) == 0; + const bool SupportsProcFSInterpreter = FEX::HLE::_SyscallHandler->FM.SupportsProcFSInterpreterPath(); fextl::string FDExecEnv; bool IsShebang{}; @@ -239,19 +240,21 @@ uint64_t ExecveHandler(const char *pathname, char* const* argv, char* const* env return -ENOENT; } - int pid = getpid(); + if (!SupportsProcFSInterpreter) { + int pid = getpid(); - char PidSelfPath[50]; - snprintf(PidSelfPath, 50, "/proc/%i/exe", pid); + char PidSelfPath[50]; + snprintf(PidSelfPath, 50, "/proc/%i/exe", pid); - if (strcmp(pathname, "/proc/self/exe") == 0 || - strcmp(pathname, "/proc/thread-self/exe") == 0 || - strcmp(pathname, PidSelfPath) == 0) { - // If the application is trying to execve `/proc/self/exe` or its variants, - // then we need to redirect this path to the true application path. - // This is because this path is a symlink to the executing application, which is always `FEXInterpreter` or `FEXLoader`. - // ex: JRE and shapez.io do this self-execution. - Filename = FEX::HLE::_SyscallHandler->Filename(); + if (strcmp(pathname, "/proc/self/exe") == 0 || + strcmp(pathname, "/proc/thread-self/exe") == 0 || + strcmp(pathname, PidSelfPath) == 0) { + // If the application is trying to execve `/proc/self/exe` or its variants, + // then we need to redirect this path to the true application path. + // This is because this path is a symlink to the executing application, which is always `FEXInterpreter` or `FEXLoader`. + // ex: JRE and shapez.io do this self-execution. + Filename = FEX::HLE::_SyscallHandler->Filename(); + } } Type = ELFLoader::ELFContainer::GetELFType(Filename); @@ -361,7 +364,8 @@ uint64_t ExecveHandler(const char *pathname, char* const* argv, char* const* env EnvpPtr = const_cast(EnvpArgs.data()); } - Result = ::syscall(SYS_execveat, Args.dirfd, "/proc/self/exe", + const char *InterpreterPath = SupportsProcFSInterpreter ? "/proc/self/interpreter" : "/proc/self/exe"; + Result = ::syscall(SYS_execveat, Args.dirfd, InterpreterPath, const_cast(ExecveArgs.data()), EnvpPtr, Args.flags); SYSCALL_ERRNO();