diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 4d6c818cfbb4..5ed3b0f887e9 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -738,6 +738,26 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_join_detach PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_handle_after_exit_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadHandleAfterExit.cpp) + target_include_directories(guest_pthread_handle_after_exit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_handle_after_exit_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_handle_after_exit_tests) + add_test(NAME guest_pthread_handle_after_exit COMMAND guest_pthread_handle_after_exit_tests) + set_tests_properties(guest_pthread_handle_after_exit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_handle_after_exit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_pthread_detach_attr_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadDetachAttr.cpp) + target_include_directories(guest_pthread_detach_attr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_detach_attr_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_detach_attr_tests) + add_test(NAME guest_pthread_detach_attr COMMAND guest_pthread_detach_attr_tests) + set_tests_properties(guest_pthread_detach_attr PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_detach_attr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_pthread_attr_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadAttrErrors.cpp) target_include_directories(guest_pthread_attr_errors_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_attr_errors_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index d7a80424cbe3..b535fdb99670 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -160,7 +160,7 @@ struct PthreadPrivate { std::string name; std::atomic _finished; void* _retval; - bool _detached; + std::atomic _detached; bool _adopted; Pthread* _handleStorage; std::atomic _joined; diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 62dc543ec93b..ded8dc80d9c9 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -554,6 +554,15 @@ std::unique_lock LockLiveGuestThread(const PthreadPrivate* thread) { return {}; } +static std::unique_lock LockRegisteredGuestThread(const PthreadPrivate* thread) { + auto& registry = CpuClocks(); + std::unique_lock lock(registry.lock); + for (const auto& [id, registered] : registry.threads) { + if (registered == thread) return lock; + } + return {}; +} + bool GuestThreadIsLive(const PthreadPrivate* thread) { return LockLiveGuestThread(thread).owns_lock(); } @@ -1272,12 +1281,16 @@ KernelCpumask APS5_VABI sceKernelGetAvailableCpumask(void) { int APS5_VABI scePthreadGetaffinity(Pthread thread, KernelCpumask* mask) { if (!thread || !mask) return SCE_KERNEL_ERROR_EINVAL; + const auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; *mask = thread->affinity.load(std::memory_order_relaxed); return SCE_OK; } int APS5_VABI scePthreadGetname(Pthread thread, char* name) { if (!thread || !name) return SCE_KERNEL_ERROR_EINVAL; + const auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(thread->nameLock); const std::size_t length = std::min(thread->name.size(), THREAD_NAME_CAPACITY - 1); std::memcpy(name, thread->name.data(), length); @@ -1287,6 +1300,8 @@ int APS5_VABI scePthreadGetname(Pthread thread, char* name) { int APS5_VABI scePthreadGetprio(Pthread thread, int* prio) { if (!thread || !prio) return SCE_KERNEL_ERROR_EINVAL; + const auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; *prio = thread->priority.load(std::memory_order_relaxed); return SCE_OK; } @@ -1297,6 +1312,8 @@ int APS5_VABI scePthreadGetthreadid(void) { int APS5_VABI scePthreadRename(Pthread thread, const char* name) { if (!thread || !name) return SCE_KERNEL_ERROR_EINVAL; + const auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(thread->nameLock); thread->name = name; #ifdef _WIN32 @@ -1309,6 +1326,8 @@ int APS5_VABI scePthreadRename(Pthread thread, const char* name) { int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask) { if (!thread || mask == 0) return SCE_KERNEL_ERROR_EINVAL; + const auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; thread->affinity.store(mask, std::memory_order_relaxed); return SCE_OK; } diff --git a/core/libs/tests/GuestPthreadDetachAttr.cpp b/core/libs/tests/GuestPthreadDetachAttr.cpp new file mode 100644 index 000000000000..b84d74dffd95 --- /dev/null +++ b/core/libs/tests/GuestPthreadDetachAttr.cpp @@ -0,0 +1,57 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; +static constexpr int ROUNDS = 50; +static constexpr int DETACHED = 1; + +static void Require(bool condition) { if (!condition) std::abort(); } + +struct Worker { + std::atomic release{false}; + std::atomic finished{false}; +}; + +static void* APS5_VABI Wait(void* opaque) { + auto* worker = static_cast(opaque); + while (!worker->release.load()) std::this_thread::yield(); + worker->finished.store(true); + return nullptr; +} + +int main() { + for (int round = 0; round < ROUNDS; ++round) { + Worker worker; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Wait, &worker, "detach_attr") == SCE_OK); + std::atomic observing{true}; + std::atomic started{false}; + std::thread observer([&] { + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + while (observing.load()) { + Require(scePthreadAttrGet(thread, &attr) == SCE_OK); + started.store(true); + } + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + }); + while (!started.load()) std::this_thread::yield(); + Require(scePthreadDetach(thread) == SCE_OK); + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrGet(thread, &attr) == SCE_OK); + int state = -1; + Require(scePthreadAttrGetdetachstate(&attr, &state) == SCE_OK && state == DETACHED); + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + observing.store(false); + observer.join(); + worker.release.store(true); + while (!worker.finished.load()) std::this_thread::yield(); + } + return 0; +} diff --git a/core/libs/tests/GuestPthreadHandleAfterExit.cpp b/core/libs/tests/GuestPthreadHandleAfterExit.cpp new file mode 100644 index 000000000000..411680441fb9 --- /dev/null +++ b/core/libs/tests/GuestPthreadHandleAfterExit.cpp @@ -0,0 +1,71 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; + +static void Require(bool condition) { if (!condition) std::abort(); } + +struct Worker { + std::atomic started{false}; + std::atomic release{false}; +}; + +static void* APS5_VABI Wait(void* opaque) { + auto* worker = static_cast(opaque); + worker->started.store(true); + while (!worker->release.load()) std::this_thread::yield(); + return nullptr; +} + +static void RequireAlive(Pthread thread) { + char name[32]; + int priority = 0; + KernelCpumask mask = 0; + Require(scePthreadGetname(thread, name) == SCE_OK); + Require(scePthreadGetprio(thread, &priority) == SCE_OK); + Require(scePthreadGetaffinity(thread, &mask) == SCE_OK && mask != 0); + Require(scePthreadSetaffinity(thread, mask) == SCE_OK); + Require(scePthreadRename(thread, "renamed") == SCE_OK); +} + +static void RequireEnded(Pthread thread) { + char name[32]; + int priority = 0; + KernelCpumask mask = 0; + Require(scePthreadGetname(thread, name) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadGetprio(thread, &priority) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadGetaffinity(thread, &mask) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadSetaffinity(thread, 1) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadRename(thread, "renamed") == SCE_KERNEL_ERROR_ESRCH); +} + +static void WaitUntilReleased(Pthread thread) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + int priority = 0; + while (scePthreadGetprio(thread, &priority) != SCE_KERNEL_ERROR_ESRCH) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::yield(); + } +} + +int main() { + RequireAlive(scePthreadSelf()); + + for (int round = 0; round < 20; ++round) { + Worker detached; + Pthread detachedThread = nullptr; + Require(scePthreadCreate(&detachedThread, nullptr, Wait, &detached, "detached") == SCE_OK); + while (!detached.started.load()) std::this_thread::yield(); + RequireAlive(detachedThread); + Require(scePthreadDetach(detachedThread) == SCE_OK); + detached.release.store(true); + WaitUntilReleased(detachedThread); + RequireEnded(detachedThread); + } + return 0; +} diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index dd4a9f498faa..718555f2b9b0 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -101,7 +101,7 @@ - [__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, setgid, setreuid, setregid, setresuid, setresgid, 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_setgid`, `sys_setreuid`, `sys_setregid`, `sys_setresuid` and `sys_setresgid`, id 0 (or -1 for an unchanged `setreuid`/`setregid`/`setresuid`/`setresgid` 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_getname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - a thread that has already been joined returns `ESRCH` (3) and its output buffer is left untouched, as measured on a PS5 (firmware 13.60) by the author of this entry; since [#4481](https://github.com/boykopovar/AnyPS5/pull/4481) the joined thread's object is retained, so the posix wrapper answers `ESRCH` for it instead of reading the retained name. A null name buffer is answered with `EFAULT` before that, an order the console table does not measure. The SCE sibling `scePthreadGetname` has no measured joined-thread behaviour on the console and still reads the retained object's name, so it is unchanged, as is the pre-existing dangling case of a thread that ended detached (its object is released at exit; see the scePthreadJoin entry) +- [pthread_getname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - a thread that has already been joined returns `ESRCH` (3) and its output buffer is left untouched, as measured on a PS5 (firmware 13.60) by the author of this entry; since [#4481](https://github.com/boykopovar/AnyPS5/pull/4481) the joined thread's object is retained, so the posix wrapper answers `ESRCH` for it instead of reading the retained name. A null name buffer is answered with `EFAULT` before that, an order the console table does not measure. The SCE sibling `scePthreadGetname` has no measured joined-thread behaviour on the console and still reads the retained object's name. After a detached thread is released, it now returns `ESRCH` through the thread registry instead of reading freed storage; that released-handle answer is not measured on the console (see the scePthreadJoin entry) - [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. In PPSA12544 the call at 0x16a5889 is followed by a log of both values and a comparison of the second with 1, and when it equals 1 a byte of the title is set; a second call (0x67e404) is made from a function that passes its caller's arguments through, and neither reads the result. - [sceCoredumpGetStopInfoCpu](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) has no active coredump context. Native null/size validation is recovered, including both 8-byte and 16-byte records; valid requests return `NOT_IN_COREDUMP_HANDLER` without accessing the output. Active-handler data retrieval remains unimplemented. One title (PPSA02225) disassembled for this entry calls it once (arguments set up at 0x8d67dd and 0x8d67e1) with a stack buffer and the length 0x10: for 0x81180005 it branches away, for 0 it reads a 32-bit code at offset 8 of the buffer, and for any other nonzero result, which includes the `NOT_IN_COREDUMP_HANDLER` result returned here, it formats an error text containing the code. The call does not show what the console returns outside a coredump handler. - [sceCoredumpGetThreadContextInfo](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) validates a nonnull 144-byte output using the native checks. Active-handler register capture remains unimplemented; valid requests return `NOT_IN_COREDUMP_HANDLER` without accessing the output.