diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index a0d6f80550..08e0a82b9c 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -717,7 +717,7 @@ static constexpr std::pair GUEST_FILE_FLAGS[] = { {0x00000400, "UF_REPARSE"}, {0x00000800, "UF_ARCHIVE"}, {0x00001000, "UF_READONLY"}, {0x00008000, "UF_HIDDEN"}, {0x00010000, "SF_ARCHIVED"}, {0x00020000, "SF_IMMUTABLE"}, {0x00040000, "SF_APPEND"}, {0x00100000, "SF_NOUNLINK"}, {0x00200000, "SF_SNAPSHOT"}}; -static constexpr std::uint64_t GUEST_SF_SNAPSHOT = 0x00200000; +static constexpr std::uint64_t GUEST_SF_SETTABLE = 0xffff0000; static std::string FileFlagNames(std::uint64_t flags) { std::string names; @@ -733,7 +733,7 @@ static int SetFileFlags(const char* function, const std::string& target, std::ui std::uint64_t known = 0; for (const auto& entry : GUEST_FILE_FLAGS) known |= entry.first; if ((flags & ~known) != 0) return GUEST_EOPNOTSUPP; - if ((flags & GUEST_SF_SNAPSHOT) != 0) return GUEST_EPERM; + if ((flags & GUEST_SF_SETTABLE) != 0) return GUEST_EPERM; if (flags != 0) throw std::runtime_error(std::string(function) + ": setting " + FileFlagNames(flags) + " on " + target + " is not supported, the host keeps no BSD file flags"); return 0; diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 4c8ffa4dc0..29384fcceb 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -675,7 +675,11 @@ int main() { Require(chflags_nid_postfix(file.string().c_str(), ~0ULL) == -1 && *__error_nid_postfix() == 45); Require(chflags_nid_postfix(file.string().c_str(), 0x200000) == -1 && *__error_nid_postfix() == 1); Require(ChflagsThrows(file, 0x2, "UF_IMMUTABLE")); - Require(ChflagsThrows(file, 0x1000 | 0x40000, "UF_READONLY|SF_APPEND")); + for (const std::uint64_t system : {0x10000ULL, 0x20000ULL, 0x40000ULL, 0x100000ULL, 0x200000ULL, 0x370000ULL, 0x1000ULL | 0x40000ULL, 0x2ULL | 0x20000ULL}) + Require(chflags_nid_postfix(file.string().c_str(), system) == -1 && *__error_nid_postfix() == 1); + Require(chflags_nid_postfix(file.string().c_str(), 0x40000 | 0x20) == -1 && *__error_nid_postfix() == 45); + Require(chflags_nid_postfix(root.string().c_str(), 0x20000) == -1 && *__error_nid_postfix() == 1); + Require(chflags_nid_postfix((root / "missing").string().c_str(), 0x20000) == -1 && *__error_nid_postfix() == 2); const auto chflagsDangling = root / "dangling.link"; const auto chflagsPointing = root / "file.link"; #ifdef _WIN32 @@ -704,7 +708,10 @@ int main() { Require(fchflags_nid_postfix(flagged, ~0ULL) == -1 && *__error_nid_postfix() == 45); Require(fchflags_nid_postfix(flagged, 0x200000) == -1 && *__error_nid_postfix() == 1); Require(FchflagsThrows(flagged, 0x2, "UF_IMMUTABLE")); - Require(FchflagsThrows(flagged, 0x1000 | 0x40000, "UF_READONLY|SF_APPEND")); + for (const std::uint64_t system : {0x10000ULL, 0x20000ULL, 0x40000ULL, 0x100000ULL, 0x200000ULL, 0x1000ULL | 0x40000ULL, 0x2ULL | 0x20000ULL}) + Require(fchflags_nid_postfix(flagged, system) == -1 && *__error_nid_postfix() == 1); + Require(fchflags_nid_postfix(flagged, 0x370000) == -1 && *__error_nid_postfix() == 1); + Require(fchflags_nid_postfix(flagged, 0x40000 | 0x20) == -1 && *__error_nid_postfix() == 45); Require(close_nid_postfix(flagged) == 0); Require(fchflags_nid_postfix(flagged, 0) == -1 && *__error_nid_postfix() == 9); Require(fchflags_nid_postfix(flagged, ~0ULL) == -1 && *__error_nid_postfix() == 9); diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index b8453cc8f2..f9e627abff 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -1514,6 +1514,9 @@ int main() { Require(lchflags_nid_postfix((file + "/child").c_str(), 0) == -1 && Error() == 20); Require(lchflags_nid_postfix(file.c_str(), 0x20) == -1 && Error() == 45); Require(lchflags_nid_postfix(file.c_str(), 0x200000) == -1 && Error() == 1); + Require(lchflags_nid_postfix(file.c_str(), 0x20000) == -1 && Error() == 1); + Require(lchflags_nid_postfix(file.c_str(), 0x2 | 0x40000) == -1 && Error() == 1); + Require(lchflags_nid_postfix(file.c_str(), 0x40000 | 0x20) == -1 && Error() == 45); const auto symbolic = (root / "symbolic").string(); const int created = symlink_nid_postfix("original.bin", symbolic.c_str()); if (created == 0) { diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 003bc703af..5039328437 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -334,8 +334,8 @@ - [settimeofday](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel_sys) checks like FreeBSD 11.4's [`kern_settimeofday`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_time.c#L688-L707) for a root caller: `EINVAL` for a negative second count or a microsecond count outside 0 to 999999, before anything is stored. A valid time throws, naming it, since the host clock is never set, and its time zone is not stored, as FreeBSD stores the zone only after setting the clock. A time zone without a time is kept for the process and reported by `gettimeofday`; `sceKernelGettimezone` still reports 0 and 0, since whether the console reads the same value is not known. FreeBSD checks `PRIV_SETTIMEOFDAY` first, which a jailed process fails with `EPERM`; whether a title holds it on the console is not known. - [clock_settime](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) validates like FreeBSD 11.4's [`kern_clock_settime`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_time.c#L396-L412) for a root caller: `EFAULT` for a null time, `EINVAL` for any clock but `CLOCK_REALTIME`, a negative second count or a nanosecond count outside 0 to 999999999. A valid `CLOCK_REALTIME` request throws, naming the requested time, since the host clock is never set and no guest clock offset is kept. FreeBSD checks `PRIV_CLOCK_SETTIME` first, which a jailed process fails with `EPERM`; whether a title holds it on the console is unverified. PS5MC (PPSA99420) imports it; no call site is known - [Short sleeps ended by GPU and flip completions](../../../core/libs/prx/libkernel/Time/SleepWake.cpp) (libkernel) - a `sceKernelUsleep`, `usleep`, `nanosleep` or `sceKernelNanosleep` of 1 ms or less returns before the requested time, which FreeBSD and the console do not do: it ends once the AGC driver reports a completed GPU batch or batch completion action, or VideoOut completes a flip, so a title that polls a GPU label or the flip status between sleeps does not sleep the rest. A title that paces frames with such a sleep can wake up to 1 ms early. A thread returns early at most 8 times in a run of sleeps; the run restarts when the thread submits AGC work or did not sleep the requested time since its last sleep, so a loop that counts sleeps to time out still sleeps all but 8 in full. `APS5_NO_SLEEP_WAKE=1` keeps every sleep in full -- [chflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) follows FreeBSD 11.4's [`kern_chflagsat`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2366-L2383) and UFS's [`ufs_setattr`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/ufs/ufs/ufs_vnops.c#L509-L556) for a root caller: the path lookup reports `EFAULT`, `ENOENT` and `ENOTDIR`, a flag outside the 17 UFS stores (`VNOVAL` included) `EOPNOTSUPP`, and `SF_SNAPSHOT` `EPERM`. Neither host keeps BSD file flags, so every file is taken to have none (`stat` reports `st_flags` 0): flags 0 succeed without touching the file, and any other flag throws, naming the flags. `UF_READONLY`, `UF_HIDDEN`, `UF_SYSTEM` and `UF_ARCHIVE` are FreeBSD's names for the Windows attributes but are not mapped to them: UFS stores them without enforcing them, `FILE_ATTRIBUTE_READONLY` already carries the owner write bit of `chmod` on Windows, and Linux has no equivalent a process can set without `CAP_LINUX_IMMUTABLE`. The console's filesystem types, `EROFS` on read-only mounts and the change-time update are not modelled -- [fchflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) has the gaps of `chflags`: a valid non-zero flag throws, and the console's filesystem types, `EROFS` and the change-time update are not modelled. Character devices, the random device, other host descriptors and files read from app0.zar throw, since their console filesystems and the privilege check for devices are not modelled. +- [chflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) follows FreeBSD 11.4's `kern_chflagsat` and [`ufs_setattr`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/ufs/ufs/ufs_vnops.c#L509-L556): path errors are `EFAULT`, `ENOENT` and `ENOTDIR`; a flag outside the 17 UFS stores (`VNOVAL` included) gives `EOPNOTSUPP`. Any `SF_*` flag gives `EPERM`: the jail model agreed on [#11216](https://github.com/boykopovar/AnyPS5/pull/11216) follows [`prison_priv_check`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/kern_jail.c#L3311-L3315), which denies `PRIV_VFS_SYSFLAGS`; not measured on a console. No host keeps BSD file flags, so every file has none: flags 0 succeed, any other user flag throws. `EROFS`, the console's filesystem types and the change time are not modelled +- [fchflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) has the gaps of `chflags`: a valid non-zero user flag throws and a system flag fails with `EPERM`, and the console's filesystem types, `EROFS` and the change-time update are not modelled. Character devices, the random device, other host descriptors and files read from app0.zar throw, since their console filesystems and the privilege check for devices are not modelled. - [lchflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel_sys) has the gaps of `chflags`. As FreeBSD 11.4's [`sys_lchflags`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2357-L2363) passes `AT_SYMLINK_NOFOLLOW`, a symbolic link in the last component is the file whose flags are set instead of its target, so a dangling link is accepted. This no-follow behaviour and the console's filesystem and flag handling have not been measured on a PS5. - [getpriority](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) follows FreeBSD 11.4's [`sys_getpriority`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_resource.c#L93-L165) for the one guest process: `PRIO_PROCESS` and `PRIO_PGRP` with 0 or its own pid (the guest is taken to lead its own process group, see `kill`) and `PRIO_USER` with 0 or its user id 0 return 0, any other id fails with `ESRCH` and any other `which` with `EINVAL`, leaving errno unchanged on success. `setpriority` fails the same way. A negative nice fails with `EACCES`: `donice` needs `PRIV_SCHED_SETPRIORITY`, which `prison_priv_check` denies a jailed caller (a title is taken to run jailed, not measured; a payload process accepted -25 and -5). A `PRIO_PROCESS` set of 0 or more returns 0 without storing, so `getpriority` stays 0, as PS5 firmware 13.60 did in a payload process for sets of -25, -5, 5 and 25. A `PRIO_PGRP` or `PRIO_USER` set of 0 or more throws: unverified. What a title process reads, and the host nice of the process or of the threads `APS5_THREAD_PRIORITY=high` raises, are not reported or unverified. The `scePthread` and `sceKernel` priority calls do not go through `setpriority` (nothing in the tree calls it), so the `EACCES` reaches only a title that imports `setpriority` itself - [rtprio_thread, pthread_setconcurrency, pthread_getconcurrency](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - `RTP_SET` of a real-time (2, 10) or idle (4) class fails with `EPERM`: FreeBSD 11.4's `sys_rtprio_thread` needs `PRIV_SCHED_RTPRIO`, which `prison_priv_check` denies a jailed caller (a title is taken to run jailed, not measured; a payload process on a PS5 (firmware 12.70) answered type 2, prio 0/15/31 on the calling thread with `EINVAL`). Any other `RTP_SET` throws (sce attributes allow only `SCHED_FIFO`/`SCHED_RR`) except type 3 with 0 on a calling thread without real-time attributes; no accepted value or set by another thread's id measured. A thread without real-time attributes reports 3 with 0 (unmeasured). `RTP_LOOKUP` reports the policy's type (10 or 2) and the scheduling priority, as measured, not FreeBSD's 0-31; another function fails with `ESRCH` (2 measured), a null `rtprio` with `EFAULT`, an id of no live thread with `ESRCH` (unknown ids measured). `pthread_setconcurrency` stores 0 or more, returns `EINVAL` (not errno) below 0, as libthr does; `pthread_getconcurrency` returns it, a hint. Morrowind (`mshivam019/morrowind-ps5`) imports them. The `scePthread` and `sceKernel` priority calls do not go through `rtprio_thread` (nothing in the tree calls it), so the `EPERM` reaches only a title that imports it itself, as Morrowind does