CVE-2026-12631: 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.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Configuration
Apply the fix in Zephyr kernel so that the syscall handler’s verification expression checks `thread_obj_validate()` with `ret == 0`; this ensures that access-denied results from `k_thread_join()`/`k_thread_abort()` (non-zero `-EPERM`/`-EBADF`) correctly trigger `K_OOPS(K_SYSCALL_VERIFY_MSG(...))` instead of falling through to `__builtin_unreachable()`.
Zephyr kernel (syscall verification for k_thread_join/k_thread_abort) thread_obj_validate() syscall verification condition = ret == 0
Event History
Frequently Asked Questions
Which deployments are exposed?
Systems using CONFIG_USERSPACE are exposed when an unprivileged user-mode thread can invoke k_thread_join() or k_thread_abort(). The problematic path is reached when that thread targets a thread object for which it was never granted access, or supplies a pointer that is not a registered thread object.
What access does an attacker need to trigger the issue?
An attacker needs the ability to run an unprivileged user-mode thread and call either affected syscall. No user interaction is required, and the supplied target only needs to fail the syscall's thread-object access or type validation.
What happens when the invalid access is attempted?
On the affected denial path, the expected termination of the offending thread does not occur before execution reaches CODE_UNREACHABLE in supervisor-mode syscall handling. On Clang builds, CODE_UNREACHABLE emits an illegal-instruction trap.