From c565100965590b67efdbf51e8e1601981c638602 Mon Sep 17 00:00:00 2001 From: babyhuey <7751607+babyhuey@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:15:57 -0400 Subject: [PATCH 1/2] fix(libkernel): return EFAULT from rtprio_thread and sched_rr_get_interval for a bad pointer rtprio_thread read and wrote the guest rtprio struct in place, and sched_rr_get_interval wrote the interval in place, so a non-null pointer into unmapped or read-only memory crashed the host. rtprio_thread now copies the struct in with GuestCopyIn_nid_no_patch before the thread lookup for RTP_SET, as FreeBSD's sys_rtprio_thread does, and builds the RTP_LOOKUP result locally and copies it out with GuestCopyOut_nid_no_patch. sched_rr_get_interval copies its interval out the same way. A failed copy returns EFAULT. --- core/libs/prx/libkernel/Pthread/Posix/Thread.cpp | 10 +++++++--- core/libs/prx/libkernel_sys/Export.cpp | 5 ++--- core/libs/tests/GuestRtprioThread.cpp | 10 ++++++++++ core/libs/tests/GuestSchedPriority.cpp | 8 ++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index 39aac0adc6..073455a172 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -120,6 +120,8 @@ int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* pr }; if (function != Lookup && function != Set) return fail(GuestEsrch); if (priority == nullptr) return fail(GuestEfault); + GuestRtPrio requested{}; + if (function == Set && !GuestCopyIn_nid_no_patch(&requested, priority, sizeof(requested))) return fail(GuestEfault); auto* self = scePthreadSelf(); int policy = self->schedPolicy.load(std::memory_order_relaxed); int schedPriority = self->priority.load(std::memory_order_relaxed); @@ -127,13 +129,15 @@ int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* pr if (other && !GetLiveGuestThreadSched_nid_no_patch(lwpid, policy, schedPriority)) return fail(GuestEsrch); const bool realTime = policy == GuestSchedFifo || policy == GuestSchedRr; if (function == Lookup) { - priority->type = realTime ? (policy == GuestSchedFifo ? Fifo : Realtime) : Normal; - priority->prio = realTime ? static_cast(schedPriority) : 0; + GuestRtPrio current{}; + current.type = realTime ? (policy == GuestSchedFifo ? Fifo : Realtime) : Normal; + current.prio = realTime ? static_cast(schedPriority) : 0; + if (!GuestCopyOut_nid_no_patch(priority, ¤t, sizeof(current))) return fail(GuestEfault); return 0; } if (other) NotImplemented_nid_no_patch("rtprio_thread: a thread other than the calling one"); if (realTime) NotImplemented_nid_no_patch("rtprio_thread: a guest thread's real-time class"); - if (priority->type == Normal && priority->prio == 0) return 0; + if (requested.type == Normal && requested.prio == 0) return 0; NotImplemented_nid_no_patch("rtprio_thread: a real-time priority change"); } diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 69e3b4f4db..6ab1180ccf 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -942,9 +942,8 @@ int APS5_VABI sched_rr_get_interval_nid_postfix(int pid, KernelTimespec* interva constexpr int GuestEsrch = 3; const int self = getpid_nid_postfix(); if (pid != 0 && pid != self) return static_cast(Fail(GuestEsrch)); - if (interval == nullptr) return static_cast(Fail(GuestEfault)); - interval->tv_sec = 0; - interval->tv_nsec = 94000000; + const KernelTimespec quantum{0, 94000000}; + if (interval == nullptr || !GuestCopyOut_nid_no_patch(interval, &quantum, sizeof(quantum))) return static_cast(Fail(GuestEfault)); return 0; } diff --git a/core/libs/tests/GuestRtprioThread.cpp b/core/libs/tests/GuestRtprioThread.cpp index 0d44857120..5899f85e03 100644 --- a/core/libs/tests/GuestRtprioThread.cpp +++ b/core/libs/tests/GuestRtprioThread.cpp @@ -92,6 +92,16 @@ int main() { CheckThread(GUEST_SCHED_FIFO, 256, RTP_PRIO_FIFO); Require(LookupError(2, 0, &self) == GUEST_ESRCH); Require(LookupError(RTP_LOOKUP, 0, nullptr) == GUEST_EFAULT); + auto* unmapped = reinterpret_cast(std::uintptr_t{0x10}); + Require(LookupError(RTP_LOOKUP, 0, unmapped) == GUEST_EFAULT); + Require(LookupError(RTP_SET, 0, unmapped) == GUEST_EFAULT); + void* page = nullptr; + Require(sceKernelMapFlexibleMemory(&page, 0x4000, 3, 0) == 0 && page != nullptr); + auto* readOnly = static_cast(page); + *readOnly = {7, 7}; + Require(sceKernelMprotect(page, 0x4000, 1) == 0); + Require(LookupError(RTP_LOOKUP, 0, readOnly) == GUEST_EFAULT); + Require(readOnly->type == 7 && readOnly->prio == 7); Require(LookupError(RTP_LOOKUP, 1, &self) == GUEST_ESRCH); Require(LookupError(RTP_LOOKUP, 999999, &self) == GUEST_ESRCH); bool threw = false; diff --git a/core/libs/tests/GuestSchedPriority.cpp b/core/libs/tests/GuestSchedPriority.cpp index 27f7e23a90..feca496e9b 100644 --- a/core/libs/tests/GuestSchedPriority.cpp +++ b/core/libs/tests/GuestSchedPriority.cpp @@ -54,6 +54,14 @@ int main() { Require(sched_rr_get_interval_nid_postfix(self + 1, &interval) == -1 && *__error_nid_postfix() == 3); *__error_nid_postfix() = 0; Require(sched_rr_get_interval_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(sched_rr_get_interval_nid_postfix(0, reinterpret_cast(std::uintptr_t{0x10})) == -1 && *__error_nid_postfix() == 14); + void* page = nullptr; + Require(sceKernelMapFlexibleMemory(&page, 0x4000, 3, 0) == 0 && page != nullptr); + Require(sceKernelMprotect(page, 0x4000, 1) == 0); + *__error_nid_postfix() = 0; + Require(sched_rr_get_interval_nid_postfix(0, static_cast(page)) == -1 && *__error_nid_postfix() == 14); + Require(static_cast(page)->tv_nsec == 0); for (int policy : {1, 3}) { Require(sched_get_priority_max_nid_postfix(policy) == 767); Require(sched_get_priority_min_nid_postfix(policy) == 256); From 24135ac3a515566903f52bc11ab1d79b2c9e9412 Mon Sep 17 00:00:00 2001 From: babyhuey <7751607+babyhuey@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:59:00 -0400 Subject: [PATCH 2/2] fix(libkernel): copy the rtprio_thread RTP_SET struct in after the thread lookup FreeBSD's sys_rtprio_thread returns ESRCH for an unknown lwpid before a failed copyin of the struct, so the copy-in now follows the thread lookup, and RTP_SET with an unknown lwpid and an unreadable struct reports ESRCH. Tests pin that order and sched_rr_get_interval's pid check before its copy-out. --- core/libs/prx/libkernel/Pthread/Posix/Thread.cpp | 4 ++-- core/libs/tests/GuestRtprioThread.cpp | 1 + core/libs/tests/GuestSchedPriority.cpp | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index 073455a172..a873a4e104 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -120,13 +120,13 @@ int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* pr }; if (function != Lookup && function != Set) return fail(GuestEsrch); if (priority == nullptr) return fail(GuestEfault); - GuestRtPrio requested{}; - if (function == Set && !GuestCopyIn_nid_no_patch(&requested, priority, sizeof(requested))) return fail(GuestEfault); auto* self = scePthreadSelf(); int policy = self->schedPolicy.load(std::memory_order_relaxed); int schedPriority = self->priority.load(std::memory_order_relaxed); const bool other = lwpid != 0 && static_cast(lwpid) != static_cast(self->tid); if (other && !GetLiveGuestThreadSched_nid_no_patch(lwpid, policy, schedPriority)) return fail(GuestEsrch); + GuestRtPrio requested{}; + if (function == Set && !GuestCopyIn_nid_no_patch(&requested, priority, sizeof(requested))) return fail(GuestEfault); const bool realTime = policy == GuestSchedFifo || policy == GuestSchedRr; if (function == Lookup) { GuestRtPrio current{}; diff --git a/core/libs/tests/GuestRtprioThread.cpp b/core/libs/tests/GuestRtprioThread.cpp index 5899f85e03..a9ec33d1f7 100644 --- a/core/libs/tests/GuestRtprioThread.cpp +++ b/core/libs/tests/GuestRtprioThread.cpp @@ -95,6 +95,7 @@ int main() { auto* unmapped = reinterpret_cast(std::uintptr_t{0x10}); Require(LookupError(RTP_LOOKUP, 0, unmapped) == GUEST_EFAULT); Require(LookupError(RTP_SET, 0, unmapped) == GUEST_EFAULT); + Require(LookupError(RTP_SET, 999999, unmapped) == GUEST_ESRCH); void* page = nullptr; Require(sceKernelMapFlexibleMemory(&page, 0x4000, 3, 0) == 0 && page != nullptr); auto* readOnly = static_cast(page); diff --git a/core/libs/tests/GuestSchedPriority.cpp b/core/libs/tests/GuestSchedPriority.cpp index feca496e9b..8aaec24a56 100644 --- a/core/libs/tests/GuestSchedPriority.cpp +++ b/core/libs/tests/GuestSchedPriority.cpp @@ -56,6 +56,8 @@ int main() { Require(sched_rr_get_interval_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == 14); *__error_nid_postfix() = 0; Require(sched_rr_get_interval_nid_postfix(0, reinterpret_cast(std::uintptr_t{0x10})) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(sched_rr_get_interval_nid_postfix(-1, reinterpret_cast(std::uintptr_t{0x10})) == -1 && *__error_nid_postfix() == 3); void* page = nullptr; Require(sceKernelMapFlexibleMemory(&page, 0x4000, 3, 0) == 0 && page != nullptr); Require(sceKernelMprotect(page, 0x4000, 1) == 0);