Skip to content

Commit

Permalink
app-emulation/FEX: add lina's Chromium/CEF patches
Browse files Browse the repository at this point in the history
Fixes a whole host of issues with Steam and probably Electron

Signed-off-by: James Calligeros <[email protected]>
  • Loading branch information
chadmed committed Nov 20, 2024
1 parent e0dc658 commit 2eab831
Show file tree
Hide file tree
Showing 6 changed files with 713 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ PATCHES="
${FILESDIR}/${P}-fmt-as-static.patch
${FILESDIR}/${PN}-thunks-toolchain-paths.patch
${FILESDIR}/${PN}-thunkgen-gcc-install-dir.patch
${FILESDIR}/${P}-clone-fork-semantics.patch
${FILESDIR}/${P}-listen-abstract-named-sockets.patch
${FILESDIR}/${P}-align-stack-base.patch
${FILESDIR}/${P}-hide-rootfs-fd.patch
${FILESDIR}/${P}-hide-rootfs-fd-2.patch
"

IUSE="crossdev-toolchain fexconfig qt5 qt6 +thunks"
Expand Down
25 changes: 25 additions & 0 deletions app-emulation/FEX/files/FEX-2410-align-stack-base.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
From 4a67893f1d36fc2611a63eaac30eea3b9e69213d Mon Sep 17 00:00:00 2001
From: Asahi Lina <[email protected]>
Date: Wed, 13 Nov 2024 03:47:03 +0900
Subject: [PATCH] FEXLoader: Align stack base

This ensures that __libc_stack_end is aligned, the same way it is on
native.
---
Source/Tools/FEXLoader/ELFCodeLoader.h | 3 +++
1 file changed, 3 insertions(+)

diff --git a/Source/Tools/FEXLoader/ELFCodeLoader.h b/Source/Tools/FEXLoader/ELFCodeLoader.h
index 8e24f75b83..733be0317f 100644
--- a/Source/Tools/FEXLoader/ELFCodeLoader.h
+++ b/Source/Tools/FEXLoader/ELFCodeLoader.h
@@ -725,6 +725,9 @@ class ELFCodeLoader final : public FEX::CodeLoader {
uint64_t ExecFNLocation = TotalArgumentMemSize;
TotalArgumentMemSize += Args[0].size() + 1;

+ // Align the argument block to 16 bytes to keep the stack aligned
+ TotalArgumentMemSize = FEXCore::AlignUp(TotalArgumentMemSize, 16);
+
// Offset the stack by how much memory we need
StackPointer -= TotalArgumentMemSize;

72 changes: 72 additions & 0 deletions app-emulation/FEX/files/FEX-2410-clone-fork-semantics.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
From bfed21870fff0be99327f21c0a4cb1c68861d999 Mon Sep 17 00:00:00 2001
From: Asahi Lina <[email protected]>
Date: Tue, 12 Nov 2024 22:16:58 +0900
Subject: [PATCH] Support CLONE_FS and CLONE_FILES with fork() semantics

Needed by Discord, part of the Chromium sandbox code. The warning still
triggers because Chromium asks for CLONE_VM on x86_64, but that can be
safely ignored (CLONE_FS is the one that matters).
---
.../LinuxEmulation/LinuxSyscalls/Syscalls.cpp | 2 +-
.../LinuxSyscalls/Syscalls/Thread.cpp | 14 ++++++++++++--
2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp
index 9541471059..bcb92096a7 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp
@@ -583,7 +583,7 @@ uint64_t CloneHandler(FEXCore::Core::CpuStateFrame* Frame, FEX::HLE::clone3_args
return false;
}
} else {
- if (AnyFlagsSet(args->args.flags, CLONE_SYSVSEM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM)) {
+ if (AnyFlagsSet(args->args.flags, CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_VM)) {
// CLONE_VM is particularly nasty here
// Memory regions at the point of clone(More similar to a fork) are shared
LogMan::Msg::IFmt("clone: Unsupported flags w/o CLONE_THREAD (Shared Resources), {:X}", args->args.flags);
diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp
index 0a1178019b..7f2313b03e 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp
@@ -26,6 +26,7 @@ tags: LinuxSyscalls|syscalls-shared
#include <limits.h>
#include <linux/futex.h>
#include <linux/seccomp.h>
+#include <linux/sched.h>
#include <stdint.h>
#include <sched.h>
#include <sys/personality.h>
@@ -228,6 +229,15 @@ uint64_t HandleNewClone(FEX::HLE::ThreadStateObject* Thread, FEXCore::Context::C
return Thread->Thread->StatusCode;
}

+static int Clone3Fork(uint32_t flags) {
+ struct clone_args cl_args = {
+ .flags = (flags & (CLONE_FS | CLONE_FILES)),
+ .exit_signal = SIGCHLD,
+ };
+
+ return syscall(SYS_clone3, cl_args, sizeof(cl_args));
+}
+
uint64_t ForkGuest(FEXCore::Core::InternalThreadState* Thread, FEXCore::Core::CpuStateFrame* Frame, uint32_t flags, void* stack,
size_t StackSize, pid_t* parent_tid, pid_t* child_tid, void* tls) {
// Just before we fork, we lock all syscall mutexes so that both processes will end up with a locked mutex
@@ -248,7 +258,7 @@ uint64_t ForkGuest(FEXCore::Core::InternalThreadState* Thread, FEXCore::Core::Cp

// XXX: We don't currently support a real `vfork` as it causes problems.
// Currently behaves like a fork (with wait after the fact), which isn't correct. Need to find where the problem is
- Result = fork();
+ Result = Clone3Fork(flags);

if (Result == 0) {
// Close the read end of the pipe.
@@ -259,7 +269,7 @@ uint64_t ForkGuest(FEXCore::Core::InternalThreadState* Thread, FEXCore::Core::Cp
close(VForkFDs[1]);
}
} else {
- Result = fork();
+ Result = Clone3Fork(flags);
}
const bool IsChild = Result == 0;

107 changes: 107 additions & 0 deletions app-emulation/FEX/files/FEX-2410-hide-rootfs-fd-2.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
From 73ffaa1e18e80c6ee0bbd38b3f3e6baebff4b60a Mon Sep 17 00:00:00 2001
From: Asahi Lina <[email protected]>
Date: Wed, 13 Nov 2024 01:22:42 +0900
Subject: [PATCH] FileManagement: Hide the FEX RootFS fd from /proc/self/fd
take 2

Apparently Chromium/CEF can chroot or otherwise sandbox the filesystem
away before forking and checking for directory FDs, making /proc
inaccessible, which means we can't stat it for our inode check, breaking
the hiding.

So, double down on things and do what Chromium does: open an fd to /proc
ahead of time, so that continues to work. Then we use it to update the
inode of our RootFS fd instead, and finally, also do the /proc fd itself
to hide that one too.

We also don't need to check the st_dev of /proc more than once, since
that's not expected to change anyway.

Fixes cefsimple.
---
.../LinuxSyscalls/FileManagement.cpp | 33 +++++++++++++++----
.../LinuxSyscalls/FileManagement.h | 2 ++
2 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp
index e71d3e049c..d4ccaa5a4a 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp
@@ -322,6 +322,16 @@ FileManager::FileManager(FEXCore::Context::Context* ctx)
}
}

+ // Keep an fd open for /proc, to bypass chroot-style sandboxes
+ ProcFD = open("/proc", O_RDONLY | O_CLOEXEC);
+
+ // Track the st_dev of /proc, to check for inode equality
+ struct stat Buffer;
+ auto Result = fstat(ProcFD, &Buffer);
+ if (Result >= 0) {
+ ProcFSDev = Buffer.st_dev;
+ }
+
UpdatePID(::getpid());
}

@@ -998,30 +1008,39 @@ void FileManager::UpdatePID(uint32_t PID) {
CurrentPID = PID;

// Track the inode of /proc/self/fd/<RootFSFD>, to be able to hide it
- auto FDpath = fextl::fmt::format("/proc/self/fd/{}", RootFSFD);
+ auto FDpath = fextl::fmt::format("self/fd/{}", RootFSFD);
struct stat Buffer {};
- int Result = fstatat(AT_FDCWD, FDpath.c_str(), &Buffer, AT_SYMLINK_NOFOLLOW);
+ int Result = fstatat(ProcFD, FDpath.c_str(), &Buffer, AT_SYMLINK_NOFOLLOW);
if (Result >= 0) {
RootFSFDInode = Buffer.st_ino;
+ } else {
+ // Probably in a strict sandbox
+ RootFSFDInode = 0;
+ ProcFDInode = 0;
+ return;
}

- // Track the st_dev of /proc, to check for inode equality
- Result = stat("/proc/self", &Buffer);
+ // And track the ProcFSFD itself
+ FDpath = fextl::fmt::format("self/fd/{}", ProcFD);
+ Result = fstatat(ProcFD, FDpath.c_str(), &Buffer, AT_SYMLINK_NOFOLLOW);
if (Result >= 0) {
- ProcFSDev = Buffer.st_dev;
+ ProcFDInode = Buffer.st_ino;
+ } else {
+ // ??
+ ProcFDInode = 0;
+ return;
}
}

bool FileManager::IsRootFSFD(int dirfd, uint64_t inode) {

// Check if we have to hide this entry
- if (inode == RootFSFDInode) {
+ if (inode == RootFSFDInode || inode == ProcFDInode) {
struct stat Buffer;
if (fstat(dirfd, &Buffer) >= 0) {
if (Buffer.st_dev == ProcFSDev) {
LogMan::Msg::DFmt("Hiding directory entry for RootFSFD");
return true;
- } else {
}
}
}
diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h
index f84ef30be6..aecf9a874c 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h
@@ -161,7 +161,9 @@ class FileManager final {
FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE);
uint32_t CurrentPID {};
int RootFSFD {AT_FDCWD};
+ int ProcFD {0};
int64_t RootFSFDInode = 0;
+ int64_t ProcFDInode = 0;
dev_t ProcFSDev;
};
} // namespace FEX::HLE
167 changes: 167 additions & 0 deletions app-emulation/FEX/files/FEX-2410-hide-rootfs-fd.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
From 3d701f5fcf8a729246ad51f5e9e57a0d48732cd6 Mon Sep 17 00:00:00 2001
From: Asahi Lina <[email protected]>
Date: Sun, 27 Oct 2024 07:00:54 +0900
Subject: [PATCH] FileManagement: Hide the FEX RootFS fd from /proc/self/fd

Chromium/CEF has code that iterates through all open FDs and bails if
any are directories (apparently a sandboxing sanity check). To avoid
this check, we need to hide the RootFS FD. This requires hooking all the
getdents variants to skip that entry.

To keep the runtime cost low, we keep track of the inode of
/proc/self/fd/<rootfs fd> (note: not the RootFS inode, the inode of the
magic symlink in /proc), and first do a quick check on that. If it
matches, then we stat the dirfd we are reading and check against the
procfs device, to complete the inode equality check.

As an extra benefit, this also fixes code that tries to iterate and
close all/extra FDs and ends up closing the RootFS fd.
---
.../LinuxSyscalls/FileManagement.cpp | 34 +++++++++++++++++++
.../LinuxSyscalls/FileManagement.h | 7 ++--
.../LinuxEmulation/LinuxSyscalls/Syscalls.cpp | 5 +++
.../LinuxSyscalls/Syscalls/Passthrough.cpp | 2 --
.../LinuxEmulation/LinuxSyscalls/x32/FD.cpp | 5 +++
.../LinuxEmulation/LinuxSyscalls/x64/FD.cpp | 17 ++++++++++
6 files changed, 65 insertions(+), 5 deletions(-)

diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp
index e57c281586..e71d3e049c 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp
@@ -994,4 +994,38 @@ uint64_t FileManager::LRemovexattr(const char* path, const char* name) {
return ::lremovexattr(SelfPath, name);
}

+void FileManager::UpdatePID(uint32_t PID) {
+ CurrentPID = PID;
+
+ // Track the inode of /proc/self/fd/<RootFSFD>, to be able to hide it
+ auto FDpath = fextl::fmt::format("/proc/self/fd/{}", RootFSFD);
+ struct stat Buffer {};
+ int Result = fstatat(AT_FDCWD, FDpath.c_str(), &Buffer, AT_SYMLINK_NOFOLLOW);
+ if (Result >= 0) {
+ RootFSFDInode = Buffer.st_ino;
+ }
+
+ // Track the st_dev of /proc, to check for inode equality
+ Result = stat("/proc/self", &Buffer);
+ if (Result >= 0) {
+ ProcFSDev = Buffer.st_dev;
+ }
+}
+
+bool FileManager::IsRootFSFD(int dirfd, uint64_t inode) {
+
+ // Check if we have to hide this entry
+ if (inode == RootFSFDInode) {
+ struct stat Buffer;
+ if (fstat(dirfd, &Buffer) >= 0) {
+ if (Buffer.st_dev == ProcFSDev) {
+ LogMan::Msg::DFmt("Hiding directory entry for RootFSFD");
+ return true;
+ } else {
+ }
+ }
+ }
+ return false;
+}
+
} // namespace FEX::HLE
diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h
index c230aedb48..f84ef30be6 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.h
@@ -81,9 +81,8 @@ class FileManager final {
std::optional<std::string_view> GetSelf(const char* Pathname);
bool IsSelfNoFollow(const char* Pathname, int flags) const;

- void UpdatePID(uint32_t PID) {
- CurrentPID = PID;
- }
+ void UpdatePID(uint32_t PID);
+ bool IsRootFSFD(int dirfd, uint64_t inode);

fextl::string GetEmulatedPath(const char* pathname, bool FollowSymlink = false);
using FDPathTmpData = std::array<char[PATH_MAX], 2>;
@@ -162,5 +161,7 @@ class FileManager final {
FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE);
uint32_t CurrentPID {};
int RootFSFD {AT_FDCWD};
+ int64_t RootFSFDInode = 0;
+ dev_t ProcFSDev;
};
} // namespace FEX::HLE
diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp
index 01d6554256..9541471059 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp
@@ -120,6 +120,11 @@ uint64_t GetDentsEmulation(int fd, T* dirp, uint32_t count) {
Outgoing->d_name[Outgoing->d_reclen - offsetof(T, d_name) - 1] = Tmp->d_type;

TmpOffset += Tmp->d_reclen;
+
+ if (FEX::HLE::_SyscallHandler->FM.IsRootFSFD(fd, Outgoing->d_ino)) {
+ continue;
+ }
+
// Outgoing is 5 bytes smaller
Offset += NewRecLen;

diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Passthrough.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Passthrough.cpp
index d8e02c87ad..b75d09dfcd 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Passthrough.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Passthrough.cpp
@@ -694,8 +694,6 @@ namespace x64 {
SyscallPassthrough6<SYSCALL_DEF(futex)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(io_getevents, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(io_getevents)>);
- REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(getdents64, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
- SyscallPassthrough3<SYSCALL_DEF(getdents64)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(semtimedop, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(semtimedop)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(timer_create, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/FD.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/FD.cpp
index 9a2dfabaf1..01712da60a 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/FD.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/FD.cpp
@@ -600,6 +600,11 @@ void RegisterFD(FEX::HLE::SyscallHandler* Handler) {
for (size_t i = 0, num = 0; i < Result; ++num) {
linux_dirent_64* Incoming = (linux_dirent_64*)(reinterpret_cast<uint64_t>(dirp) + i);
Incoming->d_off = num;
+ if (FEX::HLE::_SyscallHandler->FM.IsRootFSFD(fd, Incoming->d_ino)) {
+ Result -= Incoming->d_reclen;
+ memmove(Incoming, (linux_dirent_64*)(reinterpret_cast<uint64_t>(Incoming) + Incoming->d_reclen), Result - i);
+ continue;
+ }
i += Incoming->d_reclen;
}
}
diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/FD.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/FD.cpp
index e973bce1f3..ea2d78265e 100644
--- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/FD.cpp
+++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/FD.cpp
@@ -112,6 +112,23 @@ void RegisterFD(FEX::HLE::SyscallHandler* Handler) {
return GetDentsEmulation<false>(fd, reinterpret_cast<FEX::HLE::x64::linux_dirent*>(dirp), count);
});

+ REGISTER_SYSCALL_IMPL_X64(getdents64, [](FEXCore::Core::CpuStateFrame* Frame, int fd, void* dirp, uint32_t count) -> uint64_t {
+ uint64_t Result = ::syscall(SYSCALL_DEF(getdents64), static_cast<uint64_t>(fd), dirp, static_cast<uint64_t>(count));
+ if (Result != -1) {
+ // Check for and hide the RootFS FD
+ for (size_t i = 0; i < Result;) {
+ linux_dirent_64* Incoming = (linux_dirent_64*)(reinterpret_cast<uint64_t>(dirp) + i);
+ if (FEX::HLE::_SyscallHandler->FM.IsRootFSFD(fd, Incoming->d_ino)) {
+ Result -= Incoming->d_reclen;
+ memmove(Incoming, (linux_dirent_64*)(reinterpret_cast<uint64_t>(Incoming) + Incoming->d_reclen), Result - i);
+ continue;
+ }
+ i += Incoming->d_reclen;
+ }
+ }
+ SYSCALL_ERRNO();
+ });
+
REGISTER_SYSCALL_IMPL_X64(dup2, [](FEXCore::Core::CpuStateFrame* Frame, int oldfd, int newfd) -> uint64_t {
uint64_t Result = ::dup2(oldfd, newfd);
SYSCALL_ERRNO();
Loading

0 comments on commit 2eab831

Please sign in to comment.