#include "prx/libc/libc.hpp" #include "prx/libc/include/GuestArena.hpp" #include #include #include #include #include #include namespace { void Require(bool value, const char* message) { if (value) return; std::fprintf(stderr, "sigvec: %s\n", message); std::abort(); } GuestSignalSet Mask() { GuestSignalSet value{}; Require(_sigprocmask_nid_postfix(0, nullptr, &value) == 0, "query mask"); return value; } bool Equal(const GuestSignalSet& first, const GuestSignalSet& second) { for (int word = 0; word < 4; ++word) if (first.bits[word] != second.bits[word]) return false; return true; } bool Equal(const GuestSignalVector& first, const GuestSignalVector& second) { return first.handler == second.handler && first.mask == second.mask && first.flags == second.flags; } int received = 0; int signalReceived = 0; void APS5_VABI Handler(int signal) { ++received; signalReceived = signal; } void Vectors() { constexpr int signal = 30; const auto handler = reinterpret_cast(&Handler); GuestSigaction initial{}; Require(sigaction_nid_postfix(signal, nullptr, &initial) == 0, "save original action"); for (int flags = 0; flags < 128; ++flags) { const GuestSignalVector replacement{handler, std::bit_cast(0x80000005u), flags}; Require(sigvec_nid_disambig1(signal, &replacement, nullptr) == 0, "install each supported flag combination"); GuestSigaction actual{}; Require(sigaction_nid_postfix(signal, nullptr, &actual) == 0, "query modern action"); Require(actual.handler == handler && actual.flags == (flags ^ 2), "legacy interrupt flag reverses restart flag"); Require(Equal(actual.mask, GuestSignalSet{{0x80000005, 0, 0, 0}}), "legacy vector zero extends mask"); GuestSignalVector queried{}; Require(sigvec_nid_disambig1(signal, nullptr, &queried) == 0 && Equal(queried, replacement), "query converts flags and mask back"); } const GuestSigaction extended{handler, 0x13, {{0x87654321, 0x12345678, 0x80000001, 0x7fffffff}}}; Require(sigaction_nid_postfix(signal, &extended, nullptr) == 0, "install action with extended mask"); GuestSignalVector queried{}; *__error_nid_postfix() = 81; Require(sigvec_nid_disambig1(signal, nullptr, &queried) == 0, "query extended action"); Require(Equal(queried, GuestSignalVector{handler, std::bit_cast(0x87654321u), 0x11}), "query truncates modern mask to low word"); Require(*__error_nid_postfix() == 81, "successful vector preserves errno"); GuestSignalVector alias{handler, 7, 0}; Require(sigvec_nid_disambig1(signal, &alias, &alias) == 0 && Equal(alias, queried), "aliased input and output returns old action"); GuestSignalVector newState{}; Require(sigvec_nid_disambig1(signal, nullptr, &newState) == 0 && Equal(newState, GuestSignalVector{handler, 7, 0}), "alias installs original input"); const GuestSignalVector sentinel{0x12345678, -123, 0x345}; const GuestSignalVector invalid{handler, -1, 0x80}; for (const int number : std::array{-1, 0, 129, signal}) { GuestSignalVector output = sentinel; *__error_nid_postfix() = 0; Require(sigvec_nid_disambig1(number, &invalid, &output) == -1, "invalid signal or flags fail"); Require(*__error_nid_postfix() == 22 && Equal(output, sentinel), "failure preserves previous output and exposes errno"); } Require(sigvec_nid_disambig1(signal, nullptr, &queried) == 0 && Equal(queried, newState), "failed update leaves disposition unchanged"); for (const int number : std::array{9, 17}) { GuestSignalVector output = sentinel; Require(sigvec_nid_disambig1(number, &newState, &output) == -1 && Equal(output, sentinel), "uninstallable handlers preserve output"); } Require(sigvec_nid_disambig1(signal, nullptr, nullptr) == 0, "null input and output query is accepted"); const GuestSignalSet saved = Mask(); const GuestSignalSet empty{}; Require(_sigprocmask_nid_postfix(3, &empty, nullptr) == 0, "clear mask for delivery"); received = 0; const GuestSignalSet blocked{{1u << (signal - 1), 0, 0, 0}}; Require(_sigprocmask_nid_postfix(1, &blocked, nullptr) == 0, "block installed handler"); Require(raise_nid_postfix(signal) == 0 && received == 0, "blocked signal becomes pending"); GuestSignalSet previous{}; Require(_sigprocmask_nid_postfix(3, &empty, &previous) == 0 && previous.bits[0] == (1u << (signal - 1)), "unblock returns prior mask"); Require(received == 1 && signalReceived == signal, "unblock delivers pending signal through guest handler"); Require(raise_nid_postfix(signal) == 0 && received == 2, "unblocked delivery retains vector handler"); Require(sigaction_nid_postfix(signal, &initial, nullptr) == 0, "restore disposition"); Require(_sigprocmask_nid_postfix(3, &saved, nullptr) == 0, "restore mask after delivery"); } void GuestBackedOutput() { constexpr std::size_t page = 0x4000; constexpr std::size_t bytes = page * 2; std::int64_t physical = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, 0, 0, &physical) == 0, "allocate guest signal storage"); void* mapping = nullptr; Require(sceKernelMapDirectMemory(&mapping, bytes, 3, 0, physical, 0) == 0 && mapping != nullptr, "map guest signal storage"); Require(GuestArena::GuestArenaContains_nid_postfix(mapping, bytes), "output is not backed by the guest arena"); auto* memory = static_cast(mapping); { GuestArena::HostWrite access(mapping, bytes); Require(access.Open(), "initialize guest signal storage"); std::memset(mapping, 0xA5, bytes); } constexpr int signal = 30; GuestSigaction initial{}; Require(sigaction_nid_postfix(signal, nullptr, &initial) == 0, "save guest-output disposition"); const GuestSignalVector replacement{reinterpret_cast(&Handler), -123, 0}; auto* output = reinterpret_cast(memory + page - 8); Require(sigvec_nid_disambig1(signal, &replacement, output) == 0, "write complete vector across guest pages"); Require(Equal(*output, GuestSignalVector{initial.handler, std::bit_cast(initial.mask.bits[0]), initial.flags ^ 2}), "guest vector conversion"); Require(memory[page - 9] == 0xA5 && memory[page + 8] == 0xA5, "guest vector output escaped its bounds"); *output = replacement; Require(sigvec_nid_disambig1(signal, output, output) == 0 && Equal(*output, replacement), "guest input-output alias conversion"); #ifdef _WIN32 const GuestSignalVector sentinel{0x12345678, -123, 0x345}; const GuestSignalVector changed{replacement.handler, 5, 2}; const auto reject = [&](GuestSignalVector* destination, void* protectedPage) { *destination = sentinel; Require(sceKernelMprotect(protectedPage, page, 1) == 0, "protect guest vector output"); { GuestArena::HostWrite access(destination, sizeof(*destination)); Require(!access.Open(), "read-only guest output unexpectedly opened"); } *__error_nid_postfix() = 73; Require(sigvec_nid_disambig1(signal, &changed, destination) == -1 && *__error_nid_postfix() == 14, "read-only guest vector was not rejected"); Require(Equal(*destination, sentinel), "rejected guest vector was partially overwritten"); GuestSignalVector current{}; Require(sigvec_nid_disambig1(signal, nullptr, ¤t) == 0 && Equal(current, replacement), "rejected output changed the disposition"); Require(sigvec_nid_disambig1(signal, destination, destination) == -1 && *__error_nid_postfix() == 14, "read-only aliased vector was not rejected before mutation"); Require(Equal(*destination, sentinel), "rejected aliased vector was changed"); Require(sigvec_nid_disambig1(signal, nullptr, ¤t) == 0 && Equal(current, replacement), "rejected alias changed the disposition"); Require(sceKernelMprotect(protectedPage, page, 3) == 0, "restore guest vector protection"); }; reject(reinterpret_cast(memory + 128), mapping); reject(output, memory + page); #endif Require(sigaction_nid_postfix(signal, &initial, nullptr) == 0, "restore guest-output disposition"); Require(sceKernelMunmap(reinterpret_cast(mapping), bytes) == 0, "unmap guest signal storage"); Require(sceKernelReleaseDirectMemory(physical, bytes) == 0, "release guest signal storage"); } void MissingBackend() { const auto saved = Mask(); const GuestSignalVector sentinel{0x12345678, -123, 0x345}; GuestSignalVector output = sentinel; const GuestSignalVector replacement{reinterpret_cast(&Handler), 7, 0}; RegisterSigvec_nid_no_patch(nullptr); bool threw = false; try { sigvec_nid_disambig1(30, &replacement, &output); } catch (const std::runtime_error&) { threw = true; } Require(threw, "missing backend throws"); Require(Equal(output, sentinel) && Equal(Mask(), saved), "missing backend leaves output and masks untouched"); RegisterSigvec_nid_no_patch(sigaction_nid_postfix); } } int main() { Vectors(); GuestBackedOutput(); MissingBackend(); }