mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 12:00:17 +02:00
The JIT was doing a bunch of additional work where it was saving and restoring registers and then juggling the arguments back in to a stack frame. All of this is nonsensical without the optimization where we could call syscalls inline without a stack frame. Instead remove this optimization entirely and behave like a "generic" syscall path always. The Linux syscall handler now pulls the arguments out of the CPU context directly and stores the result back in to RAX directly as well. This has knock-on effects where technically syscalls are going to be slightly faster because no stack frame setup for the arguments, but additionally we are going to be able to have syscalls be proper serialization points where we can interrupt the syscall and long-jump out without problems. Bumps the DiskCache version again because it causes codegen to change.
722 lines
24 KiB
C++
722 lines
24 KiB
C++
// SPDX-License-Identifier: MIT
|
|
/*
|
|
$info$
|
|
tags: LinuxSyscalls|syscalls-shared
|
|
$end_info$
|
|
*/
|
|
|
|
#include "LinuxSyscalls/Seccomp/BPFEmitter.h"
|
|
#include "LinuxSyscalls/Seccomp/SeccompEmulator.h"
|
|
|
|
#include "LinuxSyscalls/x32/Syscalls.h"
|
|
#include "LinuxSyscalls/x64/Syscalls.h"
|
|
#include "LinuxSyscalls/SignalDelegator.h"
|
|
|
|
#include <CodeEmitter/Emitter.h>
|
|
#include <FEXCore/Core/CoreState.h>
|
|
#include <FEXCore/fextl/fmt.h>
|
|
#include <FEXCore/HLE/SyscallHandler.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
#include <FEXHeaderUtils/Syscalls.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <linux/audit.h>
|
|
#include <linux/bpf_common.h>
|
|
#include <linux/filter.h>
|
|
#include <linux/seccomp.h>
|
|
#include <sys/prctl.h>
|
|
|
|
// seccomp
|
|
//
|
|
// global
|
|
// - kcmp - pass
|
|
// - mode_strict_support - pass
|
|
// - mode_strict_cannot_call_prctl - pass
|
|
// - no_new_privs_support - pass
|
|
// - mode_filter_support - pass
|
|
// - mode_filter_without_nnp - pass
|
|
// - filter_size_limits - pass
|
|
// - filter_chain_limits - pass
|
|
// - mode_filter_cannot_move_to_strict - pass
|
|
// - mode_filter_get_seccomp - pass
|
|
// - ALLOW_all - pass
|
|
// - empty_prog - pass
|
|
// - log_all - pass
|
|
// - unknown_ret_is_kill_inside - pass
|
|
// - unknown_ret_is_kill_above_allow - pass
|
|
// - KILL_all - pass
|
|
// - KILL_one - pass
|
|
// - KILL_one_arg_one - pass
|
|
// - KILL_one_arg_six - pass
|
|
// - KILL_thread - FAIL (unrelated to bpf)
|
|
// - KILL_process - FAIL (unrelated to bpf)
|
|
// - KILL_unknown - FAIL (unrelated to bpf)
|
|
// - arg_out_of_range - pass
|
|
// - ERRNO_valid - pass
|
|
// - ERRNO_zero - pass
|
|
// - ERRNO_capped - pass
|
|
// - ERRNO_order - pass
|
|
// - seccomp_syscall - pass
|
|
// - seccomp_syscall_mode_lock - pass
|
|
// - detect_seccomp_filter_flags - pass
|
|
// - TSYNC_first - pass
|
|
// - syscall_restart - FAIL (PTRACE)
|
|
// - filter_flag_log - pass
|
|
// - get_action_avail - FAIL (ptrace and user-notif)
|
|
// TSYNC
|
|
// - siblings_fail_prctl - pass
|
|
// - two_siblings_with_ancestor - FAIL (kill-thread not working quite right)
|
|
// - two_sibling_want_nnp - pass
|
|
// - two_siblings_with_one_divergence - pass
|
|
// - two_siblings_with_one_divergence_no_tid_in_err - pass
|
|
// - two_siblings_not_under_filter - FAIL (kill-thread not working quite right)
|
|
// - two_siblings_with_no_filter - FAIL (kill-thread not working quite right)
|
|
//
|
|
// user-notif stuff
|
|
// - get_metadata - SKIP (Needs root)
|
|
// - user_notification_basic - FAIL (user-notif)
|
|
// - user_notification_with_tsync - FAIL (user-notif)
|
|
// - user_notification_kill_in_middle - FAIL (user-notif)
|
|
// - user_notification_signal - FAIL (user-notif)
|
|
// - user_notification_closed_listener - FAIL (user-notif)
|
|
// - user_notification_child_pid_ns - FAIL (user-notif)
|
|
// - user_notification_sibling_pid_ns - FAIL (user-notif)
|
|
// - user_notification_fault_recv - FAIL (user-notif)
|
|
// - seccomp_get_notif_sizes - pass
|
|
// - user_notification_continue - FAIL (user-notif)
|
|
// - user_notification_filter_empty - FAIL (user-notif)
|
|
// - user_notification_filter_empty_threaded - FAIL (user-notif)
|
|
// - user_notification_addfd - FAIL (user-notif)
|
|
// - user_notification_addfd_rlimit - FAIL (user-notif)
|
|
// - user_notification_sync - FAIL (user-notif)
|
|
// - user_notification_fifo - FAIL (user-notif)
|
|
// - user_notification_wait_killable_pre_notification - FAIL (user-notif)
|
|
// - user_notification_wait_killable - FAIL (user-notif)
|
|
// - user_notification_wait_killable_fatal - FAIL (user-notif)
|
|
//
|
|
// O_SUSPEND_SECCOMP
|
|
// - setoptions - FAIL (ptrace)
|
|
// - seize - FAIL (ptrace)
|
|
// TRAP
|
|
// - dfl - pass
|
|
// - ign - pass
|
|
// - handler - pass
|
|
//
|
|
// precedence
|
|
// - allow_ok - pass
|
|
// - kill_is_highest - pass
|
|
// - kill_is_highest_in_any_order - pass
|
|
// - trap_is_second - pass
|
|
// - trap_is_second_in_any_order - pass
|
|
// - errno_is_third - pass
|
|
// - errno_is_third_in_any_order - pass
|
|
// - trace_is_fourth - pass
|
|
// - trace_is_fourth_in_any_order - pass
|
|
// - log_is_fifth - pass
|
|
// - log_is_fifth_in_any_order - pass
|
|
//
|
|
// TRACE_poke
|
|
// - ptrace unsupported
|
|
// TRACE_syscall
|
|
// - ptrace unsupported
|
|
|
|
namespace FEX::HLE {
|
|
uint64_t SeccompEmulator::Handle(FEXCore::Core::CpuStateFrame* Frame, uint32_t Op, uint32_t flags, void* arg) {
|
|
// If seccomp isn't enabled then say so.
|
|
if (!NeedsSeccomp) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (Op) {
|
|
case SECCOMP_SET_MODE_STRICT: return SetModeStrict(Frame, flags, arg);
|
|
case SECCOMP_SET_MODE_FILTER: return SetModeFilter(Frame, flags, static_cast<const sock_fprog*>(arg));
|
|
case SECCOMP_GET_ACTION_AVAIL: return GetActionAvail(flags, static_cast<const uint32_t*>(arg));
|
|
case SECCOMP_GET_NOTIF_SIZES: return GetNotifSizes(flags, static_cast<struct seccomp_notif_sizes*>(arg));
|
|
default:
|
|
// operation is unknown or is not supported by this kernel version or configuration.
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
// Equivalent to prctl(PR_GET_SECCOMP)
|
|
uint64_t SeccompEmulator::GetSeccomp(FEXCore::Core::CpuStateFrame* Frame) {
|
|
// If seccomp isn't enabled then say so.
|
|
if (!NeedsSeccomp) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
return Thread->SeccompMode;
|
|
}
|
|
|
|
void SeccompEmulator::InheritSeccompFilters(FEX::HLE::ThreadStateObject* Parent, FEX::HLE::ThreadStateObject* Child) {
|
|
// Don't interrupt me while I'm copying.
|
|
auto lk = FEXCore::MaskSignalsAndLockMutex(FilterMutex);
|
|
|
|
Child->Filters.resize(Parent->Filters.size());
|
|
|
|
for (size_t i = 0; i < Child->Filters.size(); ++i) {
|
|
auto& ParentFilter = Parent->Filters[i];
|
|
auto& ChildFilter = Child->Filters[i];
|
|
ChildFilter = ParentFilter;
|
|
std::atomic_ref<uint64_t>(ParentFilter->RefCount)++;
|
|
}
|
|
|
|
// Copy the operating mode.
|
|
Child->SeccompMode = Parent->SeccompMode;
|
|
}
|
|
|
|
void SeccompEmulator::FreeSeccompFilters(FEX::HLE::ThreadStateObject* Thread) {
|
|
// Don't talk to me when I'm busy deleting myself.
|
|
auto lk = FEXCore::MaskSignalsAndLockMutex(FilterMutex);
|
|
|
|
bool HasFiltersToDelete {};
|
|
for (auto& Filter : Thread->Filters) {
|
|
auto RefCount = std::atomic_ref<uint64_t>(Filter->RefCount).fetch_sub(1);
|
|
|
|
if (RefCount == 1) {
|
|
HasFiltersToDelete = true;
|
|
}
|
|
}
|
|
Thread->Filters.clear();
|
|
|
|
if (HasFiltersToDelete) {
|
|
// Garbage collect filters
|
|
std::erase_if(Filters, [](auto& Filter) {
|
|
if (std::atomic_ref<uint64_t>(Filter.RefCount).load(std::memory_order_relaxed) != 0) {
|
|
return false;
|
|
}
|
|
|
|
FEXCore::Allocator::munmap(reinterpret_cast<void*>(Filter.Func), Filter.MappedSize);
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
struct SerializedFilter {
|
|
size_t CodeSize;
|
|
uint32_t FilterInstructions;
|
|
bool ShouldLog;
|
|
char Code[];
|
|
};
|
|
|
|
struct SerializationHeader {
|
|
size_t NumberOfFilters;
|
|
uint32_t SeccompMode;
|
|
SerializedFilter Filters[];
|
|
};
|
|
|
|
std::optional<int> SeccompEmulator::SerializeFilters(FEXCore::Core::CpuStateFrame* Frame) {
|
|
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
if (Thread->SeccompMode == SECCOMP_MODE_DISABLED) {
|
|
// Didn't have seccomp enabled.
|
|
return std::nullopt;
|
|
}
|
|
|
|
int FD = memfd_create("seccomp_filters", MFD_ALLOW_SEALING);
|
|
if (FD == -1) {
|
|
// Couldn't create memfd
|
|
LogMan::Msg::EFmt("Couldn't create seccomp filter FD!");
|
|
return -1;
|
|
}
|
|
|
|
SerializationHeader Header {
|
|
.NumberOfFilters = Thread->Filters.size(),
|
|
.SeccompMode = Thread->SeccompMode,
|
|
};
|
|
|
|
int Res = write(FD, &Header, sizeof(Header));
|
|
if (Res == -1) {
|
|
LogMan::Msg::EFmt("Couldn't write header!");
|
|
close(FD);
|
|
return -1;
|
|
}
|
|
|
|
for (auto& Filter : Thread->Filters) {
|
|
SerializedFilter SFilter {
|
|
.CodeSize = Filter->MappedSize,
|
|
.FilterInstructions = Filter->FilterInstructions,
|
|
.ShouldLog = Filter->ShouldLog,
|
|
};
|
|
|
|
Res = write(FD, &SFilter, sizeof(SFilter));
|
|
if (Res == -1) {
|
|
LogMan::Msg::EFmt("Couldn't write filter header!");
|
|
close(FD);
|
|
return -1;
|
|
}
|
|
|
|
Res = write(FD, (const void*)Filter->Func, Filter->MappedSize);
|
|
if (Res == -1) {
|
|
LogMan::Msg::EFmt("Couldn't write filter!");
|
|
close(FD);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Reset FD to start.
|
|
lseek(FD, 0, SEEK_SET);
|
|
|
|
// Seal everything about this FD.
|
|
if (fcntl(FD, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_FUTURE_WRITE) == -1) {
|
|
LogMan::Msg::IFmt("Couldn't seal seccomp serialize FD. Nefarious code could modify");
|
|
}
|
|
|
|
return FD;
|
|
}
|
|
|
|
void SeccompEmulator::DeserializeFilters(FEXCore::Core::CpuStateFrame* Frame, int FD) {
|
|
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
|
|
SerializationHeader Header;
|
|
int Res = read(FD, &Header, sizeof(Header));
|
|
if (Res == -1 || Res != sizeof(Header)) {
|
|
LogMan::Msg::EFmt("Couldn't read Seccomp header!");
|
|
close(FD);
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < Header.NumberOfFilters; ++i) {
|
|
SerializedFilter SFilter;
|
|
|
|
Res = read(FD, &SFilter, sizeof(SFilter));
|
|
if (Res == -1 || Res != sizeof(SFilter)) {
|
|
LogMan::Msg::EFmt("Couldn't read Seccomp Filter header!");
|
|
close(FD);
|
|
return;
|
|
}
|
|
auto Ptr = FEXCore::Allocator::mmap(nullptr, SFilter.CodeSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if (Ptr == (void*)~0ULL) {
|
|
LogMan::Msg::EFmt("Couldn't allocate ptr for filter!");
|
|
close(FD);
|
|
return;
|
|
}
|
|
|
|
Res = read(FD, Ptr, SFilter.CodeSize);
|
|
if (Res == -1 || Res != SFilter.CodeSize) {
|
|
LogMan::Msg::EFmt("Couldn't read Seccomp Filter code!");
|
|
close(FD);
|
|
return;
|
|
}
|
|
|
|
::mprotect(Ptr, SFilter.CodeSize, PROT_READ | PROT_EXEC);
|
|
|
|
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Ptr), SFilter.CodeSize);
|
|
|
|
auto& it =
|
|
Filters.emplace_back(SeccompFilterInfo {(SeccompFilterFunc)Ptr, 1, SFilter.CodeSize, SFilter.FilterInstructions, SFilter.ShouldLog});
|
|
TotalFilterInstructions += SFilter.FilterInstructions;
|
|
|
|
// Append the filter to the thread.
|
|
Thread->Filters.emplace_back(&it);
|
|
}
|
|
|
|
Thread->SeccompMode = Header.SeccompMode;
|
|
close(FD);
|
|
}
|
|
|
|
SeccompEmulator::ExecuteFilterResult
|
|
SeccompEmulator::ExecuteFilter(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC, FEX::HLE::SyscallArguments* Args) {
|
|
if (!HasFilter(Frame)) {
|
|
// Seccomp not installed. Allow it.
|
|
return {false, 0};
|
|
}
|
|
// Reconstruct the RIP from the JITPC.
|
|
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
const uint64_t RIP = Thread->Thread->CTX->RestoreRIPFromHostPC(Frame->Thread, JITPC);
|
|
|
|
const auto Arch = Is64BitMode() ? AUDIT_ARCH_X86_64 : AUDIT_ARCH_I386;
|
|
bool ShouldLog {};
|
|
uint32_t SeccompResult {};
|
|
|
|
{
|
|
BPFEmitter::WorkingBuffer Data {
|
|
.Data =
|
|
{
|
|
.nr = static_cast<int32_t>(Args->Argument[0]),
|
|
.arch = Arch,
|
|
.instruction_pointer = RIP,
|
|
.args =
|
|
{
|
|
Args->Argument[1],
|
|
Args->Argument[2],
|
|
Args->Argument[3],
|
|
Args->Argument[4],
|
|
Args->Argument[5],
|
|
Args->Argument[6],
|
|
},
|
|
},
|
|
};
|
|
|
|
bool HasResult {};
|
|
// seccomp filters are executed from latest added to oldest.
|
|
for (auto it = Thread->Filters.rbegin(); it != Thread->Filters.rend(); ++it) {
|
|
// Explicitly zero scratch memory.
|
|
memset(&Data.ScratchMemory, 0, sizeof(Data.ScratchMemory));
|
|
|
|
uint32_t CurrentResult = (*it)->Func(0, 0, 0, 0, &Data);
|
|
|
|
if (!HasResult) {
|
|
SeccompResult = CurrentResult;
|
|
ShouldLog = (*it)->ShouldLog;
|
|
HasResult = true;
|
|
continue;
|
|
}
|
|
|
|
const int16_t CurrentAction = (CurrentResult & SECCOMP_RET_ACTION_FULL) >> 16;
|
|
const int16_t Action = (SeccompResult & SECCOMP_RET_ACTION_FULL) >> 16;
|
|
|
|
// All actions are executed but the first highest precendent result is returned.
|
|
// Precedent order from highest priority to lowest:
|
|
// - SECCOMP_RET_KILL_PROCESS (0x8000, -32768)
|
|
// - SECCOMP_RET_KILL_THREAD (0x0000, 0)
|
|
// - SECCOMP_RET_TRAP (0x0003, 3)
|
|
// - SECCOMP_RET_ERRNO (0x0005, 5)
|
|
// - SECCOMP_RET_USER_NOTIF (0x7fc0, 32704)
|
|
// - SECCOMP_RET_TRACE (0x7ff0, 32752)
|
|
// - SECCOMP_RET_LOG (0x7ffc, 32764)
|
|
// - SECCOMP_RET_ALLOW (0x7fff, 32767)
|
|
if (CurrentAction < Action) {
|
|
SeccompResult = CurrentResult;
|
|
ShouldLog = (*it)->ShouldLog;
|
|
}
|
|
}
|
|
}
|
|
|
|
const auto ActionMasked = SeccompResult & SECCOMP_RET_ACTION_FULL;
|
|
const auto DataMasked = SeccompResult & SECCOMP_RET_DATA;
|
|
|
|
// Logging rules
|
|
// - Log if explicitly returning SECCOMP_RET_LOG
|
|
// - Log if the filter enabled the logging flag and the action is something other than SECCOMP_RET_ALLOW.
|
|
if ((ShouldLog && ActionMasked != SECCOMP_RET_ALLOW) || ActionMasked == SECCOMP_RET_LOG) {
|
|
int Signal = 0;
|
|
switch (ActionMasked) {
|
|
case SECCOMP_RET_KILL_PROCESS:
|
|
case SECCOMP_RET_KILL_THREAD: Signal = GetKillSignal(); break;
|
|
case SECCOMP_RET_TRAP: Signal = SIGSYS; break;
|
|
default: break;
|
|
}
|
|
|
|
// With real secommp the logs go to dmesg. log through FEX since we can't use dmesg.
|
|
// ex: `[13572.669277] audit: type=1326 audit(1715469332.533:62): auid=1000 uid=1000 gid=1000 ses=2 subj=unconfined pid=52546 comm="seccomp_bpf"
|
|
// exe="/mnt/Work/Projects/work/linux/tools/testing/selftests/seccomp/seccomp_bpf" sig=0 arch=c000003e syscall=39 compat=0 ip=0x7d789352725d code=0x7ffc0000`
|
|
timespec tp {};
|
|
clock_gettime(CLOCK_MONOTONIC, &tp);
|
|
LogMan::Msg::IFmt("audit: type={} audit({}.{:03}:{}): uid={} gid={} pid={} comm={} sig={} arch={:x} syscall={} ip=0x{:x} code=0x{:x}",
|
|
AUDIT_SECCOMP, tp.tv_sec, tp.tv_nsec / 1'000'000, AuditSerialIncrement(), ::getuid(), ::getgid(), ::getpid(),
|
|
Filename(), Signal, Arch, Args->Argument[0], RIP, SeccompResult);
|
|
}
|
|
|
|
switch (ActionMasked) {
|
|
// Unknown actions behave like RET_KILL_PROCESS.
|
|
default:
|
|
case SECCOMP_RET_KILL_PROCESS: {
|
|
const int KillSignal = GetKillSignal();
|
|
// Ignores signal handler and sigmask
|
|
uint64_t Mask = 1ULL << (KillSignal - 1);
|
|
SignalDelegation->GuestSigProcMask(Thread, SIG_UNBLOCK, &Mask, nullptr);
|
|
SignalDelegation->UninstallHostHandler(KillSignal);
|
|
kill(0, KillSignal);
|
|
break;
|
|
}
|
|
case SECCOMP_RET_KILL_THREAD: {
|
|
// Ignores signal handler and sigmask
|
|
uint64_t Mask = 1 << (SIGSYS - 1);
|
|
SignalDelegation->GuestSigProcMask(Thread, SIG_UNBLOCK, &Mask, nullptr);
|
|
SignalDelegation->UninstallHostHandler(SIGSYS);
|
|
FHU::Syscalls::tgkill(::getpid(), ::gettid(), SIGSYS);
|
|
break;
|
|
}
|
|
case SECCOMP_RET_TRAP: {
|
|
siginfo_t Info {
|
|
.si_signo = SIGSYS,
|
|
.si_errno = static_cast<int32_t>(DataMasked),
|
|
.si_code = 1, // SYS_SECCOMP
|
|
};
|
|
|
|
Info.si_call_addr = reinterpret_cast<void*>(RIP);
|
|
Info.si_syscall = Args->Argument[0];
|
|
Info.si_arch = Arch;
|
|
|
|
SignalDelegation->QueueSignal(::getpid(), ::gettid(), SIGSYS, &Info, true);
|
|
break;
|
|
}
|
|
case SECCOMP_RET_ERRNO: {
|
|
// errno return is clamped.
|
|
return {true, -(std::min<uint64_t>(DataMasked, 4095))};
|
|
}
|
|
case SECCOMP_RET_TRACE: {
|
|
// When no tracer attached, behave like RET_ERRNO returning ENOSYS.
|
|
// TODO: Implement once FEX supports tracing.
|
|
return {true, static_cast<uint64_t>(-ENOSYS)};
|
|
}
|
|
case SECCOMP_RET_USER_NOTIF:
|
|
case SECCOMP_RET_LOG:
|
|
case SECCOMP_RET_ALLOW: break;
|
|
}
|
|
|
|
return {false, 0};
|
|
}
|
|
|
|
// Equivalent to seccomp(SECCOMP_SET_MODE_STRICT, ...);
|
|
uint64_t SeccompEmulator::SetModeStrict(FEXCore::Core::CpuStateFrame* Frame, uint32_t flags, const void* arg) {
|
|
const auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
|
|
if (::prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 0) {
|
|
// The caller did not have the CAP_SYS_ADMIN capability in its user namespace, or had not set no_new_privs before using SECCOMP_SET_MODE_FILTER.
|
|
return -EACCES;
|
|
}
|
|
|
|
if (flags != 0) {
|
|
// The specified flags are invalid for the given operation.
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (arg != nullptr) {
|
|
// The specified arg are invalid for the given operation.
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (Thread->SeccompMode == SECCOMP_MODE_FILTER) {
|
|
// Filter mode cannot move to strict
|
|
return -EINVAL;
|
|
}
|
|
|
|
#define syscall_nr (offsetof(struct seccomp_data, nr))
|
|
#define ALLOW_SYSCALL(name) \
|
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, FEX::HLE::x64::SYSCALL_x64_##name, 0, 1), BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)
|
|
#define ALLOW_SYSCALL_x32(name) \
|
|
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, FEX::HLE::x32::SYSCALL_x86_##name, 0, 1), BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)
|
|
|
|
constexpr static struct sock_filter strict_filter_x64[] = {
|
|
// Load syscall number
|
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
|
|
|
|
// Allow read, write, exit, exit_group, and sigreturn
|
|
ALLOW_SYSCALL(read),
|
|
ALLOW_SYSCALL(write),
|
|
ALLOW_SYSCALL(exit),
|
|
ALLOW_SYSCALL(exit_group),
|
|
ALLOW_SYSCALL(rt_sigreturn),
|
|
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL_PROCESS),
|
|
};
|
|
|
|
constexpr static struct sock_filter strict_filter_x32[] = {
|
|
// Load syscall number
|
|
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
|
|
|
|
// Allow read, write, exit, exit_group, and sigreturn
|
|
ALLOW_SYSCALL_x32(read),
|
|
ALLOW_SYSCALL_x32(write),
|
|
ALLOW_SYSCALL_x32(exit),
|
|
ALLOW_SYSCALL_x32(exit_group),
|
|
ALLOW_SYSCALL_x32(rt_sigreturn),
|
|
ALLOW_SYSCALL_x32(sigreturn),
|
|
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL_PROCESS),
|
|
};
|
|
|
|
const sock_fprog prog_x64 {
|
|
.len = (unsigned short)(sizeof(strict_filter_x64) / sizeof(strict_filter_x64[0])),
|
|
.filter = const_cast<struct sock_filter*>(strict_filter_x64),
|
|
};
|
|
|
|
const sock_fprog prog_x32 {
|
|
.len = (unsigned short)(sizeof(strict_filter_x32) / sizeof(strict_filter_x32[0])),
|
|
.filter = const_cast<struct sock_filter*>(strict_filter_x32),
|
|
};
|
|
CurrentKillSignal = SIGKILL;
|
|
const sock_fprog* prog = Is64BitMode() ? &prog_x64 : &prog_x32;
|
|
SetModeFilter(Frame, 0, prog);
|
|
Thread->SeccompMode = SECCOMP_MODE_STRICT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t SeccompEmulator::CanDoTSync(FEXCore::Core::CpuStateFrame* Frame) {
|
|
auto ParentThread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
auto Threads = SyscallHandler->TM.GetThreads();
|
|
|
|
for (auto& Thread : *Threads) {
|
|
if (Thread == ParentThread) {
|
|
// Skip same thread.
|
|
continue;
|
|
}
|
|
|
|
if (Thread->SeccompMode == SECCOMP_MODE_DISABLED) {
|
|
// Threads which have seccomp disabled are safe to TSync
|
|
continue;
|
|
}
|
|
|
|
if (Thread->SeccompMode != ParentThread->SeccompMode) {
|
|
/// If the seccomp mode differs between threads then it can't tsync.
|
|
/// Strict versus filter mode aren't tsync compatible.
|
|
return Thread->ThreadInfo.TID;
|
|
}
|
|
|
|
if (Thread->Filters.size() != ParentThread->Filters.size()) {
|
|
// If the filter count doesn't even match then it can't tsync.
|
|
return Thread->ThreadInfo.TID;
|
|
}
|
|
|
|
// Walk each filter and ensure the entry points are the same and in the same order.
|
|
for (size_t i = 0; i < ParentThread->Filters.size(); ++i) {
|
|
if (Thread->Filters[i]->Func != ParentThread->Filters[i]->Func) {
|
|
/// Entry point mismatch, not the same filter.
|
|
/// Not tsync compatible.
|
|
return Thread->ThreadInfo.TID;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Everything matched. tsync compatible!
|
|
return 0;
|
|
}
|
|
|
|
void SeccompEmulator::TSyncFilters(FEXCore::Core::CpuStateFrame* Frame) {
|
|
auto ParentThread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
auto Threads = SyscallHandler->TM.GetThreads();
|
|
|
|
for (auto& Thread : *Threads) {
|
|
if (Thread == ParentThread) {
|
|
// Skip same thread.
|
|
continue;
|
|
}
|
|
|
|
Thread->Filters.clear();
|
|
Thread->Filters = ParentThread->Filters;
|
|
for (auto& Filter : ParentThread->Filters) {
|
|
// Need to increment all the refcounters
|
|
std::atomic_ref<uint64_t>(Filter->RefCount)++;
|
|
}
|
|
Thread->SeccompMode = ParentThread->SeccompMode;
|
|
}
|
|
}
|
|
|
|
// Equivalent to seccomp(SECCOMP_SET_MODE_FILTER, ...);
|
|
uint64_t SeccompEmulator::SetModeFilter(FEXCore::Core::CpuStateFrame* Frame, uint32_t flags, const sock_fprog* prog) {
|
|
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
|
|
|
|
// Order of checks in this function matter
|
|
// 1) Check flags
|
|
// 2) Check if program is invalid
|
|
uint32_t SUPPORTED_FLAGS = SECCOMP_FILTER_FLAG_TSYNC | // 1U << 0
|
|
SECCOMP_FILTER_FLAG_LOG | // 1U << 1
|
|
SECCOMP_FILTER_FLAG_SPEC_ALLOW | // 1U << 2
|
|
// SECCOMP_FILTER_FLAG_NEW_LISTENER | // 1U << 3
|
|
SECCOMP_FILTER_FLAG_TSYNC_ESRCH | // 1U << 4
|
|
0;
|
|
|
|
const bool DoingTsync = flags & SECCOMP_FILTER_FLAG_TSYNC;
|
|
|
|
if (flags & ~SUPPORTED_FLAGS) {
|
|
// Unknown flags passed in.
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((flags & SECCOMP_FILTER_FLAG_TSYNC) && (flags & SECCOMP_FILTER_FLAG_NEW_LISTENER) && !(flags & SECCOMP_FILTER_FLAG_TSYNC_ESRCH)) {
|
|
/// If NEW_LISTENER and TSYNC are both used then TSYNC_ESRCH must also be set.
|
|
/// Otherwise on error there would be no way to tell the difference between success and failure.
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!prog) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (::prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 0) {
|
|
// The caller did not have the CAP_SYS_ADMIN capability in its user namespace, or had not set no_new_privs before using SECCOMP_SET_MODE_FILTER.
|
|
return -EACCES;
|
|
}
|
|
|
|
if (prog->len > BPF_MAXINSNS || prog->len == 0) {
|
|
// operation specified SECCOMP_SET_MODE_FILTER, but the filter program pointed to by args was not valid or the length of the filter
|
|
// program was zero or exceeded BPF_MAXINSNS (4096) instructions.
|
|
return -EINVAL;
|
|
}
|
|
|
|
// Don't interrupt me while I'm jitting.
|
|
auto lk = FEXCore::MaskSignalsAndLockMutex(FilterMutex);
|
|
|
|
const size_t TotalFinalInstructions = TotalFilterInstructions + prog->len + Thread->Filters.size() * BPF_MULTIFILTERPENALTY;
|
|
if (TotalFinalInstructions > BPF_MAX_INSNS_PER_PATH) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if constexpr (false) {
|
|
// Useful for debugging seccomp problems.
|
|
DumpProgram(prog);
|
|
}
|
|
|
|
if (DoingTsync) {
|
|
auto TSyncThread = CanDoTSync(Frame);
|
|
if (TSyncThread != 0) {
|
|
if (flags & SECCOMP_FILTER_FLAG_TSYNC_ESRCH) {
|
|
// This flag explicitly ensures that if TSYNC can't sync then it won't return a TID.
|
|
return -ESRCH;
|
|
} else {
|
|
// Return the TID that caused a tsync problem.
|
|
return TSyncThread;
|
|
}
|
|
}
|
|
}
|
|
|
|
BPFEmitter emit {};
|
|
const bool LoggingEnabled = flags & SECCOMP_FILTER_FLAG_LOG;
|
|
auto Result = emit.JITFilter(flags, prog);
|
|
if (Result == 0) {
|
|
auto& it = Filters.emplace_back(SeccompFilterInfo {(SeccompFilterFunc)emit.GetFunc(), 1, emit.AllocationSize(), prog->len, LoggingEnabled});
|
|
TotalFilterInstructions += prog->len;
|
|
|
|
// Append the filter to the thread.
|
|
Thread->Filters.emplace_back(&it);
|
|
Thread->SeccompMode = SECCOMP_MODE_FILTER;
|
|
if (flags & SECCOMP_FILTER_FLAG_TSYNC) {
|
|
TSyncFilters(Frame);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
// Equivalent to seccomp(SECCOMP_GET_ACTION_AVAIL, ...);
|
|
uint64_t SeccompEmulator::GetActionAvail(uint32_t flags, const uint32_t* action) {
|
|
if (flags != 0) {
|
|
// Unknown flags passed in
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!action) {
|
|
// Invalid action
|
|
return -EFAULT;
|
|
}
|
|
switch (*action) {
|
|
case SECCOMP_RET_KILL_PROCESS:
|
|
case SECCOMP_RET_KILL_THREAD:
|
|
case SECCOMP_RET_TRAP:
|
|
case SECCOMP_RET_ERRNO:
|
|
case SECCOMP_RET_LOG:
|
|
case SECCOMP_RET_ALLOW: return 0;
|
|
case SECCOMP_RET_USER_NOTIF:
|
|
case SECCOMP_RET_TRACE:
|
|
default: break;
|
|
}
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
// Equivalent to seccomp(SECCOMP_GET_NOTIF_SIZES, ...);
|
|
uint64_t SeccompEmulator::GetNotifSizes(uint32_t flags, struct seccomp_notif_sizes* sizes) {
|
|
if (flags != 0) {
|
|
// Unknown flags passed in
|
|
return -EINVAL;
|
|
}
|
|
sizes->seccomp_notif = sizeof(struct seccomp_notif);
|
|
sizes->seccomp_notif_resp = sizeof(struct seccomp_notif_resp);
|
|
sizes->seccomp_data = sizeof(struct seccomp_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace FEX::HLE
|