Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h
T
Ryan Houdek ac32876e4e LinuxEmulation: Implement support for seccomp
Seccomp is a relatively complex feature that was added to Linux back in
2005, and was further extended in 2013 to support BPF based protections.
Once seccomp is enabled, you can no longer disable seccomp but
additional protections can be placed on top of existing seccomp filters.
Additionally seccomp filters are inherited in child processes, which
ensures the process tree can't escape from the secure computing
environment through child processes.

The basis of this feature is a shim that lives between userspace and the
kernel at the syscall entrypoint.
In "strict" mode, seccomp only allows read, write, exit, exit_group, and {rt_,}sigreturn to function.
When in "filter" mode, a BPF filter is run on syscall entrypoint and
returns state about if the syscall should be allowed or not. Multiple
filters can be installed in this mode, all of which get executed. The
result that is the most restricted is the action that occurs at the end.

There are some significant limitations in filter mode that must be
adhered to which makes executing this code inside of kernel space a
non-issue and effectively limits how much cpu time is spent in the filters.
Although these filters are free to do basically anything with the
provided data, just can't do any loops.

FEX needs to implement seccomp because there are multiple applications
using the feature, the primary one being Chromium which some games embed
without disabling the sandbox. WINE also uses seccomp for capturing
games that do raw Windows system calls. Apparently Red Dead Redemption
is one of the games that requires this.

While FEX implements seccomp, it is not yet all encompassing, which is
one of the reasons why it isn't enabled by default and requires a config
option.

**seccomp_unotify is not implemented**
This is a relatively new feature for seccomp which lets the seccomp
filter signal an FD for multiple things. Luckily Chromium and WINE don't
use this. This will be tricky to implement under FEX since it
requires ioctl trapping and some other behaviour

**ptrace isn't supported**
One feature of seccomp is that it can raise ptrace events. Since FEX
doesn't support ptrace at all, this isn't handled. Again Chromium and
WINE don't use this.

**kill-thread not quite correct**
This isn't directly related to seccomp but more about how we do thread
shutdown in FEX. This will require some more changes around thread state
tracking before fully supporting this. Chromium and WINE don't use this.
kill-process also falls under this

Features that are supported:
- Strict mode and seccomp-bpf mode supported
- All BFP instructions that seccomp-bpf understands
- Inheriting seccomp through execve
   - This means we serialize and deserialize the calling thread's
     seccomp filters
   - An execve that escapes FEX will also escape seccomp. Not much we
     can do about it
- TSync - Allowing post-mortem seccomp insertion which allows threads to
  synchronize seccomp filters after the fact

Features that are not supported:
- Different arch qualifiers depending on syscall entrypoint
  - Just like our syscall handler, we are hardcoded to the arch that the
    application starts with
- user_notif
- ptrace
- Runtime code cache invalidation when seccomp is installed
  - Currently we must ensure all syscalls go through the frontend
    syscall handler
  - Runtime invalidation of code cache with inline syscalls will get
    fixed in the future.

This currently isn't enabled by default because of the minor feature
problems that haven't been resolved. Currently the Linux Kernel's test
application works for the features that FEX supports, and WINE's usage
can be handled by FEX. Chromium's sandbox doesn't yet work with this PR,
but it only fails due to features unrelated to seccomp.

Having this open for merging now so we can work to resolve the remaining
issues without this bitrotting.
2024-09-02 14:07:53 -07:00

682 lines
23 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
tags: LinuxSyscalls|common
desc: Glue logic, STRACE magic
$end_info$
*/
#pragma once
#include "LinuxSyscalls/FileManagement.h"
#include "LinuxSyscalls/LinuxAllocator.h"
#include "LinuxSyscalls/ThreadManager.h"
#include "LinuxSyscalls/Seccomp/SeccompEmulator.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/HLE/SyscallHandler.h>
#include <FEXCore/HLE/SourcecodeResolver.h>
#include <FEXCore/IR/IR.h>
#include <FEXCore/Utils/CompilerDefs.h>
#include <FEXCore/Utils/SignalScopeGuards.h>
#include <FEXCore/fextl/fmt.h>
#include <FEXCore/fextl/map.h>
#include <FEXCore/fextl/memory.h>
#include <FEXCore/fextl/string.h>
#include <FEXCore/fextl/vector.h>
#include <mutex>
#include <shared_mutex>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <type_traits>
#include <list>
#ifdef _M_X86_64
#define SYSCALL_ARCH_NAME x64
#elif _M_ARM_64
#include "LinuxSyscalls/Arm64/SyscallsEnum.h"
#define SYSCALL_ARCH_NAME Arm64
#endif
#include "LinuxSyscalls/x64/SyscallsEnum.h"
#include "LinuxSyscalls/x32/SyscallsEnum.h"
#define CONCAT_(a, b) a##b
#define CONCAT(a, b) CONCAT_(a, b)
#define SYSCALL_DEF(name) (HLE::SYSCALL_ARCH_NAME::CONCAT(CONCAT(SYSCALL_, SYSCALL_ARCH_NAME), _##name))
// #define DEBUG_STRACE
namespace FEX {
class CodeLoader;
}
namespace FEXCore {
namespace Context {
class Context;
}
namespace Core {
struct CpuStateFrame;
}
} // namespace FEXCore
namespace FEX::HLE {
class SyscallHandler;
class SignalDelegator;
void RegisterEpoll(FEX::HLE::SyscallHandler* Handler);
void RegisterFD(FEX::HLE::SyscallHandler* Handler);
void RegisterFS(FEX::HLE::SyscallHandler* Handler);
void RegisterInfo(FEX::HLE::SyscallHandler* Handler);
void RegisterIO(FEX::HLE::SyscallHandler* Handler);
void RegisterMemory(FEX::HLE::SyscallHandler* Handler);
void RegisterNuma(FEX::HLE::SyscallHandler* Handler);
void RegisterSignals(FEX::HLE::SyscallHandler* Handler);
void RegisterThread(FEX::HLE::SyscallHandler* Handler);
void RegisterTimer(FEX::HLE::SyscallHandler* Handler);
void RegisterNotImplemented(FEX::HLE::SyscallHandler* Handler);
void RegisterStubs(FEX::HLE::SyscallHandler* Handler);
uint64_t UnimplementedSyscall(FEXCore::Core::CpuStateFrame* Frame, uint64_t SyscallNumber);
uint64_t UnimplementedSyscallSafe(FEXCore::Core::CpuStateFrame* Frame, uint64_t SyscallNumber);
struct ExecveAtArgs {
int dirfd;
int flags;
static ExecveAtArgs Empty() {
return ExecveAtArgs {
.dirfd = AT_FDCWD,
.flags = 0,
};
}
};
uint64_t ExecveHandler(FEXCore::Core::CpuStateFrame* Frame, const char* pathname, char* const* argv, char* const* envp, ExecveAtArgs Args);
class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::SourcecodeResolver, public FEXCore::Allocator::FEXAllocOperators {
public:
ThreadManager TM;
FEX::HLE::SeccompEmulator SeccompEmulator;
virtual ~SyscallHandler();
// In the case that the syscall doesn't hit the optimized path then we still need to go here
uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) final override;
void DefaultProgramBreak(uint64_t Base, uint64_t Size);
void DeserializeSeccompFD(FEX::HLE::ThreadStateObject* Thread, int FD) {
if (FD == -1) {
return;
}
SeccompEmulator.DeserializeFilters(Thread->Thread->CurrentFrame, FD);
}
using SyscallPtrArg0 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame);
using SyscallPtrArg1 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t);
using SyscallPtrArg2 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t);
using SyscallPtrArg3 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t);
using SyscallPtrArg4 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t, uint64_t);
using SyscallPtrArg5 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
using SyscallPtrArg6 = uint64_t (*)(FEXCore::Core::CpuStateFrame* Frame, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
struct SyscallFunctionDefinition {
uint8_t NumArgs;
FEXCore::IR::SyscallFlags Flags;
union {
void* Ptr;
SyscallPtrArg0 Ptr0;
SyscallPtrArg1 Ptr1;
SyscallPtrArg2 Ptr2;
SyscallPtrArg3 Ptr3;
SyscallPtrArg4 Ptr4;
SyscallPtrArg5 Ptr5;
SyscallPtrArg6 Ptr6;
};
int32_t HostSyscallNumber;
#ifdef DEBUG_STRACE
fextl::string StraceFmt;
#endif
};
const SyscallFunctionDefinition* GetDefinition(uint64_t Syscall) {
return &Definitions.at(Syscall);
}
FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override {
if (NeedsSeccomp) {
// Override ABI if seccomp is enabled.
return {FEXCore::HLE::SyscallArguments::MAX_ARGS, true, -1};
}
auto& Def = Definitions.at(Syscall);
return {Def.NumArgs, true, Def.HostSyscallNumber};
}
FEXCore::IR::SyscallFlags GetSyscallFlags(uint64_t Syscall) const override {
if (NeedsSeccomp) {
// Override flags if seccomp is enabled.
return FEXCore::IR::SyscallFlags::DEFAULT;
}
auto& Def = Definitions.at(Syscall);
return Def.Flags;
}
virtual void RegisterSyscall_32(int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags,
#ifdef DEBUG_STRACE
const fextl::string& TraceFormatString,
#endif
void* SyscallHandler, int ArgumentCount) {
}
virtual void RegisterSyscall_64(int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags,
#ifdef DEBUG_STRACE
const fextl::string& TraceFormatString,
#endif
void* SyscallHandler, int ArgumentCount) {
}
uint64_t HandleBRK(FEXCore::Core::CpuStateFrame* Frame, void* Addr);
FEX::HLE::FileManager FM;
FEX::CodeLoader* GetCodeLoader() const {
return LocalLoader;
}
void SetCodeLoader(FEX::CodeLoader* Loader) {
LocalLoader = Loader;
}
FEX::HLE::SignalDelegator* GetSignalDelegator() {
return SignalDelegation;
}
FEX_CONFIG_OPT(IsInterpreter, IS_INTERPRETER);
FEX_CONFIG_OPT(IsInterpreterInstalled, INTERPRETER_INSTALLED);
FEX_CONFIG_OPT(Filename, APP_FILENAME);
FEX_CONFIG_OPT(RootFSPath, ROOTFS);
FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE);
FEX_CONFIG_OPT(SMCChecks, SMCCHECKS);
FEX_CONFIG_OPT(NeedsSeccomp, NEEDSSECCOMP);
uint32_t GetHostKernelVersion() const {
return HostKernelVersion;
}
uint32_t GetGuestKernelVersion() const {
return GuestKernelVersion;
}
bool IsHostKernelVersionAtLeast(uint32_t Major, uint32_t Minor = 0, uint32_t Patch = 0) const {
return GetHostKernelVersion() >= KernelVersion(Major, Minor, Patch);
}
static uint32_t CalculateHostKernelVersion();
uint32_t CalculateGuestKernelVersion();
static uint32_t KernelVersion(uint32_t Major, uint32_t Minor = 0, uint32_t Patch = 0) {
return (Major << 24) | (Minor << 16) | Patch;
}
static uint32_t KernelMajor(uint32_t Version) {
return Version >> 24;
}
static uint32_t KernelMinor(uint32_t Version) {
return (Version >> 16) & 0xFF;
}
static uint32_t KernelPatch(uint32_t Version) {
return Version & 0xFFFF;
}
FEX::HLE::MemAllocator* Get32BitAllocator() {
return Alloc32Handler.get();
}
// does a mmap as if done via a guest syscall
virtual void* GuestMmap(FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length, int prot, int flags, int fd, off_t offset) = 0;
// does a guest munmap as if done via a guest syscall
virtual int GuestMunmap(FEXCore::Core::InternalThreadState* Thread, void* addr, uint64_t length) = 0;
///// Memory Manager tracking /////
void TrackMmap(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size, int Prot, int Flags, int fd, off_t Offset);
void TrackMunmap(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size);
void TrackMprotect(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size, int Prot);
void TrackMremap(FEXCore::Core::InternalThreadState* Thread, uintptr_t OldAddress, size_t OldSize, size_t NewSize, int flags, uintptr_t NewAddress);
void TrackShmat(FEXCore::Core::InternalThreadState* Thread, int shmid, uintptr_t Base, int shmflg);
void TrackShmdt(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base);
void TrackMadvise(FEXCore::Core::InternalThreadState* Thread, uintptr_t Base, uintptr_t Size, int advice);
///// VMA (Virtual Memory Area) tracking /////
static bool HandleSegfault(FEXCore::Core::InternalThreadState* Thread, int Signal, void* info, void* ucontext);
void MarkGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override;
// AOTIRCacheEntryLookupResult also includes a shared lock guard, so the pointed AOTIRCacheEntry return can be safely used
FEXCore::HLE::AOTIRCacheEntryLookupResult LookupAOTIRCacheEntry(FEXCore::Core::InternalThreadState* Thread, uint64_t GuestAddr) final override;
///// FORK tracking /////
void LockBeforeFork(FEXCore::Core::InternalThreadState* Thread);
void UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThread, bool Child);
SourcecodeResolver* GetSourcecodeResolver() override {
return this;
}
void SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::CpuStateFrame* Frame) override {
TM.SleepThread(CTX, Frame);
}
bool NeedXIDCheck() const {
return NeedToCheckXID;
}
void DisableXIDCheck() {
NeedToCheckXID = false;
}
constexpr static uint64_t TASK_MAX_64BIT = (1ULL << 48);
protected:
SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation);
fextl::vector<SyscallFunctionDefinition> Definitions {std::max<std::size_t>(FEX::HLE::x64::SYSCALL_x64_MAX, FEX::HLE::x32::SYSCALL_x86_MAX),
{
.NumArgs = 255,
.Ptr = reinterpret_cast<void*>(&UnimplementedSyscall),
}};
std::mutex MMapMutex;
// BRK management
uint64_t DataSpace {};
uint64_t DataSpaceSize {};
uint64_t DataSpaceMaxSize {};
uint64_t DataSpaceStartingSize {};
// (Major << 24) | (Minor << 16) | Patch
uint32_t HostKernelVersion {};
uint32_t GuestKernelVersion {};
FEXCore::Context::Context* CTX;
private:
FEX::HLE::SignalDelegator* SignalDelegation;
std::mutex FutexMutex;
std::mutex SyscallMutex;
FEX::CodeLoader* LocalLoader {};
bool NeedToCheckXID {true};
#ifdef DEBUG_STRACE
void Strace(FEXCore::HLE::SyscallArguments* Args, uint64_t Ret);
#endif
fextl::unique_ptr<FEX::HLE::MemAllocator> Alloc32Handler {};
fextl::unique_ptr<FEXCore::HLE::SourcecodeMap>
GenerateMap(const std::string_view& GuestBinaryFile, const std::string_view& GuestBinaryFileId) override;
///// VMA (Virtual Memory Area) tracking /////
struct SpecialDev {
static constexpr uint64_t Anon = 0x1'0000'0000; // Anonymous shared mapping, id is incrementing allocation number
static constexpr uint64_t SHM = 0x2'0000'0000; // sys-v shm, id is shmid
};
// Memory Resource ID
// An id that can be used to identify when shared mappings actually have the same backing storage
// when dev != SpecialDev::Anon, this is unique system wide
struct MRID {
uint64_t dev; // kernel dev_t is actually 32-bits, we use the extra bits to track SpecialDevs
uint64_t id;
bool operator<(const MRID& other) const {
return std::tie(dev, id) < std::tie(other.dev, other.id);
}
};
struct VMAEntry;
// Used to all MAP_SHARED VMAs of a system resource.
struct MappedResource {
using ContainerType = fextl::map<MRID, MappedResource>;
FEXCore::IR::AOTIRCacheEntry* AOTIRCacheEntry;
VMAEntry* FirstVMA;
uint64_t Length; // 0 if not fixed size
ContainerType::iterator Iterator;
};
union VMAProt {
struct {
bool Readable : 1;
bool Writable : 1;
bool Executable : 1;
};
uint8_t All : 3;
static VMAProt fromProt(int Prot);
static VMAProt fromSHM(int SHMFlg);
};
struct VMAFlags {
bool Shared : 1;
static VMAFlags fromFlags(int Flags);
};
struct VMAEntry {
MappedResource* Resource;
// these are for Intrusive linked list tracking, starting from Resource->FirstVMA
VMAEntry* ResourcePrevVMA;
VMAEntry* ResourceNextVMA;
uint64_t Base;
uint64_t Offset;
uint64_t Length;
VMAFlags Flags;
VMAProt Prot;
};
struct VMATracking {
using VMAEntry = SyscallHandler::VMAEntry;
// Held while reading/writing this struct
FEXCore::ForkableSharedMutex Mutex;
// Memory ranges indexed by page aligned starting address
fextl::map<uint64_t, VMAEntry> VMAs;
using VMACIterator = decltype(VMAs)::const_iterator;
MappedResource::ContainerType MappedResources;
// Mutex must be at least shared_locked before calling
VMACIterator LookupVMAUnsafe(uint64_t GuestAddr) const;
// Mutex must be unique_locked before calling
void SetUnsafe(FEXCore::Context::Context* Ctx, MappedResource* MappedResource, uintptr_t Base, uintptr_t Offset, uintptr_t Length,
VMAFlags Flags, VMAProt Prot);
// Mutex must be unique_locked before calling
void ClearUnsafe(FEXCore::Context::Context* Ctx, uintptr_t Base, uintptr_t Length, MappedResource* PreservedMappedResource = nullptr);
// Mutex must be unique_locked before calling
void ChangeUnsafe(uintptr_t Base, uintptr_t Length, VMAProt Prot);
// Mutex must be unique_locked before calling
// Returns the Size fo the Shm or 0 if not found
uintptr_t ClearShmUnsafe(FEXCore::Context::Context* Ctx, uintptr_t Base);
private:
bool ListRemove(VMAEntry* Mapping);
void ListReplace(VMAEntry* Mapping, VMAEntry* NewMapping);
void ListInsertAfter(VMAEntry* Mapping, VMAEntry* NewMapping);
void ListPrepend(MappedResource* Resource, VMAEntry* NewVMA);
static void ListCheckVMALinks(VMAEntry* VMA);
} VMATracking;
};
uint64_t HandleSyscall(SyscallHandler* Handler, FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args);
#define SYSCALL_ERRNO() \
do { \
if (Result == -1) return -errno; \
return Result; \
} while (0)
#define SYSCALL_ERRNO_NULL() \
do { \
if (Result == 0) return -errno; \
return Result; \
} while (0)
extern FEX::HLE::SyscallHandler* _SyscallHandler;
#ifdef DEBUG_STRACE
//////
/// Templates to map parameters to format string for syscalls
//////
template<typename T>
struct ArgToFmtString;
#define ARG_TO_STR(tpy, str) \
template<> \
struct FEX::HLE::ArgToFmtString<tpy> { \
inline static const char* const Format = str; \
};
// Base types
ARG_TO_STR(int, "%d")
ARG_TO_STR(unsigned int, "%u")
ARG_TO_STR(long, "%ld")
ARG_TO_STR(unsigned long, "%lu")
// string types
ARG_TO_STR(char*, "%s")
ARG_TO_STR(const char*, "%s")
// Pointers
template<typename T>
struct ArgToFmtString<T*> {
inline static const char* const Format = "%p";
};
// Use ArgToFmtString and variadic template to create a format string from an args list
template<typename... Args>
fextl::string CollectArgsFmtString() {
std::array<const char*, sizeof...(Args)> array = {ArgToFmtString<Args>::Format...};
return fextl::fmt::format("{}", fmt::join(array, ", "));
}
#else
#define ARG_TO_STR(tpy, str)
#endif
struct open_how {
uint64_t flags;
uint64_t mode;
uint64_t resolve;
};
struct kernel_clone3_args {
uint64_t flags;
uint64_t pidfd;
uint64_t child_tid;
uint64_t parent_tid;
uint64_t exit_signal;
uint64_t stack;
uint64_t stack_size;
uint64_t tls;
uint64_t set_tid;
uint64_t set_tid_size;
uint64_t cgroup;
};
enum TypeOfClone {
TYPE_CLONE2,
TYPE_CLONE3,
};
struct clone3_args {
TypeOfClone Type;
uint64_t SignalMask;
uint64_t StackSize;
void* NewStack;
kernel_clone3_args args;
};
uint64_t CloneHandler(FEXCore::Core::CpuStateFrame* Frame, FEX::HLE::clone3_args* args);
inline static int RemapFromX86Flags(int flags) {
#ifdef _M_X86_64
// Nothing to change here
#elif _M_ARM_64
constexpr int X86_64_FLAG_O_DIRECT = 040000;
constexpr int X86_64_FLAG_O_LARGEFILE = 0100000;
constexpr int X86_64_FLAG_O_DIRECTORY = 0200000;
constexpr int X86_64_FLAG_O_NOFOLLOW = 0400000;
constexpr int AARCH64_FLAG_O_DIRECTORY = 040000;
constexpr int AARCH64_FLAG_O_NOFOLLOW = 0100000;
constexpr int AARCH64_FLAG_O_DIRECT = 0200000;
constexpr int AARCH64_FLAG_O_LARGEFILE = 0400000;
int new_flags {};
if (flags & X86_64_FLAG_O_DIRECT) {
flags = (flags & ~X86_64_FLAG_O_DIRECT);
new_flags |= AARCH64_FLAG_O_DIRECT;
}
if (flags & X86_64_FLAG_O_LARGEFILE) {
flags = (flags & ~X86_64_FLAG_O_LARGEFILE);
new_flags |= AARCH64_FLAG_O_LARGEFILE;
}
if (flags & X86_64_FLAG_O_DIRECTORY) {
flags = (flags & ~X86_64_FLAG_O_DIRECTORY);
new_flags |= AARCH64_FLAG_O_DIRECTORY;
}
if (flags & X86_64_FLAG_O_NOFOLLOW) {
flags = (flags & ~X86_64_FLAG_O_NOFOLLOW);
new_flags |= AARCH64_FLAG_O_NOFOLLOW;
}
flags |= new_flags;
#else
#error Unknown flag remappings for this host platform
#endif
return flags;
}
inline static int RemapToX86Flags(int flags) {
#ifdef _M_X86_64
// Nothing to change here
#elif _M_ARM_64
constexpr int X86_64_FLAG_O_DIRECT = 040000;
constexpr int X86_64_FLAG_O_LARGEFILE = 0100000;
constexpr int X86_64_FLAG_O_DIRECTORY = 0200000;
constexpr int X86_64_FLAG_O_NOFOLLOW = 0400000;
constexpr int AARCH64_FLAG_O_DIRECTORY = 040000;
constexpr int AARCH64_FLAG_O_NOFOLLOW = 0100000;
constexpr int AARCH64_FLAG_O_DIRECT = 0200000;
constexpr int AARCH64_FLAG_O_LARGEFILE = 0400000;
int new_flags {};
if (flags & AARCH64_FLAG_O_DIRECT) {
flags = (flags & ~AARCH64_FLAG_O_DIRECT);
new_flags |= X86_64_FLAG_O_DIRECT;
}
if (flags & AARCH64_FLAG_O_LARGEFILE) {
flags = (flags & ~AARCH64_FLAG_O_LARGEFILE);
new_flags |= X86_64_FLAG_O_LARGEFILE;
}
if (flags & AARCH64_FLAG_O_DIRECTORY) {
flags = (flags & ~AARCH64_FLAG_O_DIRECTORY);
new_flags |= X86_64_FLAG_O_DIRECTORY;
}
if (flags & AARCH64_FLAG_O_NOFOLLOW) {
flags = (flags & ~AARCH64_FLAG_O_NOFOLLOW);
new_flags |= X86_64_FLAG_O_NOFOLLOW;
}
flags |= new_flags;
#else
#error Unknown flag remappings for this host platform
#endif
return flags;
}
/**
* @brief Checks raw syscall return for error
*
* This should only be used with raw syscall usage
*
* This should not be used with glibc wrapped syscall functions
* - This includes the glibc ::syscall(...) function
* - This is due to glibc already wrapping the return and setting errno
*
* This function should not be used with UAPI breaking syscall results
* ioctl specifically will break this convention.
*
* @param Result The raw syscall return
*
* @return If the result was an error result
*/
[[maybe_unused]]
static bool HasSyscallError(uint64_t Result) {
// MAX_ERRNO is part of the Linux Syscall ABI
// Redefined here since it doesn't exist as a visible define in the UAPI headers
constexpr uint64_t MAX_ERRNO = 0xFFFF'FFFF'FFFF'0001ULL;
// Raw syscalls are guaranteed to not return a valid result in the range of [-4095, -1]
// In cases where FEX needs to use raw syscalls, this helper checks for this idiom
return reinterpret_cast<uint64_t>(Result) >= MAX_ERRNO;
}
[[maybe_unused]]
static bool HasSyscallError(const void* Result) {
return HasSyscallError(reinterpret_cast<uintptr_t>(Result));
}
template<bool IncrementOffset, typename T>
uint64_t GetDentsEmulation(int fd, T* dirp, uint32_t count);
namespace FaultSafeUserMemAccess {
// These are little helper functions for cases when FEX needs to copy data to or from the application in a robust fashion.
// CopyFromUser and CopyToUser are memcpy routines that expect to safely SIGSEGV when reading or writing application memory respectively.
// Returns zero if the memcpy completed, or crashes with SIGABRT and a log message if it faults.
[[nodiscard]]
size_t CopyFromUser(void* Dest, const void* Src, size_t Size);
[[nodiscard]]
size_t CopyToUser(void* Dest, const void* Src, size_t Size);
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED && defined(_M_ARM_64)
// These helpers just check if the user pointer is readable and writable.
// This is useful in an assert build that can be safely sprinkled through the syscall handler without overhead in release builds.
void VerifyIsReadable(const void* Src, size_t Size);
void VerifyIsReadableOrNull(const void* Src, size_t Size);
void VerifyIsWritable(void* Src, size_t Size);
void VerifyIsWritableOrNull(void* Src, size_t Size);
// Iterates a null-terminated string and checks if all bytes are readable
void VerifyIsStringReadable(const char* Src);
// Iterates a null-terminated string and checks if all bytes are readable. Up to MaxSize bytes are checked.
void VerifyIsStringReadableMaxSize(const char* Src, size_t MaxSize);
#else
inline void VerifyIsReadable(const void* Src, size_t Size) {
if (Src == nullptr) {
ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument");
}
}
inline void VerifyIsReadableOrNull(const void* Src, size_t Size) {}
inline void VerifyIsWritable(void* Src, size_t Size) {
if (Src == nullptr) {
ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument");
}
}
inline void VerifyIsWritableOrNull(void* Src, size_t Size) {}
inline void VerifyIsStringReadable(const char* Src) {
if (Src == nullptr) {
ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument");
}
}
inline void VerifyIsStringReadableMaxSize(const char* Src, size_t MaxSize) {
if (Src == nullptr) {
ERROR_AND_DIE_FMT("Unexpected nullptr syscall argument");
}
}
#endif
bool IsFaultLocation(uint64_t PC);
} // namespace FaultSafeUserMemAccess
} // namespace FEX::HLE
// Registers syscall for both 32bit and 64bit
#define REGISTER_SYSCALL_IMPL(name, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, ~0, FEXCore::IR::SyscallFlags::DEFAULT, lambda)
#define REGISTER_SYSCALL_IMPL_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, ~0, flags, lambda)
#define REGISTER_SYSCALL_IMPL_PASS_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, SYSCALL_DEF(name), flags, lambda)
#define REGISTER_SYSCALL_IMPL_INTERNAL(name, number, flags, lambda) \
do { \
FEX::HLE::x64::RegisterSyscall(Handler, FEX::HLE::x64::SYSCALL_x64_##name, (number), (flags), #name, (lambda)); \
FEX::HLE::x32::RegisterSyscall(Handler, FEX::HLE::x32::SYSCALL_x86_##name, (number), (flags), #name, (lambda)); \
} while (false)