diff --git a/core/libs/prx/libkernel/File/src/SharedMemory.cpp b/core/libs/prx/libkernel/File/src/SharedMemory.cpp index 8e18c4bcb68..ca0ca0a6e35 100644 --- a/core/libs/prx/libkernel/File/src/SharedMemory.cpp +++ b/core/libs/prx/libkernel/File/src/SharedMemory.cpp @@ -640,15 +640,36 @@ int OpenNamedFile(const char* path, int flags, int mode) { int CreateAnonymousFile() { #ifdef _WIN32 static std::atomic sequence{0}; - const auto directory = std::filesystem::temp_directory_path(); - const auto seed = std::to_string(std::random_device{}()); + const auto root = std::filesystem::path(WindowsNamedRoot()); + EnsurePrivateWindowsDirectory(root.parent_path()); + EnsurePrivateWindowsDirectory(root, true); + std::random_device random; for (int attempt = 0; attempt < 64; ++attempt) { - const auto path = directory / ("anyps5-shm-" + seed + "-" + std::to_string(sequence++)); + std::vector seed; + for (int index = 0; index < 8; ++index) { + const auto value = random(); + for (int shift = 0; shift < 32; shift += 8) seed.push_back(static_cast(value >> shift)); + } + const auto process = ::GetCurrentProcessId(); + const auto serial = sequence.fetch_add(1, std::memory_order_relaxed); + const std::uint32_t identifiers[] = {static_cast(process), static_cast(serial)}; + for (const auto value : identifiers) + for (int shift = 0; shift < 32; shift += 8) seed.push_back(static_cast(value >> shift)); + const auto digest = Nid::Sha1(seed); + std::string name; + for (const auto byte : digest) { + name += "0123456789abcdef"[byte >> 4]; + name += "0123456789abcdef"[byte & 15]; + } + const auto path = root / (name + ".dat"); int descriptor = -1; const int error = _wsopen_s(&descriptor, path.c_str(), _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY | _O_TEMPORARY | _O_SHORT_LIVED | _O_NOINHERIT, _SH_DENYNO, _S_IREAD | _S_IWRITE); - if (error == 0) return descriptor; + if (error == 0) { + File::TrackSharedMemoryDescriptor_nid_no_patch(descriptor, true); + return descriptor; + } if (error != EEXIST) { errno = error; return -1; diff --git a/core/libs/tests/GuestNamedShmWindows.cpp b/core/libs/tests/GuestNamedShmWindows.cpp index 401ac8df2ef..2719e11bebf 100644 --- a/core/libs/tests/GuestNamedShmWindows.cpp +++ b/core/libs/tests/GuestNamedShmWindows.cpp @@ -111,6 +111,34 @@ void VerifyUntrackedDescriptor(int descriptor) { Require(!File::IsSharedMemoryDescriptor_nid_no_patch(untracked), "a closed untracked descriptor was classified as shared memory"); } +void VerifyAnonymousDescriptorClassification() { + const int descriptor = shm_open_nid_postfix(reinterpret_cast(1), ReadWrite, 0600); + Require(descriptor >= 0 && File::IsSharedMemoryDescriptor_nid_no_patch(descriptor), + "anonymous shared memory was not recognized"); + *__error_nid_postfix() = 0; + Require(fchflags_nid_postfix(descriptor, 0) == -1 && *__error_nid_postfix() == 22, + "fchflags did not reject an anonymous shared-memory descriptor"); + const int duplicate = dup_nid_postfix(descriptor); + Require(duplicate >= 0 && File::IsSharedMemoryDescriptor_nid_no_patch(duplicate), + "duplicating an anonymous shared-memory descriptor lost its classification"); + Require(close_nid_postfix(descriptor) == 0, "closing the original anonymous descriptor failed"); + Require(!File::IsSharedMemoryDescriptor_nid_no_patch(descriptor), + "the closed anonymous descriptor remained classified as shared memory"); + const int reusedOriginal = ::_open("NUL", _O_RDONLY | _O_BINARY); + Require(reusedOriginal == descriptor && !File::IsSharedMemoryDescriptor_nid_no_patch(reusedOriginal), + "reusing the original descriptor retained its shared-memory classification"); + Require(File::IsSharedMemoryDescriptor_nid_no_patch(duplicate), + "closing the original anonymous descriptor invalidated its duplicate"); + Require(close_nid_postfix(duplicate) == 0, "closing the anonymous descriptor duplicate failed"); + Require(!File::IsSharedMemoryDescriptor_nid_no_patch(duplicate), + "the closed anonymous descriptor duplicate remained classified as shared memory"); + const int reusedDuplicate = ::_open("NUL", _O_RDONLY | _O_BINARY); + Require(reusedDuplicate == duplicate && !File::IsSharedMemoryDescriptor_nid_no_patch(reusedDuplicate), + "reusing the duplicate descriptor retained its shared-memory classification"); + Require(close_nid_postfix(reusedOriginal) == 0, "closing the reused original descriptor failed"); + Require(close_nid_postfix(reusedDuplicate) == 0, "closing the reused duplicate descriptor failed"); +} + void VerifyConcurrentDescriptorReuse(const std::string& name) { std::barrier start(3); std::vector okay(2, 1); @@ -213,6 +241,7 @@ void VerifyResizeReadPosition(const std::string& name) { } void Run() { + VerifyAnonymousDescriptorClassification(); const std::string base = "/anyps5-windows-named-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(::GetTickCount64()); const std::string name = base + "-lifecycle"; Require(shm_unlink_nid_postfix(name.c_str()) == -1 && *__error_nid_postfix() == 2, diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 4968f46ca45..df5a42dacaf 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -307,7 +307,7 @@ - [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. On Windows, anonymous shared memory objects are temporary files that are not told apart from regular files, so they are treated as files instead of reporting `EINVAL` as FreeBSD 11.4's [`getvnode`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L3850-L3877) does. +- [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. - [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 only 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; `PRIO_PROCESS` 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 valid `PRIO_PGRP` or `PRIO_USER` set 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 - [rtprio_thread, pthread_setconcurrency, pthread_getconcurrency](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - `RTP_SET` throws for a real-time thread (sce attributes allow only `SCHED_FIFO`/`SCHED_RR`) and for another thread: a payload process on a PS5 (firmware 12.70) answered type 2, prio 0/15/31 on the calling thread with `EINVAL`; no accepted value or set by another thread's id measured. A non-real-time thread (POSIX attributes store `SCHED_OTHER`), unmeasured, reports 3 with 0, and `RTP_SET` accepts only that. `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