From cf950fac5d285098a1fd741a7b9675d22d6b8d76 Mon Sep 17 00:00:00 2001 From: babyhuey <7751607+babyhuey@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:57:59 -0400 Subject: [PATCH] feat(libkernel): answer kern.proc.pid for the calling process Source Port (PPSA20040) checks whether a debugger is attached with sysctl {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()} into a 1,088-byte buffer and tests P_TRACED (0x800) in ki_flag at offset 0x170; the MIB was unsupported, so the title threw at start-up. Return a FreeBSD 11 amd64 kinfo_proc for the calling process with ki_structsize, ki_pid, ki_ppid and ki_comm filled and every other field 0, so P_TRACED is clear. Like kern.proc.vmmap, any other pid fails with ESRCH, a write with EPERM and a short buffer with ENOMEM. The rest of the record is not measured on a console and is recorded as debt. --- .../System/src/SystemConfiguration.cpp | 34 +++++++++++++++ core/libs/tests/GuestSystemConfiguration.cpp | 42 +++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 3 files changed, 77 insertions(+) diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index 7abef3192e..41aac13bfb 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -135,6 +135,20 @@ struct GuestVmEntry { }; static_assert(offsetof(GuestVmEntry, start) == 8 && offsetof(GuestVmEntry, protection) == 56 && offsetof(GuestVmEntry, path) == 136 && sizeof(GuestVmEntry) == 144); +struct GuestKinfoProc { + std::int32_t structSize; + std::int32_t layout; + unsigned char reserved0[64]; + std::int32_t pid; + std::int32_t parentPid; + unsigned char reserved1[288]; + std::int64_t flag; + unsigned char reserved2[71]; + char command[20]; + unsigned char reserved3[621]; +}; +static_assert(offsetof(GuestKinfoProc, pid) == 72 && offsetof(GuestKinfoProc, parentPid) == 76 && offsetof(GuestKinfoProc, flag) == 368 && offsetof(GuestKinfoProc, command) == 447 && sizeof(GuestKinfoProc) == 1088); + std::vector GuestVmEntries() { std::vector entries; std::uintptr_t start = 0; @@ -322,6 +336,26 @@ int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void static constexpr char path[] = "/app0/eboot.bin"; return CopyOut(path, sizeof(path), oldValue, oldLength); } + if (nameLength >= 3 && name[0] == 1 && name[1] == 14 && name[2] == 1) { + if (newValue != nullptr) { + *__error_nid_postfix() = 1; + return -1; + } + if (nameLength != 4) { + *__error_nid_postfix() = 22; + return -1; + } + if (name[3] != getpid_nid_postfix()) { + *__error_nid_postfix() = 3; + return -1; + } + GuestKinfoProc process{}; + process.structSize = sizeof(process); + process.pid = name[3]; + process.parentPid = getppid_nid_postfix(); + std::memcpy(process.command, "eboot.bin", sizeof("eboot.bin")); + return CopyOut(&process, sizeof(process), oldValue, oldLength); + } if (nameLength >= 3 && name[0] == 1 && name[1] == 14 && name[2] == 35) { if (newValue != nullptr) { *__error_nid_postfix() = 1; diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index ec022beb6f..f6869e0edf 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -304,6 +304,47 @@ static void CheckPathnameSysctl() { length = sizeof(buffer); Require(sysctl_nid_postfix(mibSelf, 4, buffer, &length, buffer, sizeof(buffer)) == -1 && *__error_nid_postfix() == 1); } +static void CheckProcessSysctl() { + const int callerPid = getpid_nid_postfix(); + const int mibPid[] = {1, 14, 1, callerPid}; + std::size_t length = 0; + Require(sysctl_nid_postfix(mibPid, 4, nullptr, &length, nullptr, 0) == 0 && length == 1088); + + unsigned char buffer[1100]; + std::memset(buffer, 0xaa, sizeof(buffer)); + length = sizeof(buffer); + Require(sysctl_nid_postfix(mibPid, 4, buffer, &length, nullptr, 0) == 0 && length == 1088); + std::int32_t structSize = 0; + std::int32_t pid = 0; + std::int32_t parentPid = 0; + std::int64_t flag = -1; + std::memcpy(&structSize, buffer, sizeof(structSize)); + std::memcpy(&pid, buffer + 72, sizeof(pid)); + std::memcpy(&parentPid, buffer + 76, sizeof(parentPid)); + std::memcpy(&flag, buffer + 368, sizeof(flag)); + Require(structSize == 1088 && pid == callerPid && parentPid == getppid_nid_postfix()); + Require((flag & 0x800) == 0); + Require(std::strcmp(reinterpret_cast(buffer + 447), "eboot.bin") == 0); + Require(buffer[4] == 0 && buffer[80] == 0 && buffer[1087] == 0 && buffer[1088] == 0xaa); + + const int mibForeign[] = {1, 14, 1, callerPid + 1000}; + Require(sysctl_nid_postfix(mibForeign, 4, buffer, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 3); + + const int mibSelfAlias[] = {1, 14, 1, -1}; + Require(sysctl_nid_postfix(mibSelfAlias, 4, buffer, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 3); + + const int mibTooShort[] = {1, 14, 1}; + Require(sysctl_nid_postfix(mibTooShort, 3, buffer, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 22); + + const int mibTooLong[] = {1, 14, 1, callerPid, 0}; + Require(sysctl_nid_postfix(mibTooLong, 5, buffer, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 22); + + length = sizeof(buffer); + Require(sysctl_nid_postfix(mibPid, 4, buffer, &length, buffer, sizeof(buffer)) == -1 && *__error_nid_postfix() == 1); + + length = 100; + Require(sysctl_nid_postfix(mibPid, 4, buffer, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 12 && length == 100); +} struct SystemString { int mib[2]; const char* name; @@ -899,6 +940,7 @@ int main() { CheckUnansweredSysctl(); CheckUnknownSysctlName(); CheckPathnameSysctl(); + CheckProcessSysctl(); CheckSystemStringSysctl(); CheckSystemStringNames(); CheckOsReleaseDateSysctl(); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index cc17825081..f3d4c45f9e 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -197,6 +197,7 @@ - [execve](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws like `execvp`: replacing the process image is not supported. The console behavior for invalid arguments and executable path lookups is not modeled. - [sysarch](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `AMD64_GET_FSBASE`, `AMD64_GET_GSBASE` and `AMD64_SET_GSBASE` follow FreeBSD 11's amd64 [`sysarch`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c) on Linux x86-64 hosts, through the host's `arch_prctl`. The GS base belongs to the calling thread, a base at or above `0x800000000000` (`VM_MAXUSER_ADDRESS`) returns `EINVAL`, and a null argument returns `EFAULT`. `AMD64_SET_FSBASE` throws, since guest and host code share the thread's FS base for their TLS. In a payload process on a PS5 (firmware 12.70), `I386_GET_FSBASE` (7) and `I386_GET_GSBASE` (9) wrote 4 bytes, the low 32 bits of the FS and GS bases, and failed with `EFAULT` for a null argument; here they do the same on Linux x86-64 hosts. There a GS base set by one thread left the main thread's unchanged, and threads made with `pthread_create` and `scePthreadCreate` started with their creator's GS base; here a guest thread's host thread is created by the creating thread, and Linux's [`copy_thread`](https://github.com/torvalds/linux/blob/v6.6/arch/x86/kernel/process.c#L182-L186) gives a new thread its creator's GS base. The operation numbers FreeBSD 11's amd64 `sysarch` has no case for returned `EINVAL` there for the nine tried, each with a null argument (2, 5, 6, 12, 127, 133, 134, 1000 and -1; 6 is `I386_VM86`, which its header names), as its [default case](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c#L324-L325) does; here every such number returns `EINVAL` on every host; the numbers other than those nine follow that default case and were not measured. `AMD64_GET_XFPUSTATE` (132) returned 0 there for lengths 0, 64, 128, 256 and 512 (512 was also the largest length a search accepted) and failed with `EINVAL` for 1024, 2048, 4096 and 8192 and with `EFAULT` for a null argument or address, and `I386_GET_XFPUSTATE` (11) failed with `EFAULT` for a null argument; both throw here. The other operations FreeBSD 11's amd64 `sysarch` handles, and every operation it handles on other hosts (Windows keeps its TEB in GS), throw. - [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified. +- [kern.proc.pid](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - what the console fills in a process's `kinfo_proc` is unverified; `sysctl` {`CTL_KERN`, `KERN_PROC`, `KERN_PROC_PID`, pid} for the calling process returns a 1,088-byte FreeBSD 11 amd64 `kinfo_proc` ([`sys/sys/user.h`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/sys/user.h)) holding only `ki_structsize`, `ki_pid`, `ki_ppid` and `ki_comm` (`eboot.bin`, the base name of the `kern.proc.pathname` value), with every other field 0, including `ki_flag`, so `P_TRACED` (0x800) is clear. Source Port (PPSA20040) asks for its own record with a 0x440-byte buffer and tests only bit 0x800 of `ki_flag` at offset 0x170, to see whether a debugger is attached. Any other pid, including -1, fails with `ESRCH`, a write with `EPERM` and a short buffer with `ENOMEM`, as for `kern.proc.vmmap`. - [kern.proc.env](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - which variables, if any, the console starts an application with is unverified; `sysctl` {`CTL_KERN`, `KERN_PROC`, `KERN_PROC_ENV`, pid} returns 0 bytes, the empty start-up environment `environ` and libc's `getenv` also begin with, as FreeBSD 11's [`sysctl_kern_proc_env`](https://github.com/freebsd/freebsd-src/blob/releng/11.0/sys/kern/kern_proc.c) copies a process's start-up strings, not later `setenv` changes. Only the calling process's pid is accepted (`ESRCH` otherwise, -1 included); a write returns `EPERM`. PS5VR (PPSA99177) reads it for its own pid at start-up. - [kern.proc.vmmap](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_KERN`, `KERN_PROC`, `KERN_PROC_VMMAP`, pid} returns packed FreeBSD 11 `kinfo_vmentry` records (144 bytes each, an empty path) for the used ranges of the guest address-space arena, in address order, as [`kinfo_getvmmap`](https://github.com/freebsd/freebsd-src/blob/releng/11.0/lib/libutil/kinfo_getvmmap.c) reads them. A range is one record: the type is always `KVME_TYPE_DEFAULT`, the protection is `sceKernelVirtualQuery`'s for the range's first page (0 where it reports none), and offset, object, resident page counts, flags and path are 0. Only the calling process's pid is accepted (`ESRCH` otherwise, -1 included, as FreeBSD's `pget` gives); a write returns `EPERM` and a short buffer `ENOMEM`. Ranges outside the arena, which guest code cannot map, are not reported. The walk takes the arena lock once per range, not for the whole walk as FreeBSD holds the map lock, so an `mmap` or `munmap` on another thread can change the list between two records. Whether a PS5 application may read its own map, and what types and paths the console reports, is unverified - [sysctl names measured absent on a PS5 payload](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - On firmware 12.70, a payload gets `ENOENT` for `hw.byteorder`, `hw.cacheline_size`, `hw.machine_arch`, `hw.usermem`, `kern.clockrate`, `kern.maxfiles`, `kern.maxfilesperproc`, `kern.maxproc`, `kern.ps_strings`, `kern.sched.name`, `kern.smp.maxcpus`, `kern.stackprot`, `kern.timecounter.hardware` and `machdep.cacheline_size`. `sysctl` returns `ENOENT` for MIBs {1, 6}, {1, 7}, {1, 12}, {1, 27}, {1, 32}, {6, 4} and {6, 11}; reads and size queries leave output buffers and lengths untouched. `sysctlnametomib` resolves the seven corresponding MIB names and returns `ENOENT` for `hw.cacheline_size`, `hw.usermem` and `machdep.cacheline_size`. Writes and title behavior are unmeasured.