GHSA-xvwx-ww79-r346: UART wide-data syscall verifiers check the 8-bit driver op, allowing a user-mode thread to trigger a NULL function-pointer call in kernel mode
The user-mode syscall verifiers for the 16-bit ("wide data") UART API in drivers/serial/uarthandlers.c validated the wrong driver operation. zvrfyuartpolloutu16(), zvrfyuarttxu16() and zvrfyuartrxenableu16() each ran KSYSCALLDRIVERUART() against the 8-bit op (pollout, tx, rxenable) rather than the 16-bit op that the corresponding implementation dereferences (polloutu16, txu16, rxenableu16). zimpluartpolloutu16(), zimpluarttxu16() and zimpluartrxenableu16() in include/zephyr/drivers/uart/uartinternal.h call their op unconditionally, with no NULL guard of their own, because the verifier is supposed to have established that the op exists.
CONFIGUARTWIDEDATA is an application-selectable option that no driver selects, and only a handful of the serial drivers in the tree implement any u16 operation; every other driver leaves those struct uartdriverapi members NULL while still providing the 8-bit ops the old check inspected. In a build with CONFIGUSERSPACE and CONFIGUARTWIDEDATA (plus CONFIGUARTASYNCAPI for the uarttxu16() and uartrxenableu16() variants), an unprivileged user-mode thread that legitimately holds access to such a UART device object can invoke the u16 syscall: the object, permission and op checks all pass, and the kernel-side implementation then performs an indirect call through a NULL function pointer while running at supervisor privilege.
The result is a CPU exception taken in kernel context instead of the intended KOOPS or -ENOSYS return. With the default ksysfatalerrorhandler() this halts the system (or warm-reboots it under CONFIGRESETONFATALERROR), so a thread confined by the userspace sandbox can deny service to the whole device — a capability the user-mode privilege boundary is specifically meant to withhold. The call target is a fixed NULL, so there is no attacker-controlled code execution and no memory disclosure; the separate KSYSCALLMEMORYARRAYREAD()/KSYSCALLMEMORYARRAYWRITE() bounds checks on the caller's buffer were and remain correct. The fix points each verifier at the matching u16 op, using a plain KSYSCALLOBJ() object check where the op does not exist or where the implementation already tests it for NULL.