diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index a63d8296fd..ac45791981 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -1333,11 +1333,6 @@ int APS5_VABI sceKernelIsNeoMode(void) { return (attribute & PARAM_SFO_ATTRIBUTE_SUPPORT_NEO_MODE) != 0 ? 1 : 0; } -int APS5_VABI sceKernelIsPs4Process(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelJitGetSharedMemoryInfo(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 de60e1a45c..3bf2885a07 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -28,6 +28,7 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" @@ -426,6 +427,22 @@ int APS5_VABI sceKernelIsKratos(void) { return 0; } +bool IsPs4Process() { + try { + const auto titleId = GetAppTitleId_nid_postfix(); + const std::string_view id(titleId.value, strnlen(titleId.value, sizeof(titleId.value))); + return id.starts_with("CUSA"); + } catch (const std::runtime_error&) { + return false; + } catch (const std::length_error&) { + return false; + } +} + +int APS5_VABI sceKernelIsPs4Process(void) { + return IsPs4Process() ? 1 : 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)); diff --git a/core/libs/tests/GuestKernelSysConsoleIdentity.cpp b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp index 0775baa885..d5e01e8dab 100644 --- a/core/libs/tests/GuestKernelSysConsoleIdentity.cpp +++ b/core/libs/tests/GuestKernelSysConsoleIdentity.cpp @@ -6,6 +6,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace { @@ -19,7 +24,7 @@ void Require(bool condition, const char* message) { } } -void CheckConsoleKind() { +void CheckConsoleKind(bool ps4) { 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"); Require(sceKernelIsGenuineCEX() == 1, "the console must be reported as a genuine retail unit"); @@ -28,6 +33,11 @@ void CheckConsoleKind() { Require(sceKernelIsTestKit() == 0, "the console must not be reported as a test kit"); Require(sceKernelIsProspero() == 1, "the architecture must be reported as Prospero"); Require(sceKernelIsKratos() == 0, "the architecture must not be reported as Kratos"); + if (ps4) { + Require(sceKernelIsPs4Process() == 1, "a CUSA title must be reported as a PS4 process"); + } else { + Require(sceKernelIsPs4Process() == 0, "the default process must not be reported as a PS4 process"); + } } void CheckAuthInfo() { @@ -69,9 +79,33 @@ void CheckOpenPsId() { } -int main() { - CheckConsoleKind(); +int main(int argc, char** argv) { + if (argc == 2 && std::string_view(argv[1]) == "ps4-child") { + CheckConsoleKind(true); + return 0; + } + CheckConsoleKind(false); CheckAuthInfo(); CheckOpenPsId(); + + const auto directory = std::filesystem::temp_directory_path() / ("ps4-identity-test-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(directory / "app0" / "sce_sys"); + struct Entry { std::uint16_t key, format; std::uint32_t length, maximum, offset; }; + const std::uint32_t header[5] = {0x46535000, 0x0101, 52, 72, 2}; + const Entry entries[2] = {{0, 0x0204, 8, 8, 0}, {6, 0x0204, 10, 12, 8}}; + { + std::ofstream file(directory / "app0/sce_sys/param.sfo", std::ios::binary); + file.write(reinterpret_cast(header), sizeof(header)); + file.write(reinterpret_cast(entries), sizeof(entries)); + file << std::string("TITLE\0TITLE_ID\0\0\0\0\0\0", 20) << std::string("Example\0", 8) << "CUSA00000" << std::string("\0\0\0", 3); + } +#ifdef _WIN32 + const std::string command = "\"cd /d \"" + directory.string() + "\" && \"" + argv[0] + "\" ps4-child\""; +#else + const std::string command = "cd \"" + directory.string() + "\" && \"" + argv[0] + "\" ps4-child"; +#endif + const int status = std::system(command.c_str()); + std::filesystem::remove_all(directory); + Require(status == 0, "the PS4 child process must succeed"); return 0; } diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 28681126e6..770ec9cc22 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -368,7 +368,7 @@ - [sigreturn](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) follows FreeBSD 11.4's amd64 [`sys_sigreturn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/amd64/amd64/machdep.c#L473-L576) on `setcontext`'s `ucontext_t`: a null context returns `EFAULT`, and `mc_flags` outside `_MC_FLAG_MASK` or an `rflags` differing from the current flags outside `PSL_USERCHANGE` return `EINVAL`; `mc_len` is not checked. As in [`set_fpcontext`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/amd64/amd64/machdep.c#L2349-L2370), `_MC_FPFMT_NODEV` keeps the FPU state, `_MC_FPOWNED_NONE` loads the initial one, the FPU and PCB owners load `mc_fpstate`, and other values return `EINVAL`. The rest is restored as by `setcontext`, which keeps the `%fs` and `%gs` bases FreeBSD would load. A `%cs` that is not a user selector (FreeBSD also raises `SIGBUS`) and `_MC_HASFPXSTATE` throw - [sceKernelGetModuleInfoInternal, sceKernelGetModuleListInternal and sceKernelLoadStartModuleForSysmodule](../../../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. `sceKernelLoadStartModuleForSysmodule` forwards to `sceKernelLoadStartModule` - [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)) -- [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, `IsGenuineCEX` 1, `IsDevKit` 0, `IsGenuineDevKit` 0, `IsTestKit` 0, `IsGenuineTestKit` 0) and reports Prospero architecture (`IsProspero` 1, `IsKratos` 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). +- [Console identity](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - `get_authinfo` errors and capabilities are unverified on PS5, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp) with zero authority and credential words. The host models a retail unit (`IsCEX` 1, `IsGenuineCEX` 1, `IsDevKit` 0, `IsGenuineDevKit` 0, `IsTestKit` 0, `IsGenuineTestKit` 0) and Prospero (`IsProspero` 1, `IsKratos` 0). `sceKernelIsPs4Process` returns 1 when the title ID starts with `CUSA` and 0 otherwise, as `libSceRtc` and `libSceConvertKeycode` tell PS4 titles apart; this mapping follows the PS4 title-ID convention and is unmeasured on a console, since BunnyFl's [13.42 report](https://github.com/boykopovar/AnyPS5/pull/10959#issuecomment-6404011201) only mentions the export returning 0 in passing. System OpenPSID reuses the ordinary ID; firmware 13.42 reads a separate `machdep.openpsid_for_sys` of unknown value. Null returns positive 22 per the [module report](https://github.com/boykopovar/AnyPS5/pull/10524#issuecomment-6393566914). - [kldfind, kldfirstmod, kldnext and kldunload](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follow FreeBSD 11.4's linker calls ([sys/kern/kern_linker.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_linker.c)): kldfind validates the module name with CopyPath (EFAULT on null or unreadable pointers, ENAMETOOLONG for paths of 1024 bytes or more) and returns ENOENT for any module name; on FreeBSD the kernel itself is linker file 1, and AnyPS5 models an empty file list, so kldnext(0) returns 0 and kldfind("kernel") fails; kldnext returns ENOENT for negative or positive fileids; kldfirstmod returns ENOENT for any fileid; and kldunload returns EPERM as sandboxed/jailed titles lack PRIV_KLD_UNLOAD. Dynamic kernel linker module lookup and management remain unverified on hardware - [kldstat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) never succeeds: no linker file is listed here, as for `kldnext`, so the kernel's own file (id 1 on FreeBSD) and the modules a console preloads are missing, every file id gets `ENOENT` and the structure is never written. The checks before the lookup follow FreeBSD 11.4's [`sys_kldstat`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/kern_linker.c#L1228-L1250): only the `version` field is read (`EFAULT` when it cannot be), and a version other than the size of `struct kld_file_stat_1` (1056 bytes on amd64) or `struct kld_file_stat` (2080) gets `EINVAL`. Console behaviour is 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