From 1fbf3a42d2d6f6e6e0b73c19a9f1e8a3eec12fe6 Mon Sep 17 00:00:00 2001 From: Gruzzberry Date: Sun, 25 Oct 2026 23:03:58 +0000 Subject: [PATCH] fix(libkernel): fail the signal action, mask, stack and pending calls with EFAULT on an unusable pointer FreeBSD 11.4's sys_sigaction, sys_sigprocmask and sys_sigaltstack copy their input in before validating it and copy the old value out after the change, and sys_sigpending copies out; a bad pointer gives EFAULT. The guest pointers were dereferenced directly. Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libc/include/General.hpp | 1 + core/libs/prx/libc/src/General.cpp | 7 ++++ .../libs/prx/libkernel/System/src/Signals.cpp | 42 ++++++++++++++----- core/libs/tests/GuestSignals.cpp | 41 ++++++++++++++++++ 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 4d4db4d068..17d627e327 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -31,6 +31,7 @@ extern "C" int CopyGuestPath_nid_no_patch(const char* source, std::string& desti extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path); extern "C" bool GuestCopyOut_nid_no_patch(void* destination, const void* source, std::size_t bytes); +extern "C" bool GuestCopyIn_nid_no_patch(void* destination, const void* source, std::size_t bytes); extern "C" std::filesystem::path ResolvePathOrRelinkedModule_nid_no_patch(const char* path); extern "C" std::filesystem::path ResolveArchivedPath_nid_no_patch(const char* path, bool module = false); extern "C" std::filesystem::path ResolveDiskPath_nid_no_patch(const char* path); diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index f905c3ff30..cfd71da184 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -837,6 +837,13 @@ extern "C" bool GuestCopyOut_nid_no_patch(void* destination, const void* source, #endif } +extern "C" bool GuestCopyIn_nid_no_patch(void* destination, const void* source, std::size_t bytes) { + if (bytes == 0) return true; + const auto start = reinterpret_cast(source); + if (start == 0 || bytes > UINTPTR_MAX - start) return false; + return CopyGuestBytes(destination, source, bytes); +} + extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) { auto resolved = ResolveLocked(path, false, true); AppArchiveRefuseOnly_nid_no_patch(resolved, path, "reading"); diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 091f1db7d4..acab049df9 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -542,7 +542,7 @@ thread_local GuestStack alternateStack{nullptr, 0, SsDisable}; namespace { -int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr) { +int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr, GuestSignalSet* guestPrevious = nullptr) { if (set != nullptr && (how < 1 || how > 3)) { *__error_nid_postfix() = 22; return -1; } { std::lock_guard lock(registration); @@ -576,8 +576,10 @@ int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previo } } } + const bool faulted = guestPrevious != nullptr && !GuestCopyOut_nid_no_patch(guestPrevious, previousSet, sizeof(*previousSet)); std::atomic_thread_fence(std::memory_order_seq_cst); if (DeliverUnblockedPending() && caught != nullptr) *caught = true; + if (faulted) { *__error_nid_postfix() = 14; return -1; } return 0; } @@ -603,6 +605,11 @@ GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) { return previous; } int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) { + GuestSigaction requested{}; + if (action != nullptr) { + if (!GuestCopyIn_nid_no_patch(&requested, action, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; } + action = &requested; + } if (guest < 1 || guest > MaxSignal) { *__error_nid_postfix() = 22; return -1; } if (action && action->handler > 1 && (action->flags & ~SaDefinedFlags) != 0) { *__error_nid_postfix() = 22; return -1; } if (action && (guest == GuestSigkill || guest == GuestSigstop) && action->handler != 0) { *__error_nid_postfix() = 22; return -1; } @@ -614,7 +621,7 @@ int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, Gue dispositions[guest] = *action; DiscardPendingIfIgnored(guest, *action); } - if (previous) *previous = old; + if (previous && !GuestCopyOut_nid_no_patch(previous, &old, sizeof(old))) { *__error_nid_postfix() = 14; return -1; } return 0; } int APS5_VABI _sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) { @@ -664,18 +671,27 @@ int APS5_VABI pthread_kill_nid_postfix(Pthread thread, int guest) { } int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previous) { GuestStack replacement = alternateStack; + GuestStack requested{}; if (stack != nullptr) { + if (!GuestCopyIn_nid_no_patch(&requested, stack, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; } + stack = &requested; if ((stack->flags & ~SsDisable) != 0) { *__error_nid_postfix() = 22; return -1; } if (stack->flags & SsDisable) replacement.flags = SsDisable; else if (stack->size < MinSignalStackSize) { *__error_nid_postfix() = 12; return -1; } else replacement = {stack->sp, stack->size, 0}; } - if (previous != nullptr) *previous = alternateStack; + const GuestStack old = alternateStack; alternateStack = replacement; + if (previous != nullptr && !GuestCopyOut_nid_no_patch(previous, &old, sizeof(old))) { *__error_nid_postfix() = 14; return -1; } return 0; } int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) { - return ChangeBlockedMask(how, set, previousSet, true); + GuestSignalSet requested{}, old{}; + if (set != nullptr) { + if (!GuestCopyIn_nid_no_patch(&requested, set, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; } + set = &requested; + } + return ChangeBlockedMask(how, set, previousSet != nullptr ? &old : nullptr, true, nullptr, previousSet); } int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet) { @@ -684,8 +700,10 @@ int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSe } int APS5_VABI pthread_sigmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) { - if (set != nullptr && (how < 1 || how > 3)) return 22; - return _sigprocmask_nid_postfix(how, set, previousSet); + const int saved = *__error_nid_postfix(); + const int error = _sigprocmask_nid_postfix(how, set, previousSet) == 0 ? 0 : *__error_nid_postfix(); + *__error_nid_postfix() = saved; + return error; } int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet* set, void* info, const KernelTimespec* timeout) { @@ -745,10 +763,14 @@ int APS5_VABI _sigsuspend_nid_postfix(const GuestSignalSet* set) { int APS5_VABI sigpending_nid_postfix(GuestSignalSet* set) { if (set == nullptr) { *__error_nid_postfix() = 14; return -1; } - auto& pending = Pending(); - std::lock_guard lock(pending.lock); - for (int word = 0; word < 4; ++word) - set->bits[word] = pending.words[word].load(std::memory_order_relaxed) & BlockedWord(word); + GuestSignalSet result{}; + { + auto& pending = Pending(); + std::lock_guard lock(pending.lock); + for (int word = 0; word < 4; ++word) + result.bits[word] = pending.words[word].load(std::memory_order_relaxed) & BlockedWord(word); + } + if (!GuestCopyOut_nid_no_patch(set, &result, sizeof(result))) { *__error_nid_postfix() = 14; return -1; } return 0; } } diff --git a/core/libs/tests/GuestSignals.cpp b/core/libs/tests/GuestSignals.cpp index 8afec0ead8..fe96c136d1 100644 --- a/core/libs/tests/GuestSignals.cpp +++ b/core/libs/tests/GuestSignals.cpp @@ -66,8 +66,49 @@ void APS5_VABI MaskCallback(int value) { } static void Require(bool value) { if (!value) std::abort(); } +static void CheckSignalPointerFaults() { + constexpr std::size_t page = 0x4000; + void* memory = nullptr; + Require(sceKernelMapFlexibleMemory(&memory, page, 3, 0) == 0 && memory != nullptr); + Require(sceKernelMprotect(memory, page, 1) == 0); + const auto readOnly = memory; + const auto unreadable = reinterpret_cast(std::uintptr_t{0x10}); + const auto fails = [](int result) { return result == -1 && *__error_nid_postfix() == 14; }; + constexpr int user = 30; + GuestSigaction previous{}; + Require(fails(sigaction_nid_postfix(user, static_cast(unreadable), nullptr))); + Require(fails(sigaction_nid_postfix(0, static_cast(unreadable), nullptr))); + Require(fails(sigaction_nid_postfix(user, nullptr, static_cast(unreadable)))); + Require(fails(sigaction_nid_postfix(user, nullptr, static_cast(readOnly)))); + Require(sigaction_nid_postfix(0, nullptr, &previous) == -1 && *__error_nid_postfix() == 22); + Require(sigaction_nid_postfix(user, nullptr, &previous) == 0); + GuestSignalSet blocked{}, set{{1u << (user - 1), 0, 0, 0}}, none{}; + Require(sigprocmask_nid_postfix(3, nullptr, &blocked) == 0); + Require(fails(sigprocmask_nid_postfix(1, unreadable, nullptr))); + Require(fails(sigprocmask_nid_postfix(9, unreadable, nullptr))); + Require(sigprocmask_nid_postfix(9, &set, nullptr) == -1 && *__error_nid_postfix() == 22); + Require(fails(sigprocmask_nid_postfix(1, nullptr, unreadable))); + Require(fails(sigprocmask_nid_postfix(1, nullptr, readOnly))); + Require(sigprocmask_nid_postfix(1, nullptr, &none) == 0 && std::memcmp(&none, &blocked, sizeof(none)) == 0); + Require(fails(sigprocmask_nid_postfix(1, &set, readOnly))); + Require(sigprocmask_nid_postfix(1, nullptr, &none) == 0 && (none.bits[0] & set.bits[0]) != 0); + Require(sigprocmask_nid_postfix(3, &blocked, nullptr) == 0); + Require(pthread_sigmask_nid_postfix(1, static_cast(unreadable), nullptr) == 14); + Require(pthread_sigmask_nid_postfix(1, nullptr, static_cast(readOnly)) == 14); + Require(pthread_sigmask_nid_postfix(9, static_cast(unreadable), nullptr) == 14); + Require(pthread_sigmask_nid_postfix(9, &set, nullptr) == 22); + Require(sigprocmask_nid_postfix(3, &blocked, nullptr) == 0); + Require(fails(sigaltstack_nid_postfix(static_cast(unreadable), nullptr))); + Require(fails(sigaltstack_nid_postfix(nullptr, static_cast(unreadable)))); + Require(fails(sigaltstack_nid_postfix(nullptr, static_cast(readOnly)))); + Require(fails(sigpending_nid_postfix(static_cast(unreadable)))); + Require(fails(sigpending_nid_postfix(static_cast(readOnly)))); + Require(fails(sigpending_nid_postfix(nullptr))); +} + int main() { const auto invalid = reinterpret_cast(static_cast(-1)); + CheckSignalPointerFaults(); const auto ignore = reinterpret_cast(std::uintptr_t{1}); Require(signal_nid_postfix(9, Callback) == invalid); Require(*__error_nid_postfix() == 22);