From 9b6e7f66c4581e44291badeab49a9dc33fc0b1ae Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Mon, 12 Oct 2026 04:08:31 +0000 Subject: [PATCH] fix(libkernel): protect host-only guest stack pages --- .../libkernel/DirectMemory/DirectMemory.cpp | 2 + .../prx/libkernel/Pthread/include/Pthread.hpp | 1 + .../libs/prx/libkernel/Pthread/src/Thread.cpp | 47 ++++++++++++++++++- core/libs/tests/GuestKernelStackProtect.cpp | 16 ++++++- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 0f04386b3..a35b55b55 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -984,6 +984,8 @@ int DoMprotect(const void* addr, size_t len, int prot) { const auto bytes = static_cast(end - first); const auto* pointer = reinterpret_cast(first); const auto nativeProtection = LinuxProtFromSce(prot); + if (GuestThreadStackHostRangeOverlaps(address, address + len)) + throw std::system_error(EACCES, std::generic_category(), "mprotect overlaps host-only guest thread stack pages"); std::uintptr_t stackStart = 0; std::uintptr_t stackEnd = 0; if ((prot & GuestProtGpuReadWrite) == 0 && GuestThreadStack(address, &stackStart, &stackEnd) && address >= stackStart && address + len <= stackEnd) { diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index a95394497..b00a839b4 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -152,6 +152,7 @@ struct PthreadPrivate { }; bool GuestThreadStack(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end); +bool GuestThreadStackHostRangeOverlaps(std::uintptr_t start, std::uintptr_t end); void NoteGuestStackProtection(std::uintptr_t start, std::size_t bytes, bool readWrite); int GuestThreadCpuClockId(const PthreadPrivate* thread); bool GuestThreadCpuNanos(int cpuClockThread, std::uint64_t* nanos); diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 8d0f86e99..b51c5df2f 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -75,6 +75,27 @@ static GuestThreadState& CurrentGuestState() { static std::mutex stackLock; static std::map> liveStacks; static std::map protectedStackPages; +#if defined(__linux__) +struct GuestStackMapping { + std::uintptr_t mappingEnd; + std::uintptr_t stackStart; + std::uintptr_t stackEnd; +}; +static std::mutex guestStackMappingsLock; +static std::map guestStackMappings; + +static void RegisterGuestStackMapping(PthreadPrivate* thread) { + const auto mappingStart = reinterpret_cast(thread->guestStack); + const auto stackStart = reinterpret_cast(thread->stackAddress); + std::lock_guard lock(guestStackMappingsLock); + guestStackMappings[mappingStart] = {mappingStart + thread->guestStackBytes, stackStart, stackStart + thread->stackSize}; +} + +static void UnregisterGuestStackMapping(void* mapping) { + std::lock_guard lock(guestStackMappingsLock); + guestStackMappings.erase(reinterpret_cast(mapping)); +} +#endif #if defined(__linux__) static constexpr std::size_t GUEST_STACK_GUARD = PS5_PAGE_SIZE; @@ -103,7 +124,9 @@ static void AllocateGuestStack(PthreadPrivate* thread, std::size_t usableBytes) static void FreeGuestStack(PthreadPrivate* thread) { if (thread->guestStack == nullptr) return; + void* const mapping = thread->guestStack; DoMunmap(thread->guestStack, thread->guestStackBytes); + UnregisterGuestStackMapping(mapping); thread->guestStack = nullptr; thread->guestStackBytes = 0; } @@ -128,7 +151,10 @@ static void ReapRetiredStacks() { } } } - for (const auto& stack : ready) DoMunmap(stack.address, stack.bytes); + for (const auto& stack : ready) { + DoMunmap(stack.address, stack.bytes); + UnregisterGuestStackMapping(stack.address); + } } static void RetireGuestStack(PthreadPrivate* thread) { @@ -247,6 +273,24 @@ bool GuestThreadStack(std::uintptr_t address, std::uintptr_t* start, std::uintpt return false; } +bool GuestThreadStackHostRangeOverlaps(std::uintptr_t start, std::uintptr_t end) { +#if defined(__linux__) + std::lock_guard lock(guestStackMappingsLock); + auto it = guestStackMappings.upper_bound(start); + if (it != guestStackMappings.begin()) --it; + for (; it != guestStackMappings.end() && it->first < end; ++it) { + const auto mappingStart = it->first; + const auto& stack = it->second; + if (start < stack.stackStart && end > mappingStart) return true; + if (start < stack.mappingEnd && end > stack.stackEnd) return true; + } +#else + (void)start; + (void)end; +#endif + return false; +} + struct CpuClockRegistry { std::mutex lock; std::map threads; @@ -591,6 +635,7 @@ static void* StartNativeThread(void* opaque) { self->threadId = std::this_thread::get_id(); #if defined(__linux__) self->hostTid = static_cast(gettid()); + RegisterGuestStackMapping(self); #endif args->initialized.set_value(); } catch (...) { diff --git a/core/libs/tests/GuestKernelStackProtect.cpp b/core/libs/tests/GuestKernelStackProtect.cpp index 66aed4047..474d0a7d0 100644 --- a/core/libs/tests/GuestKernelStackProtect.cpp +++ b/core/libs/tests/GuestKernelStackProtect.cpp @@ -24,7 +24,7 @@ static constexpr std::uintptr_t Page = 0x4000; static constexpr std::size_t GuardBytes = 0x10000; static constexpr std::size_t HostBytes = 0x40000; -static void Require(bool value) { if (!value) std::abort(); } +#define Require(value) do { if (!(value)) { std::fprintf(stderr, "GuestKernelStackProtect failed at line %d\n", __LINE__); std::abort(); } } while (false) struct PageState { bool mapped; @@ -81,6 +81,16 @@ static void* APS5_VABI Worker(void* arg) { Require(sceKernelIsStack(&local, &start, &end) == SCE_OK); const auto low = (reinterpret_cast(start) + Page - 1) & ~(Page - 1); Require(low + GuardBytes + Page < reinterpret_cast(&local)); + std::uintptr_t hostGuard = 0; + for (auto cursor = low; cursor >= Page && low - cursor <= 4 * 1024 * 1024; cursor -= Page) { + const auto page = QueryPage(cursor); + if (!page.mapped) break; + if (!page.writable) { hostGuard = cursor; break; } + } + Require(hostGuard != 0); + const auto hostGuardBefore = QueryPage(hostGuard).raw; + Require(mprotect_nid_postfix(reinterpret_cast(hostGuard), Page, 3) == -1); + Require(QueryPage(hostGuard).raw == hostGuardBefore); auto* guard = reinterpret_cast(low); Require(mprotect_nid_postfix(const_cast(guard), GuardBytes, 0) == 0); #ifdef _WIN32 @@ -152,7 +162,11 @@ int main() { Require(scePthreadJoin(thread, nullptr) == SCE_OK); for (std::uintptr_t offset = 0; offset < GuardBytes; offset += Page) { const auto left = QueryPage(leftGuard.load() + offset); +#ifdef __linux__ + Require(!left.mapped || left.writable || left.raw == 1); +#else Require(!left.mapped || left.writable); +#endif } Require(scePthreadCreate(&thread, nullptr, WriteLeftGuard, nullptr, nullptr) == SCE_OK); Require(scePthreadJoin(thread, nullptr) == SCE_OK);