fix(libkernel): protect host-only guest stack pages

This commit is contained in:
MarcZX100 2026-10-12 04:08:31 +00:00 committed by Adrià Franch
parent 7637cac13e
commit 9b6e7f66c4
4 changed files with 64 additions and 2 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

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

View File

@ -75,6 +75,27 @@ 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;
@ -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<int, PthreadPrivate*> threads;
@ -591,6 +635,7 @@ static void* StartNativeThread(void* opaque) {
self->threadId = std::this_thread::get_id();
#if defined(__linux__)
self->hostTid = static_cast<int>(gettid());
RegisterGuestStackMapping(self);
#endif
args->initialized.set_value();
} catch (...) {

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,16 @@ 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));
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);
auto* guard = reinterpret_cast<volatile unsigned char*>(low);
Require(mprotect_nid_postfix(const_cast<unsigned char*>(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);