From 46f3d779e5adeab61a9361d4f529ef9663b05a86 Mon Sep 17 00:00:00 2001 From: p3nicillin Date: Tue, 13 Oct 2026 11:54:41 +0100 Subject: [PATCH] feat(libkernel): report machdep.tsc_freq through sysctlbyname PS5SX2 (PPSA99203) reads machdep.tsc_freq at start-up and stopped on the unsupported name. It reports the host TSC frequency that sceKernelGetTscFrequency measures, as guest rdtsc reads the host counter. Co-Authored-By: Claude Opus 5.5 --- .../System/src/SystemConfiguration.cpp | 7 ++++++ core/libs/tests/GuestSystemConfiguration.cpp | 24 +++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 3 files changed, 32 insertions(+) diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index 672c9d291126..7562549f1de7 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -174,6 +174,8 @@ int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void throw std::runtime_error(std::string(__func__) + ": unsupported MIB " + mib); } +uint64_t APS5_VABI sceKernelGetTscFrequency(void); + int APS5_VABI sysctlbyname_nid_postfix(const char* name, void* oldValue, std::size_t* oldLength, const void* newValue, std::size_t newLength) { if (name == nullptr) { *__error_nid_postfix() = 14; @@ -202,6 +204,11 @@ int APS5_VABI sysctlbyname_nid_postfix(const char* name, void* oldValue, std::si for (std::size_t i = 0; i < *oldLength; ++i) bytes[i] = static_cast(device()); return 0; } + if (std::strcmp(name, "machdep.tsc_freq") == 0) { + if (newValue != nullptr) throw std::runtime_error(std::string(__func__) + ": setting machdep.tsc_freq"); + const std::uint64_t frequency = sceKernelGetTscFrequency(); + return CopyOut(&frequency, sizeof(frequency), oldValue, oldLength); + } if (std::strcmp(name, "kern.sdk_version") == 0) { if (newValue != nullptr) { *__error_nid_postfix() = 1; diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index 1ad0f5c6f7e1..c5c9e503c465 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -22,6 +22,7 @@ int APS5_VABI sysctl_nid_postfix(const int*, std::uint32_t, void*, std::size_t*, int APS5_VABI sysctlbyname_nid_postfix(const char*, void*, std::size_t*, const void*, std::size_t); int APS5_VABI sceKernelGetSystemSwVersion_nid_postfix(SceKernelSystemSwVersion*); int APS5_VABI getpid_nid_postfix(); +std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); extern char** environ_nid_postfix; } static void Require(bool value) { if (!value) std::abort(); } @@ -199,6 +200,28 @@ static void CheckPseudoRandomSysctl() { } Require(threw); } +static void CheckTscFrequencySysctl() { + *__error_nid_postfix() = 13; + std::size_t length = 0; + Require(sysctlbyname_nid_postfix("machdep.tsc_freq", nullptr, &length, nullptr, 0) == 0 && length == sizeof(std::uint64_t)); + std::uint64_t frequency = 0; + length = sizeof(frequency); + Require(sysctlbyname_nid_postfix("machdep.tsc_freq", &frequency, &length, nullptr, 0) == 0 && length == sizeof(std::uint64_t)); + Require(frequency != 0 && frequency == sceKernelGetTscFrequency()); + Require(*__error_nid_postfix() == 13); + unsigned char bytes[8] = {0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}; + length = 4; + Require(sysctlbyname_nid_postfix("machdep.tsc_freq", bytes, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 12); + Require(length == 4 && bytes[4] == 0xaa && bytes[7] == 0xaa); + bool threw = false; + length = sizeof(frequency); + try { + sysctlbyname_nid_postfix("machdep.tsc_freq", &frequency, &length, &frequency, sizeof(frequency)); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw); +} static void CheckSdkVersionSysctl() { *__error_nid_postfix() = 13; std::size_t length = 0; @@ -260,6 +283,7 @@ int main() { CheckSystemSwVersion(); CheckPseudoRandomSysctl(); CheckSdkVersionSysctl(); + CheckTscFrequencySysctl(); CheckArandomSysctl(); *__error_nid_postfix() = 13; Require(sysconf_nid_postfix(47) == 0x4000); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 6e33ad17fc56..509c451e227c 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -16,6 +16,7 @@ - [sceKernelGetSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports baseline system software version `0x01000000` ("01.000.000") for ProsperoStore (PPSA99000). The 0x28-byte layout (`uint64_t size` at 0, `versionString` at 8 and `version` at 0x24) matches [ProsperoStore's caller](https://github.com/blackbearreloaded/ProsperoStore/blob/20584011/src/diag/trace.cpp#L20-L26), [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d1/src/core/libraries/kernel/kernel.h#L45-L49), [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6/kernel/ps4_libkernel.pas#L502-L506) and [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4/include/orbis/_types/kernel.h#L256-L260). These sources support the structure layout, but not the reported version value: the console firmware version remains unverified. The checks are unverified too: a null pointer returns `SCE_KERNEL_ERROR_EFAULT` and a `size` other than 0x28 returns `SCE_KERNEL_ERROR_EINVAL`, while shadPS4 returns success for null and never reads `size`. - [kern.rng_pseudo](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` fills the whole output buffer with bytes from the host's `std::random_device` and leaves the length unchanged; a write returns -1 with `EPERM` and a call without an output buffer throws. The only caller seen is mbedTLS's `mbedtls_hardware_poll` in the catalog's ProsperoLight boilerplate, which asks for 64-byte blocks and fails if fewer come back. The console's generator, its per-call maximum and its answer to a size query are unknown - [kern.sdk_version](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the system software version as the 4-byte value `0x01000000` (1.00), the value `sceKernelGetSystemSwVersion` reports; a write returns -1 with `EPERM`. The major version is byte 3 and the minor version byte 2, in hex digits that read as decimal (13.60 is `0x1360xxxx`), as [Capy Mobile](https://github.com/khalifa007/CapyMobile/blob/2758c016/src/ps5/platform.c#L64-L71) (PPSA92280), [remote_lua_loader](https://github.com/n0llptr/remote_lua_loader/blob/7c2840e475/savedata/misc.lua#L195-L209) and the [lapse payload](https://github.com/NetflixNHack/Netflix-N-Hack/blob/605eb93ba5/PS4/payloads/lapse.js#L436-L450) read it. Capy Mobile only displays it. The console's firmware version is not modeled and the low two bytes are unknown +- [machdep.tsc_freq](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the host TSC frequency as the 8-byte value `sceKernelGetTscFrequency` measures, since guest `rdtsc` reads the host counter, with FreeBSD's 64-bit [`sysctl_machdep_tsc_freq`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/x86/x86/tsc.c#L717-L736) layout and `ENOMEM` for a short buffer. The console's value is not known. A write, which FreeBSD takes as a new frequency, throws. PS5SX2 (PPSA99203) reads it at start-up and measures the TSC itself when the call fails - [sceKernelGetProsperoSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SoftwareVersion.cpp) (libkernel) reports system software version `0x01000000` ("01.000.000"); the console's firmware version is not modeled. The 0x28-byte block (a 64-bit size at 0, the version string at 8 and the packed version at 0x24) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Kernel/KernelSystem.cs), which Prospero Multi Tools (PPSA99110) and Prospero Explorer (PPSA99109) are built with and which sets the size to 40, and the same fields in [etaHEN](https://github.com/etaHEN/etaHEN/blob/main/Source%20Code/daemon/include/ipc.hpp) and [shsrv](https://github.com/ps5-payload-dev/shsrv/blob/master/sh.c). A size other than 0x28 returns `SCE_KERNEL_ERROR_EINVAL` without writing; whether the console checks the size is unverified, since etaHEN and shsrv declare a 0x30-byte block and do not set it - [sceKernelGetAllowedSdkVersionOnSystem](../../../core/libs/prx/libkernel/System/src/SoftwareVersion.cpp) (libkernel) reports the system software version with the 12 patch bits set, `0x01000fff`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/98fd0689/src/core/libraries/kernel/kernel.cpp) does on the PS4 and as [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Kernel/KernelSystem.cs) describes it, which Prospero Explorer (PPSA99109) reads it through; a null pointer returns `SCE_KERNEL_ERROR_EINVAL`. The console's value and its errors are unverified - [getuid, geteuid, getgid, getegid and issetugid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) report the root identity (0) and a process that never changed credentials (`issetugid` 0), as a jailbroken process has; the host user and group are not exposed to the guest. The values a title sees on a console are unverified