GHSA-crfw-75jw-hjm3: Broken access-control denial in k_thread_join/k_thread_abort syscall validation in Zephyr kernel
The Zephyr kernel validates the kthreadjoin() and kthreadabort() system calls (declared syscall in include/zephyr/kernel.h) through threadobjvalidate() in kernel/thread.c. Its default switch branch is the access-denied path, taken when kobjectvalidate() returns -EPERM (the calling user thread was never granted access to the target thread object) or -EBADF (the supplied pointer is not a registered kernel object of the right type). That branch invoked KOOPS(KSYSCALLVERIFYMSG(ret, "access denied")), but KSYSCALLVERIFYMSG treats a true expression as success; the non-zero error code ret therefore read as "verified OK", the kernel oops was never raised, and control fell through to CODEUNREACHABLE.
Because kthreadjoin() and kthreadabort() are system calls, an unprivileged user-mode thread (under CONFIGUSERSPACE) can reach this denial path directly by calling either syscall on a thread object it does not own. Instead of the offending thread being cleanly terminated, execution reaches builtinunreachable() while running in supervisor mode inside the syscall handler.
On Clang builds CODEUNREACHABLE emits an illegal-instruction trap, so a user thread can deterministically crash the kernel — a locally triggerable denial of service that escapes the userspace sandbox. On GCC builds the path is undefined behavior: the compiler may drop the return-value handling for threadobjvalidate(), so it can return an undefined bool; if that is false, the caller proceeds into the real kthreadjoin()/kthreadabort() implementation for a thread the user was never authorized to access, an access-control bypass.
The fix changes the verification expression to ret == 0, so a denied (non-zero) result now correctly raises KOOPS and terminates the offending caller.