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.
This commit is contained in:
Henrik Rydgård committed 2026-09-08 15:18:02 -06:00
1 parent a676eecba7
commit 41144e3b35
6 files changed
+35 -6

No files matched your search

+8 -2
View File
@@ -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;
+11
View File
@@ -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)
+4
View File
@@ -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);
+10 -3
View File
@@ -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
+1
View File
@@ -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",