From 057b8108b35c8a82eb0cdbebf0aa8867ed3dea0c Mon Sep 17 00:00:00 2001 From: jagrat Singh Date: Mon, 12 Oct 2026 21:57:07 +0530 Subject: [PATCH] feat(libkernel): implement setregid Follow FreeBSD sys_setregid and root identity model: accept real and effective group id 0 or -1 (unchanged) and change nothing; any other id throws. Update GuestCredentials test and TechnicalDebt. --- .../libs/prx/libkernel/System/src/Process.cpp | 9 +++++++++ core/libs/tests/GuestCredentials.cpp | 20 +++++++++++++++++++ docs/dev/debt/libkernel.md | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index f0f00b664..d7ccd297e 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -548,6 +548,15 @@ int APS5_VABI setreuid_nid_postfix(std::uint32_t ruid, std::uint32_t euid) { return 0; } +int APS5_VABI setregid_nid_postfix(std::uint32_t rgid, std::uint32_t egid) { + constexpr std::uint32_t unchanged = 0xffffffffu; + if (rgid != 0 && rgid != unchanged) + throw std::runtime_error("setregid: unsupported real group id " + std::to_string(rgid)); + if (egid != 0 && egid != unchanged) + throw std::runtime_error("setregid: unsupported effective group id " + std::to_string(egid)); + return 0; +} + int APS5_VABI setgroups_nid_postfix(int count, const std::uint32_t* groups) { if (count < 0 || count > freebsdGroupsMax) { *__error_nid_postfix() = errnoInvalidArgument; diff --git a/core/libs/tests/GuestCredentials.cpp b/core/libs/tests/GuestCredentials.cpp index 99e003528..01cc2d300 100644 --- a/core/libs/tests/GuestCredentials.cpp +++ b/core/libs/tests/GuestCredentials.cpp @@ -9,6 +9,7 @@ int APS5_VABI seteuid_nid_postfix(std::uint32_t); int APS5_VABI setegid_nid_postfix(std::uint32_t); int APS5_VABI setuid_nid_postfix(std::uint32_t); int APS5_VABI setreuid_nid_postfix(std::uint32_t, std::uint32_t); +int APS5_VABI setregid_nid_postfix(std::uint32_t, std::uint32_t); int APS5_VABI setgroups_nid_postfix(int, const std::uint32_t*); int APS5_VABI getgroups_nid_postfix(int, std::uint32_t*); int APS5_VABI getuid_nid_postfix(void); @@ -19,6 +20,7 @@ int* APS5_VABI __error_nid_postfix(); static void Require(bool value) { if (!value) std::abort(); } using SetId = int (APS5_VABI *)(std::uint32_t); +using SetReId = int (APS5_VABI *)(std::uint32_t, std::uint32_t); static bool Throws(SetId function, std::uint32_t id) { try { @@ -29,6 +31,15 @@ static bool Throws(SetId function, std::uint32_t id) { return false; } +static bool ThrowsRe(SetReId function, std::uint32_t r, std::uint32_t e) { + try { + function(r, e); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + constexpr int GuestEfault = 14; constexpr int GuestEinval = 22; constexpr std::uint32_t Unchanged = 0xffffffffu; @@ -54,6 +65,15 @@ int main() { Require(setreuid_nid_postfix(0, Unchanged) == 0); Require(setreuid_nid_postfix(Unchanged, 0) == 0); Require(setreuid_nid_postfix(0, 0) == 0); + Require(ThrowsRe(setreuid_nid_postfix, 1000, 0)); + Require(ThrowsRe(setreuid_nid_postfix, 0, 1000)); + + Require(setregid_nid_postfix(Unchanged, Unchanged) == 0); + Require(setregid_nid_postfix(0, Unchanged) == 0); + Require(setregid_nid_postfix(Unchanged, 0) == 0); + Require(setregid_nid_postfix(0, 0) == 0); + Require(ThrowsRe(setregid_nid_postfix, 1000, 0)); + Require(ThrowsRe(setregid_nid_postfix, 0, 1000)); std::array groups{}; Require(getgroups_nid_postfix(0, nullptr) == 1); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 132b6b9b4..e7c5199ac 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -59,7 +59,7 @@ - [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - a descriptor the socket poller reports as unknown that is below `GuestSockets::FirstDescriptor` is polled on the host instead of throwing, except on Windows where it still throws; an invalid host descriptor fails with `EBADF`, and mixed host and socket sets are polled in short alternating slices so neither side waits out the full slice alone. Wobbly Life (PPSA29413) selects on file descriptors while streaming its bundles - [__progname](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [environ](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - data export pointing to a fixed empty environment so titles that import it load; it is not the environment libc's `getenv` reads, so it lists none of those variables and `setenv`/`putenv` changes do not appear in it -- [seteuid, setegid, setuid, setreuid, setgroups, getgroups](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid`, `sys_setegid`, `sys_setuid` and `sys_setreuid`, id 0 (or -1 for an unchanged `setreuid` id) is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change an id, which is not modelled, and throws. `setgroups` keeps the supplementary group list as `kern_setgroups` does (`NGROUPS_MAX` 1023, an empty list leaves the effective group as the only entry, a first entry other than 0 would change the effective group and throws) and `getgroups` reports it. The ids and privileges a title has on a console are unverified +- [seteuid, setegid, setuid, setreuid, setregid, setgroups, getgroups](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid`, `sys_setegid`, `sys_setuid`, `sys_setreuid` and `sys_setregid`, id 0 (or -1 for an unchanged `setreuid`/`setregid` id) is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change an id, which is not modelled, and throws. `setgroups` keeps the supplementary group list as `kern_setgroups` does (`NGROUPS_MAX` 1023, an empty list leaves the effective group as the only entry, a first entry other than 0 would change the effective group and throws) and `getgroups` reports it. The ids and privileges a title has on a console are unverified - [pthread_barrierattr_setpshared](../../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) - PTHREAD_PROCESS_SHARED is accepted on the assumption that the console follows FreeBSD 11.0 libthr, which accepts it (9.0 rejects it with EINVAL); unverified on hardware. Guest code runs in one process, so a shared barrier behaves as a private one - [pthread_getname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - a thread that has already been joined returns `ESRCH` (3) on a PS5 (firmware 13.60), but its object is freed after the join here, so the pointer cannot be told apart from a live thread and its name is read as for a live one - [sceKernelGetOperationMode](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the signature `(int* mode, int* submode)` comes from PPSA12544, which logs both values after the call and ignores the result. A homebrew payload on a PS5 (firmware 13.60) reads mode 2 and submode 0, nothing else is written, and the result register holds no status, so the function is taken to return nothing. Whether a title process reads the same values is unverified. Null outputs throw