Merge pull request #4283 from MarcZX100/codex/carry-guest-thread-stacks-3283

fix(libkernel): place Linux guest thread stacks inside application area
This commit is contained in:
Adrià 2026-10-13 16:28:18 +02:00 committed by GitHub
commit b0d41be1ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 170 additions and 3 deletions

View File

@ -984,6 +984,8 @@ int DoMprotect(const void* addr, size_t len, int prot) {
const auto bytes = static_cast<std::size_t>(end - first);
const auto* pointer = reinterpret_cast<const void*>(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) {

View File

@ -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);

View File

@ -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 <algorithm>
#include <cstdio>
#include <cstdlib>
@ -13,6 +14,7 @@
#include <limits>
#include <map>
#include <utility>
#include <vector>
#include <string>
#include <future>
#include <memory>
@ -73,6 +75,101 @@ static GuestThreadState& CurrentGuestState() {
static std::mutex stackLock;
static std::map<PthreadPrivate*, std::pair<std::uintptr_t, std::uintptr_t>> liveStacks;
static std::map<std::uintptr_t, std::size_t> 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<std::uintptr_t, GuestStackMapping> guestStackMappings;
static void RegisterGuestStackMapping(PthreadPrivate* thread) {
const auto mappingStart = reinterpret_cast<std::uintptr_t>(thread->guestStack);
const auto stackStart = reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(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<RetiredStack> 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<RetiredStack> 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<int, PthreadPrivate*> threads;
@ -518,6 +633,10 @@ static void* StartNativeThread(void* opaque) {
throw std::runtime_error("Cannot query guest thread stack");
self->stackAddress = reinterpret_cast<void*>(high - reserve - self->stackSize);
self->threadId = std::this_thread::get_id();
#if defined(__linux__)
self->hostTid = static_cast<int>(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>(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<char*>(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;

View File

@ -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<int>(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<std::uintptr_t>(context.local) && reinterpret_cast<std::uintptr_t>(context.local) < context.region.end);
#if defined(__linux__)
Require(reinterpret_cast<std::uintptr_t>(context.local) < APPLICATION_AREA_END);
#endif
Require(sceKernelIsStack(&local, &start, &end) == SCE_OK);
Require(context.end <= start || context.start >= end);
void* workerStart = nullptr;

View File

@ -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<std::uintptr_t>(start) + Page - 1) & ~(Page - 1);
Require(low + GuardBytes + Page < reinterpret_cast<std::uintptr_t>(&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<void*>(hostGuard), Page, 3) == -1);
Require(QueryPage(hostGuard).raw == hostGuardBefore);
#endif
auto* guard = reinterpret_cast<volatile unsigned char*>(low);
Require(mprotect_nid_postfix(const_cast<unsigned char*>(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);