Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Thread.cpp
T
Yassin Soliman 1644d185c4 LinuxSyscalls: Preserve argv and path across portable reexec
When binfmt_misc is not visible, execve of an x86 ELF reexecs FEX by path.
That drops the caller-supplied argv[0] and asks the next FEX to reopen the
file through RootFS. Keep the resolved ELF in FEX_EXECVEFD, retain argv[0],
and mark the FD as path-backed so /proc/self/exe stays the real path.
Anonymous memfd execution is unchanged.
2026-10-02 08:45:07 -06:00

349 lines
12 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
tags: LinuxSyscalls|syscalls-x86-64
$end_info$
*/
#include "LinuxSyscalls/SignalDelegator.h"
#include "LinuxSyscalls/Syscalls.h"
#include "LinuxSyscalls/x64/Syscalls.h"
#include "LinuxSyscalls/x64/Thread.h"
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/fextl/vector.h>
#include <sched.h>
#include <signal.h>
#include <stddef.h>
#include <syscall.h>
#include <stdint.h>
#include <unistd.h>
namespace FEX::HLE {
uint64_t SyscallHandler::read_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (!Thread->ldt_entries) {
return 0;
}
bytecount = std::min(bytecount, MAX_LDT_ENTRIES * LDT_ENTRY_SIZE);
const auto EntriesToCopySize = std::min(bytecount, Thread->ldt_entry_count * LDT_ENTRY_SIZE);
if (FaultSafeUserMemAccess::CopyToUser(ptr, Thread->ldt_entries, EntriesToCopySize) != 0) {
return -EFAULT;
}
// Quirk that if the number of bytes that the user is asking for is larger than the amount we have, then zero the remaining memory.
// This means the guest can't ever know the actual size of the LDT.
size_t RemainingSize = bytecount - EntriesToCopySize;
if (RemainingSize) {
auto* Remaining = alloca(RemainingSize);
auto* RemainDst = reinterpret_cast<uint8_t*>(ptr) + EntriesToCopySize;
memset(Remaining, 0, RemainingSize);
if (FaultSafeUserMemAccess::CopyToUser(RemainDst, Remaining, RemainingSize) != 0) {
return -EFAULT;
}
}
// Return the combined size of ldt entries and zero initialized range.
// I don't make the rules, it's just the weirdness that the kernel does.
return bytecount;
}
static uint64_t read_default_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount) {
// This is some weird old legacy thing. Just returns zeroes up to 128-bytes.
uint8_t Data[128] {};
bytecount = std::min<uint64_t>(bytecount, sizeof(Data));
if (FaultSafeUserMemAccess::CopyToUser(ptr, Data, bytecount) != 0) {
return -EFAULT;
}
return bytecount;
}
uint64_t SyscallHandler::write_ldt(FEXCore::Core::CpuStateFrame* Frame, void* ptr, unsigned long bytecount, bool legacy) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
struct user_desc_x64 {
uint32_t entry_number;
uint32_t base_addr;
uint32_t limit;
uint32_t seg_32bit : 1;
uint32_t contents : 2;
uint32_t read_exec_only : 1;
uint32_t limit_in_pages : 1;
uint32_t seg_not_present : 1;
uint32_t useable : 1;
uint32_t lm : 1;
};
static_assert(sizeof(user_desc_x64) == 16);
// `content` member variables.
constexpr static uint32_t MODIFY_LDT_CONTENTS_CONFORMING = 3;
if (bytecount != sizeof(user_desc_x64)) {
// Can only write a single ldt. Reject smaller and larger values.
return -EINVAL;
}
user_desc_x64 ldt_info {};
FEXCore::Core::CPUState::gdt_segment ldt {};
if (FaultSafeUserMemAccess::CopyFromUser(&ldt_info, ptr, sizeof(ldt_info)) == EFAULT) {
// Reject if we can't read it.
return -EFAULT;
}
if (ldt_info.entry_number > MAX_LDT_ENTRIES) {
return -EINVAL;
}
if (ldt_info.contents == MODIFY_LDT_CONTENTS_CONFORMING) {
// Conforming is mostly ignored.
// Legacy doesn't support it at all. Good.
if (legacy) {
return -EINVAL;
}
// Non-legacy ignores if only if the `seg_not_present` is set.
if (ldt_info.seg_not_present == 0) {
return -EINVAL;
}
}
auto is_empty = [](user_desc_x64 ldt_info, bool legacy) {
// Legacy empty is trivial.
const bool legacy_empty = legacy && ldt_info.base_addr == 0 && ldt_info.limit == 0;
if (legacy_empty) {
return true;
}
// Non-legacy is a bit more work.
return ldt_info.base_addr == 0 && ldt_info.limit == 0 && ldt_info.contents == 0 && ldt_info.read_exec_only == 1 &&
ldt_info.limit_in_pages == 0 && ldt_info.seg_not_present == 1 && ldt_info.useable == 0;
};
auto fill_ldt = [](FEXCore::Core::CPUState::gdt_segment& segment, user_desc_x64 ldt_info) {
FEXCore::Core::CPUState::SetGDTBase(&segment, ldt_info.base_addr);
FEXCore::Core::CPUState::SetGDTLimit(&segment, ldt_info.limit);
// Additional flags
// Type: bit [11:8]
// - bit[8] - Accessed
// - bit[9] - Readable
// - bit[10] - Conforming
// - bit[11]
// - 1 - Code
// - 0 - Data
segment.Type = ((ldt_info.read_exec_only ^ 1) << 1) | // Readable
(ldt_info.contents << 2) | // Code/Data+Conforming
1; // Accessed
// S: bit [12]
// - 0 (System descriptor)
// - 1 (User descriptor)
segment.S = 1;
// DPL: bit[14:13]
segment.DPL = 3;
// P: Present
segment.P = ldt_info.seg_not_present ^ 1;
// AVL: Available to software
segment.AVL = ldt_info.useable;
// L: Long-mode
// This doesn't allow setting 64-bit segments!
segment.L = 0;
// D: Default operand size
// - 0: 16-bit operand size
// - 1: 32-bit operand size
segment.D = ldt_info.seg_32bit;
// G: Granularity
segment.G = ldt_info.limit_in_pages;
};
if (is_empty(ldt_info, legacy)) {
// If the ldt_info is considered empty then this is a zeroing operation.
// Just use the zero ldt.
} else {
// This syscall only allows installing 32-bit segments. If `seg_32bit` isn't set then
// it assumes a 16-bit segment!
if (!ldt_info.seg_32bit) {
return -EINVAL;
}
fill_ldt(ldt, ldt_info);
if (legacy) {
// Legacy always zeros this.
ldt.AVL = 0;
}
}
// Need to be careful with ldt replacement here to ensure it is atomically visible.
auto old_ldt = Thread->ldt_entries;
auto old_ldt_entries = Thread->ldt_entry_count;
const auto new_ldt_count = std::max<size_t>(old_ldt_entries, ldt_info.entry_number + 1);
const auto new_ldt_size = new_ldt_count * LDT_ENTRY_SIZE;
const auto new_ldt_entries = reinterpret_cast<FEXCore::Core::CPUState::gdt_segment*>(
FEXCore::Allocator::mmap(nullptr, new_ldt_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(new_ldt_entries), new_ldt_size);
if (old_ldt) {
// Copy old entries if they existed.
memcpy(new_ldt_entries, old_ldt, old_ldt_entries * LDT_ENTRY_SIZE);
}
// Set new LDT.
new_ldt_entries[ldt_info.entry_number] = ldt;
// Set new LDT pointer.
Thread->ldt_entries = new_ldt_entries;
Thread->ldt_entry_count = new_ldt_count;
// Give the new LDT to CPUState.
Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_LDT] = new_ldt_entries;
if (old_ldt) {
FEXCore::Allocator::munmap(old_ldt, old_ldt_entries * LDT_ENTRY_SIZE);
}
return 0;
}
} // namespace FEX::HLE
namespace FEX::HLE::x64 {
uint64_t SetThreadArea(FEXCore::Core::CpuStateFrame* Frame, void* tls) {
Frame->State.fs_cached = reinterpret_cast<uint64_t>(tls);
return 0;
}
void AdjustRipForNewThread(FEXCore::Core::CpuStateFrame* Frame) {
Frame->State.rip += 2;
}
enum Modify_ldt_func : int32_t {
LDT_READ = 0,
LDT_WRITE_LEGACY = 1,
LDT_READ_DEFAULT = 2,
LDT_WRITE = 0x11,
};
void RegisterThread(FEX::HLE::SyscallHandler* Handler) {
REGISTER_SYSCALL_IMPL_X64(modify_ldt, [](FEXCore::Core::CpuStateFrame* Frame, int func, void* ptr, unsigned long bytecount) -> uint64_t {
switch (func) {
case Modify_ldt_func::LDT_READ: return FEX::HLE::_SyscallHandler->read_ldt(Frame, ptr, bytecount);
case Modify_ldt_func::LDT_WRITE_LEGACY: return FEX::HLE::_SyscallHandler->write_ldt(Frame, ptr, bytecount, true);
case Modify_ldt_func::LDT_READ_DEFAULT: return read_default_ldt(Frame, ptr, bytecount);
case Modify_ldt_func::LDT_WRITE: return FEX::HLE::_SyscallHandler->write_ldt(Frame, ptr, bytecount, false);
default: return -ENOSYS;
}
});
REGISTER_SYSCALL_IMPL_X64(
clone, ([](FEXCore::Core::CpuStateFrame* Frame, uint32_t flags, void* stack, pid_t* parent_tid, pid_t* child_tid, void* tls) -> uint64_t {
// This is slightly different EFAULT behaviour, if child_tid or parent_tid is invalid then the kernel just doesn't write to the
// pointer. Still need to be EFAULT safe although.
if ((flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && child_tid) {
FaultSafeUserMemAccess::VerifyIsWritable(child_tid, sizeof(*child_tid));
}
if ((flags & CLONE_PARENT_SETTID) && parent_tid) {
FaultSafeUserMemAccess::VerifyIsWritable(parent_tid, sizeof(*parent_tid));
}
FEX::HLE::clone3_args args {
.Type = TypeOfClone::TYPE_CLONE2,
.args =
{
.flags = flags & ~CSIGNAL, // This no longer contains CSIGNAL
.pidfd = 0, // For clone, pidfd is duplicated here
.child_tid = reinterpret_cast<uint64_t>(child_tid),
.parent_tid = reinterpret_cast<uint64_t>(parent_tid),
.exit_signal = flags & CSIGNAL,
.stack = reinterpret_cast<uint64_t>(stack),
.stack_size = 0, // This syscall isn't able to see the stack size
.tls = reinterpret_cast<uint64_t>(tls),
.set_tid = 0, // This syscall isn't able to select TIDs
.set_tid_size = 0,
.cgroup = 0, // This syscall can't select cgroups
},
};
return CloneHandler(Frame, &args);
}));
REGISTER_SYSCALL_IMPL_X64(sigaltstack, [](FEXCore::Core::CpuStateFrame* Frame, const stack_t* ss, stack_t* old_ss) -> uint64_t {
FaultSafeUserMemAccess::VerifyIsReadableOrNull(ss, sizeof(*ss));
FaultSafeUserMemAccess::VerifyIsWritableOrNull(old_ss, sizeof(*old_ss));
return FEX::HLE::_SyscallHandler->GetSignalDelegator()->RegisterGuestSigAltStack(
FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame), ss, old_ss);
});
// launch a new process under fex
// the ELF self-reexec fallback preserves the caller-supplied argv[0]
REGISTER_SYSCALL_IMPL_X64(execve, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, char* const argv[], char* const envp[]) -> uint64_t {
fextl::vector<const char*> Args;
fextl::vector<const char*> Envp;
if (argv) {
for (int i = 0; argv[i]; i++) {
Args.push_back(argv[i]);
}
Args.push_back(nullptr);
}
if (envp) {
for (int i = 0; envp[i]; i++) {
Envp.push_back(envp[i]);
}
Envp.push_back(nullptr);
}
auto* const* ArgsPtr = argv ? const_cast<char* const*>(Args.data()) : nullptr;
auto* const* EnvpPtr = envp ? const_cast<char* const*>(Envp.data()) : nullptr;
FEX::HLE::ExecveAtArgs AtArgs = FEX::HLE::ExecveAtArgs::Empty();
return FEX::HLE::ExecveHandler(Frame, pathname, ArgsPtr, EnvpPtr, AtArgs);
});
REGISTER_SYSCALL_IMPL_X64(execveat, ([](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, char* const argv[],
char* const envp[], int flags) -> uint64_t {
fextl::vector<const char*> Args;
fextl::vector<const char*> Envp;
if (argv) {
for (int i = 0; argv[i]; i++) {
Args.push_back(argv[i]);
}
Args.push_back(nullptr);
}
if (envp) {
for (int i = 0; envp[i]; i++) {
Envp.push_back(envp[i]);
}
Envp.push_back(nullptr);
}
FEX::HLE::ExecveAtArgs AtArgs {
.dirfd = dirfd,
.flags = flags,
};
auto* const* ArgsPtr = argv ? const_cast<char* const*>(Args.data()) : nullptr;
auto* const* EnvpPtr = envp ? const_cast<char* const*>(Envp.data()) : nullptr;
return FEX::HLE::ExecveHandler(Frame, pathname, ArgsPtr, EnvpPtr, AtArgs);
}));
}
} // namespace FEX::HLE::x64