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.
This commit is contained in:
Ryan Houdek committed 2023-09-05 21:30:47 -07:00
1 parent 16f826c18e
commit 09a49a3420
6 files changed
+88 -32

No files matched your search

+1
View File
@@ -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
+1
View File
@@ -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
+13 -2
View File
@@ -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"
)
@@ -21,6 +21,7 @@ $end_info$
#include <FEXCore/fextl/string.h>
#include <FEXCore/fextl/vector.h>
#include <FEXHeaderUtils/Filesystem.h>
#include <FEXHeaderUtils/SymlinkChecks.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <algorithm>
@@ -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<int, const char*> FileManager::GetEmulatedFDPath(int dirfd, const char
}
std::optional<std::string_view> 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;
@@ -85,6 +85,8 @@ public:
using FDPathTmpData = std::array<char[PATH_MAX], 2>;
std::pair<int, const char*> 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{};
};
}
@@ -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<char *const *>(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<char *const *>(ExecveArgs.data()), EnvpPtr, Args.flags);
SYSCALL_ERRNO();