Merge pull request #6244 from xXPBSXx/fix/pthread-destroyed-einval

fix(libkernel): return EINVAL for pthread mutex and cond operations on a destroyed object
This commit is contained in:
Adrià authored and GitHub committed 2026-10-18 23:27:09 +02:00
commit 5768b25925
4 files changed
+79 -6

No files matched your search

+10
View File
@@ -518,6 +518,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_pthread_mutexattr PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_pthread_destroyed_objects_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadDestroyedObjects.cpp)
target_include_directories(guest_pthread_destroyed_objects_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_pthread_destroyed_objects_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_pthread_destroyed_objects_tests)
add_test(NAME guest_pthread_destroyed_objects COMMAND guest_pthread_destroyed_objects_tests)
set_tests_properties(guest_pthread_destroyed_objects PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_pthread_destroyed_objects PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_pthread_mutex_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadMutexErrors.cpp)
target_include_directories(guest_pthread_mutex_errors_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_pthread_mutex_errors_tests PRIVATE libkernel libc)
+18 -5
View File
@@ -40,7 +40,7 @@ std::shared_ptr<TimedWait::Condition> resolveCond(PthreadCond* cond, int* clockI
std::atomic_ref<PthreadCond> slot(*cond);
auto current = slot.load(std::memory_order_acquire);
if (current == destroyedCond())
throw std::runtime_error("Condition variable has been destroyed");
return nullptr;
if (!current) {
current = new PthreadCondPrivate();
slot.store(current, std::memory_order_release);
@@ -84,6 +84,8 @@ int finishWait(std::shared_ptr<TimedWait::Condition>& condition, bool timedOut)
}
int waitUntil(std::shared_ptr<TimedWait::Condition> condition, PthreadMutex* mutex, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
if (!condition)
return SCE_KERNEL_ERROR_EINVAL;
PthreadMutex m = nullptr;
if (const int error = mutexOwnership(mutex, &m))
return error;
@@ -124,6 +126,8 @@ int CondOperations::AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, co
KernelUseconds usec = 0;
int clockId = 0;
auto condition = resolveCond(cond, &clockId);
if (!condition)
return SCE_KERNEL_ERROR_EINVAL;
if (!PosixThread::RelativeMicroseconds(clockId, abstime, &usec))
throw std::invalid_argument("Invalid absolute condition variable timeout");
return waitUntil(std::move(condition), mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0));
@@ -167,25 +171,34 @@ int APS5_VABI scePthreadCondDestroy(PthreadCond* cond) {
throw std::invalid_argument("Condition variable pointer is null");
std::lock_guard lock(condInitializationMutex);
if (*cond == destroyedCond())
throw std::runtime_error("Condition variable has already been destroyed");
return SCE_KERNEL_ERROR_EINVAL;
delete *cond;
std::atomic_ref<PthreadCond>(*cond).store(destroyedCond(), std::memory_order_release);
return 0;
}
int APS5_VABI scePthreadCondSignal(PthreadCond* cond) {
resolveCond(cond)->NotifyOne();
const auto resolved = resolveCond(cond);
if (!resolved)
return SCE_KERNEL_ERROR_EINVAL;
resolved->NotifyOne();
return 0;
}
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond) {
resolveCond(cond)->NotifyAll();
const auto resolved = resolveCond(cond);
if (!resolved)
return SCE_KERNEL_ERROR_EINVAL;
resolved->NotifyAll();
return 0;
}
int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread) {
(void)thread;
resolveCond(cond)->NotifyAll();
const auto resolved = resolveCond(cond);
if (!resolved)
return SCE_KERNEL_ERROR_EINVAL;
resolved->NotifyAll();
return 0;
}
@@ -39,7 +39,7 @@ PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) {
std::lock_guard lock(initializationMutex);
const auto current = slot.load(std::memory_order_acquire);
if (current == destroyedMutex())
throw std::runtime_error("Mutex has been destroyed");
return nullptr;
if (current && current != adaptiveInitializer())
return current;
if (!initialize)
@@ -53,6 +53,8 @@ PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) {
template<typename TAcquire>
int acquireMutex(PthreadMutex mutex, TAcquire acquire, int unavailable, bool tryOnly) {
if (!mutex)
return SCE_KERNEL_ERROR_EINVAL;
const auto thread = std::this_thread::get_id();
const bool owned = mutex->_owner.load(std::memory_order_acquire) == thread;
if (owned && mutex->_type != MutexType::Recursive) {
@@ -188,6 +190,8 @@ int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) {
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) {
auto* current = resolveMutex(mutex, false);
if (!current)
return SCE_KERNEL_ERROR_EINVAL;
if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
return sceNotPermitted;
bool released = true;
@@ -0,0 +1,46 @@
#include "SceTypes.hpp"
#include <cstdint>
#include <cstdlib>
extern "C" {
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name);
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex);
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond);
int APS5_VABI scePthreadCondSignal(PthreadCond* cond);
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond);
int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex);
int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex);
int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond);
int APS5_VABI pthread_cond_destroy_nid_postfix(PthreadCond* cond);
}
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016u);
static constexpr int POSIX_EINVAL = 22;
static void Require(bool value) { if (!value) std::abort(); }
int main() {
PthreadMutex mutex = nullptr;
Require(scePthreadMutexInit(&mutex, nullptr, "destroyed") == SCE_OK);
Require(scePthreadMutexDestroy(&mutex) == SCE_OK);
Require(scePthreadMutexLock(&mutex) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadMutexTrylock(&mutex) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadMutexUnlock(&mutex) == SCE_KERNEL_ERROR_EINVAL);
Require(pthread_mutex_lock_nid_postfix(&mutex) == POSIX_EINVAL);
Require(mutex == reinterpret_cast<PthreadMutex>(std::uintptr_t{2}));
PthreadCond cond = nullptr;
Require(scePthreadCondInit(&cond, nullptr, "destroyed") == SCE_OK);
Require(scePthreadCondDestroy(&cond) == SCE_OK);
Require(scePthreadCondSignal(&cond) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadCondBroadcast(&cond) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadCondWait(&cond, &mutex) == SCE_KERNEL_ERROR_EINVAL);
Require(pthread_cond_signal_nid_postfix(&cond) == POSIX_EINVAL);
Require(scePthreadCondDestroy(&cond) == SCE_KERNEL_ERROR_EINVAL);
Require(pthread_cond_destroy_nid_postfix(&cond) == POSIX_EINVAL);
}