From d60500a34b6bac36cfaadc7054ffc7c4c78200fc Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:32:43 +0530 Subject: [PATCH 1/6] fix(libkernel): return EFAULT from sceKernelGetAppInfo for invalid memory pointers --- core/libs/prx/libkernel/System/src/Process.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index ec1de2cc9d..b7c33a2fe9 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -662,18 +662,18 @@ int APS5_VABI sceKernelGetAppInfo(int pid, void* info) { constexpr std::size_t TitleIdOffset = 0x10; constexpr std::size_t TitleIdBytes = 10; constexpr std::size_t MarkerOffset = 0x50; - auto* bytes = static_cast(info); - std::memset(bytes, 0, StructureBytes); + std::uint8_t record[StructureBytes]{}; if (own) { const auto titleId = GetAppTitleId_nid_postfix(); char field[TitleIdBytes] = {}; std::strncpy(field, titleId.value, sizeof(field) - 1); const std::uint32_t appId = 1; - std::memcpy(bytes, &appId, sizeof(appId)); - std::memcpy(bytes + TitleIdOffset, field, TitleIdBytes); + std::memcpy(record, &appId, sizeof(appId)); + std::memcpy(record + TitleIdOffset, field, TitleIdBytes); } const std::uint32_t marker = 0xffffffffu; - std::memcpy(bytes + MarkerOffset, &marker, sizeof(marker)); + std::memcpy(record + MarkerOffset, &marker, sizeof(marker)); + if (!WriteGuest(info, record, sizeof(record))) return SCE_KERNEL_ERROR_EFAULT; return 0; } From c65ad20571ba4809ff5ec60537fb556ff8112009 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:33:18 +0530 Subject: [PATCH 2/6] test(libkernel): cover invalid destination pointer rejection in sceKernelGetAppInfo --- core/libs/tests/GuestAppInfo.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/tests/GuestAppInfo.cpp b/core/libs/tests/GuestAppInfo.cpp index 59c07bcff8..633dd07708 100644 --- a/core/libs/tests/GuestAppInfo.cpp +++ b/core/libs/tests/GuestAppInfo.cpp @@ -98,6 +98,8 @@ int main(int argc, char** argv) { std::memset(expected + 0x50, 0xff, 4); Require(std::memcmp(wide, expected, sizeof(wide)) == 0); Require(sceKernelGetAppInfo(self, nullptr) == 0); + Require(sceKernelGetAppInfo(self, reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); + Require(sceKernelGetAppInfo(0, reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); std::filesystem::current_path(previous); std::filesystem::remove_all(root); const auto empty = std::filesystem::temp_directory_path() / ("guest_app_info_empty_" + std::to_string(self)); From b25d10ddf1846f185de081207a8c9706c18ea5ae Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:34:41 +0530 Subject: [PATCH 3/6] docs(libkernel): record unwritable store fallback for sceKernelGetAppInfo --- docs/dev/debt/libkernel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index f686a0d398..9a7ac18507 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -43,7 +43,7 @@ - [sceKernelGettimezone](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) reports the host zone, not the console's time zone setting: `tz_minuteswest` holds the standard offset in minutes east of UTC, despite its name, and `tz_dsttime` the zone's daylight saving amount in minutes, found as for the last argument of `sceKernelConvertLocaltimeToUtc`. A PS5 on firmware 13.40 set to US Eastern returned -300 and 60 (measured 2026-10-19, in summer time); what it returns outside summer time or for a zone without daylight saving was not measured. The Windows limits of the entry above apply. - [getuid, geteuid, getgid, getegid, getresuid, getresgid 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. `getresuid` and `getresgid` follow FreeBSD 11.4's `sys_getresuid` and `sys_getresgid`, writing 0 to non-null pointers and leaving errno untouched on success. The values a title sees on a console are unverified - [pthread_condattr_getclock](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) - a null output pointer with a valid attribute throws; FreeBSD faults there and the console behavior is unverified. Null attribute pointers and null handles fail with EINVAL as in FreeBSD, not measured on a PS5. The signature follows the public PS5 payload SDK's [pthread.h](https://github.com/ps5-payload-dev/sdk/blob/f5d40f02770e9b0001dd9e18056eca70b3da8b40/include/freebsd/pthread.h). Valid initialized attributes report the stored clock ID, as in [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_condattr.c); this has not been measured on a PS5. Inaccessible non-null pointers and stale handles are not validated. -- [sceKernelGetAppInfo](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - a pid other than the caller's and 0 throws, because AnyPS5 cannot know which other processes exist: a payload process on a PS5 (kernel firmware 12.70, system software reported as 13.09) got 0 and a record for pid 1 and 0x80020003 (`ESRCH`, errno 3) for its own pid + 1, so which other pids return a record cannot be modelled. `app_id` (offset 0) is 1, the running title's id in libSceSystemService, which is a guess (0 there and at 0x10). Byte 0x1e was 5 there and is unknown in a game, so it is written as 0, as is every other field except 0xffffffff at 0x50. The 0x58-byte extent was measured only in that payload process (SDK v0.44); a title with a smaller structure could be overrun if it depends on the SDK version. Pid 0 gets the zeroed record without a title ID, as that process read it; whose record that is was not identified and a game's pid 0 record was not measured +- [sceKernelGetAppInfo](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - a pid other than the caller's and 0 throws, because AnyPS5 cannot know which other processes exist: a payload process on a PS5 (kernel firmware 12.70, system software 13.09) got 0 and a record for pid 1 and 0x80020003 (`ESRCH`, errno 3) for its own pid + 1. `app_id` (offset 0) is 1, a guess. Byte 0x1e was 5 there and is unknown in a game, so it is written as 0, as is every other field except 0xffffffff at 0x50. The 0x58-byte extent was measured only in that payload process; a title with a smaller structure could be overrun if it depends on the SDK version. Pid 0 gets the zeroed record without a title ID. An unwritable destination returns `SCE_KERNEL_ERROR_EFAULT` (0x8002000e) as a host-safety fallback; console behaviour for unwritable memory has not been measured. - [AMPR object layout and compact gathers](../../../core/libs/prx/libkernel/Apr/include/AprCommandBuffer.hpp) - the 24-byte object and offsets (type 0, cursor 4, count 8, capacity 12, pointer 16) follow [ampr_emu assertions](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_emu_command_buffer_types.h#L138-L143) and its [C declaration](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr.h#L41-L47). AnyPS5 represents the 32-bit type word as low 16-bit type and high 16-bit recording flags. Command encoding is AnyPS5 internal: opcode 29 and three 32-bit words encode the high offset byte, length minus one and low offset word. The 12-byte size reduces HFW's 1 MiB buffer pressure; it is not a claim about console opcode encoding. Existing 40-bit offsets and lengths 1 through 2^32 are retained; console behavior outside those limits is unverified. - [APR resolve ForEach result](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - `sceKernelAprResolveFilepathsToIdsAndFileSizesForEach` returns the number of paths that resolved, with the per-path status in `results` and the failed ids/sizes set to the invalid id and 0. The [ampr_emu](https://github.com/drakmor/ampr_emu) bridge passes the count of its resolver through and does not show what is counted; counting successes is not confirmed on a title. The guest errno of the failing non-`ForEach` resolves is set through `__error_nid_postfix` - [APR resolve argument checks and prefix join](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - the batched `sceKernelAprResolveFilepathsToIds` forms reject a null `prefix`, `paths` or `ids` with `EINVAL`, accept a count of 0 as a success that writes nothing, take a null path element as a missing file at its own index, write the invalid id into the failing slot, and leave `error_index` untouched on success; the prefixed forms join prefix and path by plain concatenation. None of these failure and validation details is measured on a title, and all of them differ from the [ampr_emu](https://github.com/drakmor/ampr_emu) bridge, which answers `EFAULT` for null arguments and rejects a count of 0, validates every path element before resolving any, leaves the failing slot unwritten, sets `error_index` to 0 on success, and inserts a `/` between prefix and path unless the prefix already ends in a separator From 16b53200f3d5a17b977beef207ef199b521098b6 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:58:28 +0530 Subject: [PATCH 4/6] fix(libkernel): preserve caller errno across sceKernelGetAppInfo guest memory write --- core/libs/prx/libkernel/System/src/Process.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index b7c33a2fe9..65cd9afb4a 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -673,7 +673,10 @@ int APS5_VABI sceKernelGetAppInfo(int pid, void* info) { } const std::uint32_t marker = 0xffffffffu; std::memcpy(record + MarkerOffset, &marker, sizeof(marker)); - if (!WriteGuest(info, record, sizeof(record))) return SCE_KERNEL_ERROR_EFAULT; + const int savedErrno = errno; + const bool written = WriteGuest(info, record, sizeof(record)); + errno = savedErrno; + if (!written) return SCE_KERNEL_ERROR_EFAULT; return 0; } From bed3f89b78d4337d56648a08f6a89f7382a1403a Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:58:59 +0530 Subject: [PATCH 5/6] test(libkernel): assert errno preservation on invalid memory in sceKernelGetAppInfo --- core/libs/tests/GuestAppInfo.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/libs/tests/GuestAppInfo.cpp b/core/libs/tests/GuestAppInfo.cpp index 633dd07708..d61d5fbe37 100644 --- a/core/libs/tests/GuestAppInfo.cpp +++ b/core/libs/tests/GuestAppInfo.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" +#include #include #include #include @@ -98,8 +99,12 @@ int main(int argc, char** argv) { std::memset(expected + 0x50, 0xff, 4); Require(std::memcmp(wide, expected, sizeof(wide)) == 0); Require(sceKernelGetAppInfo(self, nullptr) == 0); + errno = 71; Require(sceKernelGetAppInfo(self, reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); + Require(errno == 71); + errno = 71; Require(sceKernelGetAppInfo(0, reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); + Require(errno == 71); std::filesystem::current_path(previous); std::filesystem::remove_all(root); const auto empty = std::filesystem::temp_directory_path() / ("guest_app_info_empty_" + std::to_string(self)); From d700a92a3a06f5bf44f8a31bfd96b9c67022aab3 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sat, 31 Oct 2026 18:23:51 +0530 Subject: [PATCH 6/6] docs(libkernel): record errno preservation and restore original debt detail for sceKernelGetAppInfo --- docs/dev/debt/libkernel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 9a7ac18507..7696c8384d 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -43,7 +43,7 @@ - [sceKernelGettimezone](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) reports the host zone, not the console's time zone setting: `tz_minuteswest` holds the standard offset in minutes east of UTC, despite its name, and `tz_dsttime` the zone's daylight saving amount in minutes, found as for the last argument of `sceKernelConvertLocaltimeToUtc`. A PS5 on firmware 13.40 set to US Eastern returned -300 and 60 (measured 2026-10-19, in summer time); what it returns outside summer time or for a zone without daylight saving was not measured. The Windows limits of the entry above apply. - [getuid, geteuid, getgid, getegid, getresuid, getresgid 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. `getresuid` and `getresgid` follow FreeBSD 11.4's `sys_getresuid` and `sys_getresgid`, writing 0 to non-null pointers and leaving errno untouched on success. The values a title sees on a console are unverified - [pthread_condattr_getclock](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) - a null output pointer with a valid attribute throws; FreeBSD faults there and the console behavior is unverified. Null attribute pointers and null handles fail with EINVAL as in FreeBSD, not measured on a PS5. The signature follows the public PS5 payload SDK's [pthread.h](https://github.com/ps5-payload-dev/sdk/blob/f5d40f02770e9b0001dd9e18056eca70b3da8b40/include/freebsd/pthread.h). Valid initialized attributes report the stored clock ID, as in [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_condattr.c); this has not been measured on a PS5. Inaccessible non-null pointers and stale handles are not validated. -- [sceKernelGetAppInfo](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - a pid other than the caller's and 0 throws, because AnyPS5 cannot know which other processes exist: a payload process on a PS5 (kernel firmware 12.70, system software 13.09) got 0 and a record for pid 1 and 0x80020003 (`ESRCH`, errno 3) for its own pid + 1. `app_id` (offset 0) is 1, a guess. Byte 0x1e was 5 there and is unknown in a game, so it is written as 0, as is every other field except 0xffffffff at 0x50. The 0x58-byte extent was measured only in that payload process; a title with a smaller structure could be overrun if it depends on the SDK version. Pid 0 gets the zeroed record without a title ID. An unwritable destination returns `SCE_KERNEL_ERROR_EFAULT` (0x8002000e) as a host-safety fallback; console behaviour for unwritable memory has not been measured. +- [sceKernelGetAppInfo](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - a pid other than the caller's and 0 throws, because AnyPS5 cannot know which other processes exist: a payload process on a PS5 (kernel firmware 12.70, system software reported as 13.09) got 0 and a record for pid 1 and 0x80020003 (`ESRCH`, errno 3) for its own pid + 1, so which other pids return a record cannot be modelled. `app_id` (offset 0) is 1, the running title's id in libSceSystemService, which is a guess (0 there and at 0x10). Byte 0x1e was 5 there and is unknown in a game, so it is written as 0, as is every other field except 0xffffffff at 0x50. The 0x58-byte extent was measured only in that payload process (SDK v0.44); a title with a smaller structure could be overrun if it depends on the SDK version. Pid 0 gets the zeroed record without a title ID, as that process read it; whose record that is was not identified and a game's pid 0 record was not measured. An unwritable destination returns `SCE_KERNEL_ERROR_EFAULT` (0x8002000e) as a host-safety fallback: the console's failing pid + 1 row came back with errno 3 set next to the encoded return, here errno is left as the caller had it, and what the console leaves for an unwritable pointer is not measured. - [AMPR object layout and compact gathers](../../../core/libs/prx/libkernel/Apr/include/AprCommandBuffer.hpp) - the 24-byte object and offsets (type 0, cursor 4, count 8, capacity 12, pointer 16) follow [ampr_emu assertions](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_emu_command_buffer_types.h#L138-L143) and its [C declaration](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr.h#L41-L47). AnyPS5 represents the 32-bit type word as low 16-bit type and high 16-bit recording flags. Command encoding is AnyPS5 internal: opcode 29 and three 32-bit words encode the high offset byte, length minus one and low offset word. The 12-byte size reduces HFW's 1 MiB buffer pressure; it is not a claim about console opcode encoding. Existing 40-bit offsets and lengths 1 through 2^32 are retained; console behavior outside those limits is unverified. - [APR resolve ForEach result](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - `sceKernelAprResolveFilepathsToIdsAndFileSizesForEach` returns the number of paths that resolved, with the per-path status in `results` and the failed ids/sizes set to the invalid id and 0. The [ampr_emu](https://github.com/drakmor/ampr_emu) bridge passes the count of its resolver through and does not show what is counted; counting successes is not confirmed on a title. The guest errno of the failing non-`ForEach` resolves is set through `__error_nid_postfix` - [APR resolve argument checks and prefix join](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - the batched `sceKernelAprResolveFilepathsToIds` forms reject a null `prefix`, `paths` or `ids` with `EINVAL`, accept a count of 0 as a success that writes nothing, take a null path element as a missing file at its own index, write the invalid id into the failing slot, and leave `error_index` untouched on success; the prefixed forms join prefix and path by plain concatenation. None of these failure and validation details is measured on a title, and all of them differ from the [ampr_emu](https://github.com/drakmor/ampr_emu) bridge, which answers `EFAULT` for null arguments and rejects a count of 0, validates every path element before resolving any, leaves the failing slot unwritten, sets `error_index` to 0 on success, and inserts a `/` between prefix and path unless the prefix already ends in a separator