From c2652b88ca5cd7bf64e1d9a9a76d0e3b1eb814a1 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Sat, 24 Oct 2026 10:32:57 +0300 Subject: [PATCH 1/7] feat(libkernel_sys): report a retail console from sceKernelIsCEX and sceKernelIsDevKit --- core/libs/prx/libkernel_sys/Export.Discovery.cpp | 10 ---------- core/libs/prx/libkernel_sys/Export.cpp | 8 ++++++++ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 3addcda651..bf1df5d5f0 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -1417,21 +1417,11 @@ int APS5_VABI sceKernelIsAuthenticNeo(void) { return 0; } -int APS5_VABI sceKernelIsCEX(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelIsDebuggerAttached(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceKernelIsDevKit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelIsExperimentalBeta(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 4125e11eea..a0a7f0f467 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -337,6 +337,14 @@ std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask) { return File::ExchangeCreationMask_nid_no_patch(mask); } +int APS5_VABI sceKernelIsCEX(void) { + return 1; +} + +int APS5_VABI sceKernelIsDevKit(void) { + return 0; +} + int APS5_VABI fstatfs_nid_postfix(int d, KernelStatfs* buf) { return _fstatfs_nid_postfix(d, buf); } From cd94200d5fd9821989e80d2d1640c3d041a7ce19 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Sat, 24 Oct 2026 10:32:57 +0300 Subject: [PATCH 2/7] feat(libkernel_sys): describe the guest process through get_authinfo --- .../libs/prx/libkernel_sys/Export.Discovery.cpp | 5 ----- core/libs/prx/libkernel_sys/Export.cpp | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index bf1df5d5f0..76361d486d 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -67,11 +67,6 @@ int APS5_VABI futimesat_nid_postfix(void) { return 0; } -int APS5_VABI get_authinfo_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI get_module_info_list_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index a0a7f0f467..56edb6de2d 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -62,6 +62,7 @@ static int GuestFstat(int fd, bool* withoutVnode = nullptr) { constexpr int GuestEnoent = 2; constexpr int GuestEperm = 1; +constexpr int GuestEsrch = 3; constexpr int GuestEio = 5; constexpr int GuestEbadf = 9; constexpr int GuestEfault = 14; @@ -126,6 +127,14 @@ int GuestFileError(int error) { } } +struct KernelAuthInfo { + std::uint64_t programAuthorityId; + std::uint64_t capabilities[4]; + std::uint64_t attributes[4]; + std::uint64_t credentials[8]; +}; +static_assert(sizeof(KernelAuthInfo) == 0x88); + std::int64_t Fail(int error) { *__error_nid_postfix() = error; return -1; @@ -345,6 +354,14 @@ int APS5_VABI sceKernelIsDevKit(void) { return 0; } +int APS5_VABI get_authinfo_nid_postfix(int pid, KernelAuthInfo* info) { + if (info == nullptr) return static_cast(Fail(GuestEperm)); + if (pid != 0 && pid != getpid_nid_postfix()) return static_cast(Fail(GuestEsrch)); + *info = {}; + info->capabilities[0] = 0x2000000000000000; + return 0; +} + int APS5_VABI fstatfs_nid_postfix(int d, KernelStatfs* buf) { return _fstatfs_nid_postfix(d, buf); } From afa6f6dcbc99768dc74a9be885ea75fc6e4b1aa6 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Sat, 24 Oct 2026 10:32:57 +0300 Subject: [PATCH 3/7] feat(libkernel_sys): return the OpenPSID from sceKernelGetOpenPsIdForSystem --- core/libs/prx/libkernel_sys/Export.Discovery.cpp | 5 ----- core/libs/prx/libkernel_sys/Export.cpp | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 76361d486d..c7f6bc1e49 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -982,11 +982,6 @@ int APS5_VABI sceKernelGetMemoryPstate(void) { return 0; } -int APS5_VABI sceKernelGetOpenPsIdForSystem(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetPagingStatsOfAllObjects(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 56edb6de2d..c6504ce0ec 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -346,6 +346,12 @@ std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask) { return File::ExchangeCreationMask_nid_no_patch(mask); } +int APS5_VABI sceKernelGetOpenPsId(void* id); + +int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) { + return sceKernelGetOpenPsId(id); +} + int APS5_VABI sceKernelIsCEX(void) { return 1; } From af7192f686ef69ca6cd3f013a5f6f0762ad040a5 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Sat, 24 Oct 2026 10:33:45 +0300 Subject: [PATCH 4/7] test(libkernel_sys): check the console kind, auth info and system OpenPSID --- core/libs/CMakeLists.txt | 9 +++ .../tests/GuestKernelSysConsoleIdentity.cpp | 70 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 core/libs/tests/GuestKernelSysConsoleIdentity.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index eea852c88a..81720715b7 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -269,6 +269,15 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_sys_sdk_version PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_console_identity_tests tests/GuestKernelSysConsoleIdentity.cpp) + target_include_directories(guest_kernel_sys_console_identity_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_console_identity_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_console_identity_tests) + add_test(NAME guest_kernel_sys_console_identity COMMAND guest_kernel_sys_console_identity_tests) + if(WIN32) + set_tests_properties(guest_kernel_sys_console_identity PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(nid_buffer_validation_tests nid/tests/BufferValidation.cpp) target_include_directories(nid_buffer_validation_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) target_compile_features(nid_buffer_validation_tests PRIVATE cxx_std_20) diff --git a/core/libs/tests/GuestKernelSysConsoleIdentity.cpp b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp new file mode 100644 index 0000000000..14b664e133 --- /dev/null +++ b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp @@ -0,0 +1,70 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceKernelIsCEX(void); +int APS5_VABI sceKernelIsDevKit(void); +int APS5_VABI get_authinfo_nid_postfix(int pid, void* info); +int APS5_VABI sceKernelGetOpenPsId(void* id); +int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id); +int APS5_VABI getpid_nid_postfix(void); +int* APS5_VABI __error_nid_postfix(void); +} + +namespace { + +constexpr int GuestEperm = 1; +constexpr int GuestEsrch = 3; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "KERNEL SYS CONSOLE IDENTITY: %s\n", message); + std::abort(); + } +} + +void CheckConsoleKind() { + Require(sceKernelIsCEX() == 1, "the console must be reported as a retail unit"); + Require(sceKernelIsDevKit() == 0, "the console must not be reported as a development kit"); +} + +void CheckAuthInfo() { + std::array info; + *__error_nid_postfix() = 0; + Require(get_authinfo_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == GuestEperm, "a null result must fail with EPERM"); + info.fill(0x5a5a5a5a5a5a5a5a); + *__error_nid_postfix() = 0; + Require(get_authinfo_nid_postfix(getpid_nid_postfix() + 1, info.data()) == -1 && *__error_nid_postfix() == GuestEsrch, + "another process must fail with ESRCH"); + Require(info[0] == 0x5a5a5a5a5a5a5a5a, "a refused call must not write the result"); + for (const int pid : {0, getpid_nid_postfix()}) { + info.fill(0x5a5a5a5a5a5a5a5a); + Require(get_authinfo_nid_postfix(pid, info.data()) == 0, "the guest process must be described"); + Require(info[0] == 0 && info[1] == 0x2000000000000000, "the authority ID or first capability word is wrong"); + for (std::size_t i = 2; i < 17; ++i) Require(info[i] == 0, "the rest of the result must be cleared"); + Require(info[17] == 0x5a5a5a5a5a5a5a5a, "get_authinfo wrote past its 0x88-byte result"); + } +} + +void CheckOpenPsId() { + std::array plain{}; + std::array system; + system.fill(0xa5); + Require(sceKernelGetOpenPsId(plain.data()) == 0, "the OpenPSID must be readable"); + Require(sceKernelGetOpenPsIdForSystem(system.data()) == 0, "the system OpenPSID must be readable"); + Require(std::memcmp(plain.data(), system.data(), plain.size()) == 0, "both OpenPSID queries must agree"); + for (std::size_t i = plain.size(); i < system.size(); ++i) Require(system[i] == 0xa5, "the system OpenPSID wrote past 16 bytes"); +} + +} + +int main() { + CheckConsoleKind(); + CheckAuthInfo(); + CheckOpenPsId(); + return 0; +} From 0d851dd1f3ee28f8e65aa802c5591a86c002edb8 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Sat, 24 Oct 2026 10:33:58 +0300 Subject: [PATCH 5/7] docs(debt): note the retail console identity in libkernel_sys --- docs/dev/debt/libkernel_sys.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index fd4084515b..e992912552 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -27,6 +27,7 @@ - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask - [sceKernelGetModuleInfoInternal and sceKernelGetModuleListInternal](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer for every loaded module, system modules included. `sceKernelGetModuleInfoInternal` fills the 0x1A8-byte `SceKernelModuleInfoEx` of a module handle the way `sceKernelGetModuleInfoFromAddr` does for an address, with [shadPS4's checks](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/kernel/process.cpp#L243-L258) in their order: `EFAULT` for a null pointer, `EINVAL` when `st_size` is not 0x1A8, `ESRCH` for an unknown handle. `sceKernelGetModuleListInternal` returns the same list as `sceKernelGetModuleList`, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_libkernel.pas#L852-L858) does; fpPS4 marks that as a guess, and neither is checked on a PS5 - [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331)) +- [sceKernelIsCEX, sceKernelIsDevKit, get_authinfo and sceKernelGetOpenPsIdForSystem](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - the guest always runs on a retail unit, as in [shadPS4's process.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/process.cpp) with its devkit option off. `get_authinfo` takes shadPS4's [kernel.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp) errors and capability word, so the authority ID and the other capability, attribute and credential words stay 0. `sceKernelGetOpenPsIdForSystem` returns `sceKernelGetOpenPsId`'s value, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_libkernel.pas) does; whether the console keeps a separate ID for system callers is not known. Not measured on a console - [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it - [chown, lchown and fchownat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled, as for `fchown`: owner and group 0 and the unchanged value (-1) are accepted, and any other owner or group throws, naming it. The path is looked up first, as in FreeBSD 11's `kern_fchownat`, with `stat`, with `lstat` for `lchown`, and with `fstatat` for `fchownat`, so a lookup error is returned before the owner is checked and nothing is changed on the host. `fchownat` flags other than `AT_SYMLINK_NOFOLLOW` (0x200) return `EINVAL`, as in FreeBSD 11. The console's ownership rules are unverified - [linkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) resolves `AT_FDCWD` (-100) and absolute paths as `link` does, and flags other than `AT_SYMLINK_FOLLOW` (0x400) return `EINVAL`, as in FreeBSD 11. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. A symbolic link as the source without `AT_SYMLINK_FOLLOW` throws, where FreeBSD links the symbolic link itself. Console behaviour is unverified From 803601ffa4d665dd4688340101e50e141c463434 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Sun, 25 Oct 2026 13:07:08 +0200 Subject: [PATCH 6/7] fix(libkernel_sys): publish identity types and reject null system IDs --- core/libs/prx/libkernel_sys/Export.cpp | 11 +---- core/libs/prx/libkernel_sys/include/Api.hpp | 12 +++++- .../tests/GuestKernelSysConsoleIdentity.cpp | 41 ++++++++++--------- docs/dev/debt/libkernel_sys.md | 2 +- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index c6504ce0ec..444c3f19ba 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -127,14 +127,6 @@ int GuestFileError(int error) { } } -struct KernelAuthInfo { - std::uint64_t programAuthorityId; - std::uint64_t capabilities[4]; - std::uint64_t attributes[4]; - std::uint64_t credentials[8]; -}; -static_assert(sizeof(KernelAuthInfo) == 0x88); - std::int64_t Fail(int error) { *__error_nid_postfix() = error; return -1; @@ -346,9 +338,8 @@ std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask) { return File::ExchangeCreationMask_nid_no_patch(mask); } -int APS5_VABI sceKernelGetOpenPsId(void* id); - int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) { + if (id == nullptr) return GuestEinval; return sceKernelGetOpenPsId(id); } diff --git a/core/libs/prx/libkernel_sys/include/Api.hpp b/core/libs/prx/libkernel_sys/include/Api.hpp index 938bc8178b..dc67a83fbe 100644 --- a/core/libs/prx/libkernel_sys/include/Api.hpp +++ b/core/libs/prx/libkernel_sys/include/Api.hpp @@ -24,6 +24,14 @@ struct KernelTitleWorkaround { std::uint64_t ids[2]; }; +struct KernelAuthInfo { + std::uint64_t programAuthorityId; + std::uint64_t capabilities[4]; + std::uint64_t attributes[4]; + std::uint64_t credentials[8]; +}; +static_assert(sizeof(KernelAuthInfo) == 0x88); + extern "C" { int APS5_VABI aio_read_nid_postfix(GuestAiocb* aiocb); @@ -80,7 +88,7 @@ int APS5_VABI cpuset_getid_nid_postfix(void); int APS5_VABI cpuset_setid_nid_postfix(void); int APS5_VABI dynlib_get_obj_member_nid_postfix(void); int APS5_VABI futimesat_nid_postfix(void); -int APS5_VABI get_authinfo_nid_postfix(void); +int APS5_VABI get_authinfo_nid_postfix(int pid, KernelAuthInfo* info); int APS5_VABI get_module_info_list_nid_postfix(void); int APS5_VABI get_page_table_stats_nid_postfix(void); int APS5_VABI get_sdk_compiled_version_nid_postfix(void); @@ -265,7 +273,7 @@ int APS5_VABI sceKernelGetIpcPath(void); int APS5_VABI sceKernelGetKnobs(void); int APS5_VABI sceKernelGetKqueueFromEqueue(void); int APS5_VABI sceKernelGetMemoryPstate(void); -int APS5_VABI sceKernelGetOpenPsIdForSystem(void); +int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id); int APS5_VABI sceKernelGetPagingStatsOfAllObjects(void); int APS5_VABI sceKernelGetPagingStatsOfAllThreads(void); int APS5_VABI sceKernelGetParentApp(void); diff --git a/core/libs/tests/GuestKernelSysConsoleIdentity.cpp b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp index 14b664e133..f01ac5f625 100644 --- a/core/libs/tests/GuestKernelSysConsoleIdentity.cpp +++ b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp @@ -1,20 +1,12 @@ -#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel_sys/libkernel_sys.hpp" +#include "prx/libc/libc.hpp" #include +#include #include #include #include #include -extern "C" { -int APS5_VABI sceKernelIsCEX(void); -int APS5_VABI sceKernelIsDevKit(void); -int APS5_VABI get_authinfo_nid_postfix(int pid, void* info); -int APS5_VABI sceKernelGetOpenPsId(void* id); -int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id); -int APS5_VABI getpid_nid_postfix(void); -int* APS5_VABI __error_nid_postfix(void); -} - namespace { constexpr int GuestEperm = 1; @@ -33,20 +25,25 @@ void CheckConsoleKind() { } void CheckAuthInfo() { - std::array info; + struct { + KernelAuthInfo value; + std::uint64_t guard; + } info; *__error_nid_postfix() = 0; Require(get_authinfo_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == GuestEperm, "a null result must fail with EPERM"); - info.fill(0x5a5a5a5a5a5a5a5a); + std::memset(&info, 0x5a, sizeof(info)); *__error_nid_postfix() = 0; - Require(get_authinfo_nid_postfix(getpid_nid_postfix() + 1, info.data()) == -1 && *__error_nid_postfix() == GuestEsrch, + Require(get_authinfo_nid_postfix(getpid_nid_postfix() + 1, &info.value) == -1 && *__error_nid_postfix() == GuestEsrch, "another process must fail with ESRCH"); - Require(info[0] == 0x5a5a5a5a5a5a5a5a, "a refused call must not write the result"); + for (const auto byte : std::bit_cast>(info)) + Require(byte == 0x5a, "a refused call must not write the result"); for (const int pid : {0, getpid_nid_postfix()}) { - info.fill(0x5a5a5a5a5a5a5a5a); - Require(get_authinfo_nid_postfix(pid, info.data()) == 0, "the guest process must be described"); - Require(info[0] == 0 && info[1] == 0x2000000000000000, "the authority ID or first capability word is wrong"); - for (std::size_t i = 2; i < 17; ++i) Require(info[i] == 0, "the rest of the result must be cleared"); - Require(info[17] == 0x5a5a5a5a5a5a5a5a, "get_authinfo wrote past its 0x88-byte result"); + std::memset(&info, 0x5a, sizeof(info)); + Require(get_authinfo_nid_postfix(pid, &info.value) == 0, "the guest process must be described"); + Require(info.value.programAuthorityId == 0 && info.value.capabilities[0] == 0x2000000000000000, "the authority ID or first capability word is wrong"); + const auto bytes = std::bit_cast>(info.value); + for (std::size_t i = 16; i < bytes.size(); ++i) Require(bytes[i] == 0, "the rest of the result must be cleared"); + Require(info.guard == 0x5a5a5a5a5a5a5a5a, "get_authinfo wrote past its 0x88-byte result"); } } @@ -54,6 +51,10 @@ void CheckOpenPsId() { std::array plain{}; std::array system; system.fill(0xa5); + *__error_nid_postfix() = GuestEsrch; + Require(sceKernelGetOpenPsIdForSystem(nullptr) == 22, "a null system OpenPSID must return positive EINVAL"); + Require(*__error_nid_postfix() == GuestEsrch, "a null system OpenPSID must leave errno unchanged"); + for (auto byte : system) Require(byte == 0xa5, "a null system OpenPSID must not write output"); Require(sceKernelGetOpenPsId(plain.data()) == 0, "the OpenPSID must be readable"); Require(sceKernelGetOpenPsIdForSystem(system.data()) == 0, "the system OpenPSID must be readable"); Require(std::memcmp(plain.data(), system.data(), plain.size()) == 0, "both OpenPSID queries must agree"); diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index e992912552..0286dda596 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -27,7 +27,7 @@ - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask - [sceKernelGetModuleInfoInternal and sceKernelGetModuleListInternal](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer for every loaded module, system modules included. `sceKernelGetModuleInfoInternal` fills the 0x1A8-byte `SceKernelModuleInfoEx` of a module handle the way `sceKernelGetModuleInfoFromAddr` does for an address, with [shadPS4's checks](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/kernel/process.cpp#L243-L258) in their order: `EFAULT` for a null pointer, `EINVAL` when `st_size` is not 0x1A8, `ESRCH` for an unknown handle. `sceKernelGetModuleListInternal` returns the same list as `sceKernelGetModuleList`, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_libkernel.pas#L852-L858) does; fpPS4 marks that as a guess, and neither is checked on a PS5 - [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331)) -- [sceKernelIsCEX, sceKernelIsDevKit, get_authinfo and sceKernelGetOpenPsIdForSystem](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - the guest always runs on a retail unit, as in [shadPS4's process.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/process.cpp) with its devkit option off. `get_authinfo` takes shadPS4's [kernel.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp) errors and capability word, so the authority ID and the other capability, attribute and credential words stay 0. `sceKernelGetOpenPsIdForSystem` returns `sceKernelGetOpenPsId`'s value, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_libkernel.pas) does; whether the console keeps a separate ID for system callers is not known. Not measured on a console +- [sceKernelIsCEX, sceKernelIsDevKit, get_authinfo and sceKernelGetOpenPsIdForSystem](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - the guest always runs on a retail unit, as in [shadPS4's process.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/process.cpp) with its devkit option off. `get_authinfo` takes shadPS4's [kernel.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp) errors and capability word, so the authority ID and the other capability, attribute and credential words stay 0. `sceKernelGetOpenPsIdForSystem` returns `sceKernelGetOpenPsId`'s value, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_libkernel.pas) does; firmware 13.42 reads a separate `machdep.openpsid_for_sys`, but whether its value differs is unknown. Null returns positive 22 per the [module report](https://github.com/boykopovar/AnyPS5/pull/10524#issuecomment-6393566914). The kernel-side `get_authinfo` behavior remains unverified - [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it - [chown, lchown and fchownat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled, as for `fchown`: owner and group 0 and the unchanged value (-1) are accepted, and any other owner or group throws, naming it. The path is looked up first, as in FreeBSD 11's `kern_fchownat`, with `stat`, with `lstat` for `lchown`, and with `fstatat` for `fchownat`, so a lookup error is returned before the owner is checked and nothing is changed on the host. `fchownat` flags other than `AT_SYMLINK_NOFOLLOW` (0x200) return `EINVAL`, as in FreeBSD 11. The console's ownership rules are unverified - [linkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) resolves `AT_FDCWD` (-100) and absolute paths as `link` does, and flags other than `AT_SYMLINK_FOLLOW` (0x400) return `EINVAL`, as in FreeBSD 11. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. A symbolic link as the source without `AT_SYMLINK_FOLLOW` throws, where FreeBSD links the symbolic link itself. Console behaviour is unverified From 866ab27a801c944bbbf469953f6b69d040626166 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Sun, 25 Oct 2026 13:11:03 +0200 Subject: [PATCH 7/7] docs(debt): clarify unverified console identity behavior --- docs/dev/debt/libkernel_sys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 0286dda596..ad1f62a1fa 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -27,7 +27,7 @@ - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask - [sceKernelGetModuleInfoInternal and sceKernelGetModuleListInternal](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer for every loaded module, system modules included. `sceKernelGetModuleInfoInternal` fills the 0x1A8-byte `SceKernelModuleInfoEx` of a module handle the way `sceKernelGetModuleInfoFromAddr` does for an address, with [shadPS4's checks](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/kernel/process.cpp#L243-L258) in their order: `EFAULT` for a null pointer, `EINVAL` when `st_size` is not 0x1A8, `ESRCH` for an unknown handle. `sceKernelGetModuleListInternal` returns the same list as `sceKernelGetModuleList`, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_libkernel.pas#L852-L858) does; fpPS4 marks that as a guess, and neither is checked on a PS5 - [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331)) -- [sceKernelIsCEX, sceKernelIsDevKit, get_authinfo and sceKernelGetOpenPsIdForSystem](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - the guest always runs on a retail unit, as in [shadPS4's process.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/process.cpp) with its devkit option off. `get_authinfo` takes shadPS4's [kernel.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp) errors and capability word, so the authority ID and the other capability, attribute and credential words stay 0. `sceKernelGetOpenPsIdForSystem` returns `sceKernelGetOpenPsId`'s value, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_libkernel.pas) does; firmware 13.42 reads a separate `machdep.openpsid_for_sys`, but whether its value differs is unknown. Null returns positive 22 per the [module report](https://github.com/boykopovar/AnyPS5/pull/10524#issuecomment-6393566914). The kernel-side `get_authinfo` behavior remains unverified +- [Console identity](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - `get_authinfo` errors and capability bits remain unverified on PS5; they follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp), leaving the authority ID and other capability, attribute and credential words zero. The host models a retail unit (`IsCEX` 1, `IsDevKit` 0). The system OpenPSID reuses the ordinary ID; firmware 13.42 reads a separate `machdep.openpsid_for_sys`, whose value is unknown. Null returns positive 22 per the [module report](https://github.com/boykopovar/AnyPS5/pull/10524#issuecomment-6393566914). - [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it - [chown, lchown and fchownat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled, as for `fchown`: owner and group 0 and the unchanged value (-1) are accepted, and any other owner or group throws, naming it. The path is looked up first, as in FreeBSD 11's `kern_fchownat`, with `stat`, with `lstat` for `lchown`, and with `fstatat` for `fchownat`, so a lookup error is returned before the owner is checked and nothing is changed on the host. `fchownat` flags other than `AT_SYMLINK_NOFOLLOW` (0x200) return `EINVAL`, as in FreeBSD 11. The console's ownership rules are unverified - [linkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) resolves `AT_FDCWD` (-100) and absolute paths as `link` does, and flags other than `AT_SYMLINK_FOLLOW` (0x400) return `EINVAL`, as in FreeBSD 11. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. A symbolic link as the source without `AT_SYMLINK_FOLLOW` throws, where FreeBSD links the symbolic link itself. Console behaviour is unverified