From 41144e3b35c62632be46433a0d2f4a5c6b683c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 8 Sep 2026 15:12:53 -0600 Subject: [PATCH] Give the memory partitions the caller's privilege, not the syscall's PPSSPP decided whether a caller was privileged with hleIsKernelMode(), which reports whether the syscall being executed is itself a kernel-only export. That's a different question from the one the hardware answers: on a PSP the privilege belongs to the calling module, and a kernel module reaches sceKernelCreateTlspl through the ordinary ThreadManForUser NID like anything else. So a kernel module asking for partition 1, 3 or 4 got ILLEGAL_PERM where a real PSP hands it over, which the new threads/tls/kernel/partition test shows directly. BlockAllocatorFromID now also accepts a caller whose thread belongs to a kernel module, via a new __KernelCurThreadIsKernelMode(). It checks the thread's own attribute first and then the owning module, because a kernel module's main thread isn't necessarily flagged kernel - the attribute comes from PSP_MAIN_THREAD_ATTR, which needn't set it. That mirrors how sceKernelCreateThread already works out allowKernel. This only ever widens access, and only for threads belonging to kernel modules, so games are unaffected - they run in user modules and see exactly what they saw before. --- Core/HLE/sceKernelMemory.cpp | 10 ++++++++-- Core/HLE/sceKernelThread.cpp | 11 +++++++++++ Core/HLE/sceKernelThread.h | 4 ++++ docs/pspautotests-hardware.md | 13 ++++++++++--- pspautotests | 2 +- test.py | 1 + 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index b9c875fd71..ac0ec377ff 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -390,11 +390,17 @@ void __KernelMemoryShutdown() } BlockAllocator *BlockAllocatorFromID(int id) { + // A kernel module gets the privileged partitions whichever entry point it came in through - + // threads/tls/kernel/partition records sceKernelCreateTlspl accepting 1, 3 and 4 from a + // kernel module and refusing them from user mode, and it reaches the kernel through the + // ordinary ThreadManForUser NID either way. hleIsKernelMode() alone only catches the case + // where the export itself is kernel-only. + const bool kernelMode = hleIsKernelMode() || __KernelCurThreadIsKernelMode(); switch (id) { case KERNEL_PARTITION_ID: case 3: case 4: - if (hleIsKernelMode()) + if (kernelMode) return &kernelMemory; return nullptr; @@ -404,7 +410,7 @@ BlockAllocator *BlockAllocatorFromID(int id) { case 8: case 10: - if (hleIsKernelMode()) + if (kernelMode) return &userMemory; return nullptr; diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index bd139eede5..260c349f61 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -2290,6 +2290,17 @@ SceUID __KernelGetCurThreadModuleId() { return 0; } +bool __KernelCurThreadIsKernelMode() { + PSPThread *thread = __GetCurrentThread(); + if (thread && (thread->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0) { + return true; + } + // A kernel module's own main thread isn't necessarily flagged kernel - the attribute comes + // from PSP_MAIN_THREAD_ATTR in the module info, which needn't set it - so fall back to what + // module the thread belongs to, the same way sceKernelCreateThread decides allowKernel. + return KernelModuleIsKernelMode(__KernelGetCurThreadModuleId()); +} + u32 __KernelGetCurThreadStack() { PSPThread *t = __GetCurrentThread(); if (t) diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 146c3bb5f5..2edcff09fe 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -341,6 +341,10 @@ KernelObject *__KernelCallbackObject(); SceUID __KernelGetCurThread(); int KernelCurThreadPriority(); bool KernelChangeThreadPriority(SceUID threadID, int priority); +// Whether the running thread belongs to a kernel module. Privilege on the PSP is a property of +// the caller, not of the syscall - hleIsKernelMode() only says the entry point itself is a +// kernel-only export, which is a different question. +bool __KernelCurThreadIsKernelMode(); u32 __KernelGetCurThreadStack(); u32 __KernelGetCurThreadStackStart(); const char *__KernelGetThreadName(SceUID threadID); diff --git a/docs/pspautotests-hardware.md b/docs/pspautotests-hardware.md index 453c9e306d..67329f07a4 100644 --- a/docs/pspautotests-hardware.md +++ b/docs/pspautotests-hardware.md @@ -217,9 +217,16 @@ to `host0:` with `sceIo` directly rather than through newlib's `FILE`, since tha libcglue in. Formatting still goes through newlib's `vsnprintf`, so the usual format specifiers are all available. -**PPSSPP can't run these yet.** `PPSSPPHeadless` times out on a kernel PRX built this way, so a -kernel-mode test can't go in `test.py` - keep it as a hardware reference and diff it by hand -against its user-mode twin. +Output reaches an emulator the same way it reaches the cable: a kernel build feeds +`sceIoDevctl("emulator:", SEND_OUTPUT)` as well as writing the file, and calls +`sceKernelExitGame` at the end so headless stops rather than spinning to its timeout. Both are +easy to forget when writing a new harness - without the first the test appears to produce nothing, +and without the second it always reports TIMEOUT even though it ran. + +One emulator-side note, since it took a while to pin down: privilege on the PSP belongs to the +*caller*, not to the syscall. PPSSPP's `hleIsKernelMode()` only reports whether the entry point +itself is a kernel-only export, so a kernel module calling an ordinary `ForUser` NID used to look +like user mode. `__KernelCurThreadIsKernelMode()` answers the question this test needs. ## Worked example: FAT short names diff --git a/pspautotests b/pspautotests index 926360d556..7c5d150d40 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 926360d5567fbf94c1605cd5ff7e91ef86f9f4cf +Subproject commit 7c5d150d40ab585d8bc1b5f60f5673eb56620ac9 diff --git a/test.py b/test.py index 9177f934b3..5caaed33a2 100755 --- a/test.py +++ b/test.py @@ -341,6 +341,7 @@ tests_good = [ "threads/threads/threads", "threads/tls/create", "threads/tls/partition", + "threads/tls/kernel/partition", "threads/tls/delete", "threads/tls/get", "threads/tls/free",