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 a139d7e95..b00a839b4 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -129,6 +129,9 @@ struct PthreadPrivate { #ifndef _WIN32 clockid_t hostCpuClock{}; bool hostCpuClockBound = false; + void* guestStack = nullptr; + std::size_t guestStackBytes = 0; + int hostTid = 0; #endif std::mutex nameLock; std::string name; @@ -149,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 a2ea58f0a..b51c5df2f 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -5,6 +5,7 @@ #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/CpuTopology.hpp" +#include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +75,101 @@ 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; + +struct RetiredStack { + void* address; + std::size_t bytes; + int hostTid; +}; + +static std::mutex retiredStacksLock; +static std::vector retiredStacks; + +static void AllocateGuestStack(PthreadPrivate* thread, std::size_t usableBytes) { + const std::size_t bytes = (usableBytes + PS5_PAGE_SIZE - 1) / PS5_PAGE_SIZE * PS5_PAGE_SIZE + GUEST_STACK_GUARD; + void* stack = nullptr; + if (DoMapAnon(&stack, bytes, 3, 0) != 0) + throw std::runtime_error("scePthreadCreate: cannot map the guest thread stack"); + if (DoMprotect(stack, GUEST_STACK_GUARD, 0) != 0) { + DoMunmap(stack, bytes); + throw std::runtime_error("scePthreadCreate: cannot protect the guest thread stack guard"); + } + thread->guestStack = stack; + thread->guestStackBytes = bytes; +} + +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; +} + +static bool HostThreadGone(int hostTid) { + if (hostTid == 0) return false; + char path[48]; + std::snprintf(path, sizeof(path), "/proc/self/task/%d", hostTid); + return access(path, F_OK) != 0; +} + +static void ReapRetiredStacks() { + std::vector ready; + { + std::lock_guard lock(retiredStacksLock); + for (auto it = retiredStacks.begin(); it != retiredStacks.end();) { + if (HostThreadGone(it->hostTid)) { + ready.push_back(*it); + it = retiredStacks.erase(it); + } else { + ++it; + } + } + } + for (const auto& stack : ready) { + DoMunmap(stack.address, stack.bytes); + UnregisterGuestStackMapping(stack.address); + } +} + +static void RetireGuestStack(PthreadPrivate* thread) { + if (thread->guestStack == nullptr) return; + { + std::lock_guard lock(retiredStacksLock); + retiredStacks.push_back({thread->guestStack, thread->guestStackBytes, thread->hostTid}); + } + thread->guestStack = nullptr; + thread->guestStackBytes = 0; +} +#else +static void FreeGuestStack(PthreadPrivate*) {} +static void RetireGuestStack(PthreadPrivate*) {} +#endif static bool HostStackLimits(std::uintptr_t* low, std::uintptr_t* high) { #ifdef _WIN32 @@ -176,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; @@ -518,6 +633,10 @@ static void* StartNativeThread(void* opaque) { throw std::runtime_error("Cannot query guest thread stack"); self->stackAddress = reinterpret_cast(high - reserve - self->stackSize); self->threadId = std::this_thread::get_id(); +#if defined(__linux__) + self->hostTid = static_cast(gettid()); + RegisterGuestStackMapping(self); +#endif args->initialized.set_value(); } catch (...) { args->initialized.set_exception(std::current_exception()); @@ -601,12 +720,22 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread sigset_t creatorMask; pthread_sigmask(SIG_SETMASK, nullptr, &creatorMask); args->signalMask = creatorMask; +#if defined(__linux__) + ReapRetiredStacks(); + AllocateGuestStack(p.get(), nativeStack); +#endif auto native = std::make_unique(NativeThreadArgs{std::move(args), start.get_future(), {}}); auto initialized = native->initialized.get_future(); pthread_attr_t nativeAttr; - if (const int result = pthread_attr_init(&nativeAttr); result != 0) + if (const int result = pthread_attr_init(&nativeAttr); result != 0) { + FreeGuestStack(p.get()); throw std::system_error(result, std::generic_category(), "Creating guest thread"); + } +#if defined(__linux__) + int created = pthread_attr_setstack(&nativeAttr, static_cast(p->guestStack) + GUEST_STACK_GUARD, p->guestStackBytes - GUEST_STACK_GUARD); +#else int created = pthread_attr_setstacksize(&nativeAttr, nativeStack); +#endif if (created == 0) { sigset_t allSignals; sigfillset(&allSignals); @@ -615,18 +744,22 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread pthread_sigmask(SIG_SETMASK, &creatorMask, nullptr); } pthread_attr_destroy(&nativeAttr); - if (created != 0) + if (created != 0) { + FreeGuestStack(p.get()); throw std::system_error(created, std::generic_category(), "Creating guest thread"); + } native.release(); try { initialized.get(); if (detached) { if (const int result = pthread_detach(p->hostThread); result != 0) throw std::system_error(result, std::generic_category(), "Detaching guest thread"); + RetireGuestStack(p.get()); } } catch (...) { start.set_value(false); pthread_join(p->hostThread, nullptr); + FreeGuestStack(p.get()); throw; } auto* published = p.release(); @@ -655,6 +788,7 @@ int APS5_VABI scePthreadJoin(Pthread thread, void** retval) { #else if (const int result = pthread_join(thread->hostThread, nullptr); result != 0) throw std::system_error(result, std::generic_category(), "Joining guest thread"); + FreeGuestStack(thread); if (retval) *retval = thread->_retval; ReleaseThread(thread); #endif @@ -670,6 +804,7 @@ int APS5_VABI scePthreadDetach(Pthread thread) { #else if (const int result = pthread_detach(thread->hostThread); result != 0) throw std::system_error(result, std::generic_category(), "Detaching guest thread"); + RetireGuestStack(thread); ReleaseThread(thread); #endif return SCE_OK; diff --git a/core/libs/tests/GuestKernelIsStack.cpp b/core/libs/tests/GuestKernelIsStack.cpp index b2c1097d2..941ffdc97 100644 --- a/core/libs/tests/GuestKernelIsStack.cpp +++ b/core/libs/tests/GuestKernelIsStack.cpp @@ -9,9 +9,11 @@ int APS5_VABI scePthreadJoin(Pthread thread, void** retval); int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end); int APS5_VABI sceKernelMapFlexibleMemory(void** addr, std::size_t len, int prot, int flags); int APS5_VABI sceKernelMunmap(std::uint64_t addr, std::size_t len); +int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, std::uint64_t infoSize); } static constexpr int SCE_OK = 0; +static constexpr std::uintptr_t APPLICATION_AREA_END = 0x000000FC00000000ull; static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast(0x8002000d); static int global = 0; @@ -24,6 +26,8 @@ struct WorkerContext { void* start = nullptr; void* end = nullptr; int result = -1; + VirtualQueryInfo region{}; + int regionResult = -1; }; static void* APS5_VABI Worker(void* arg) { @@ -31,6 +35,7 @@ static void* APS5_VABI Worker(void* arg) { int local = 0; context.local = &local; context.result = sceKernelIsStack(&local, &context.start, &context.end); + context.regionResult = sceKernelVirtualQuery(&local, 0, &context.region, sizeof(context.region)); context.queried.store(true); while (!context.release.load()) {} return nullptr; @@ -55,6 +60,11 @@ int main() { while (!context.queried.load()) {} Require(context.result == SCE_OK); Require(context.start <= context.local && context.local < context.end); + Require(context.regionResult == SCE_OK); + Require(context.region.start <= reinterpret_cast(context.local) && reinterpret_cast(context.local) < context.region.end); +#if defined(__linux__) + Require(reinterpret_cast(context.local) < APPLICATION_AREA_END); +#endif Require(sceKernelIsStack(&local, &start, &end) == SCE_OK); Require(context.end <= start || context.start >= end); void* workerStart = nullptr; diff --git a/core/libs/tests/GuestKernelStackProtect.cpp b/core/libs/tests/GuestKernelStackProtect.cpp index 66aed4047..f946e321a 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,18 @@ 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)); +#ifdef __linux__ + 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); +#endif auto* guard = reinterpret_cast(low); Require(mprotect_nid_postfix(const_cast(guard), GuardBytes, 0) == 0); #ifdef _WIN32 @@ -152,7 +164,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);