fix(libkernel): protect host-only guest stack pages
This commit is contained in:
parent
7637cac13e
commit
9b6e7f66c4
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 (...) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue