GHSA-p8r8-8mw8-3wf9: Wild pointer dereference in Zephyr BSD sockets when a TCP listening socket reports an asynchronous error
The native BSD-socket layer recorded a pending asynchronous socket error by type-punning it into struct netcontext's void userdata field (ctx->userdata = INTTOPOINTER(-status) in zsockacceptedcb(), zsockreceivedcb(), zsockconnectedcb() and zsockclosectx() in subsys/net/lib/sockets/socketsinet.c), reading it back with POINTERTOINT(). That same field is owned by the network stack for listening TCP contexts: nettcpaccept() stores the parent context pointer there and the TCP core passes it back to the registered accept callback. A failed accept therefore left a small integer (an errno value) where the stack expected a struct netcontext .
When the network interface carrying a listening TCP socket goes down, closetcpconn() in subsys/net/ip/tcp.c invokes the accept callback with -ENETDOWN and the context's userdata. In v4.3.0 the callback was not disarmed afterwards, so a second interface-down event forwarded the previously stored errno to zsockacceptedcb(), which dereferenced it as the parent context and performed several stores through it (sockseterror()'s read-modify-write of socketdata, kfifocancelwait(&parent->recvq)) — the crash described in the fix's commit message. v4.3.1 and v4.4.x carry a later change clearing conn->acceptcb after the error callback (269cb8823d3 on the v4.3 branch, 913fae5169425550f2364655298fceb79b320066 on main), which closes that repeat path; on those releases the poisoned cookie remains reachable only by a narrower race, a handshake completing alongside the interface-down still passing the stale cookie to kfifoput(&parent->acceptq, ...), and by getsockopt(SOERROR), which reads the field back unconditionally.
On v4.3.0 an application that keeps a listening TCP socket open across repeated link-down events is sufficient to reach the defect; the triggering condition is a network-interface state change, not attacker-supplied packet data, so the practical attacker is one able to force the link down repeatedly (for example an adjacent attacker disrupting a wireless link) or one with local/physical access. Because both the faulting address and the stored data are fixed small constants derived from the errno value, the outcome is a wild-pointer access leading to a kernel fatal error — a denial of service (device crash or reset) rather than an attacker-directed memory corruption.
The fix stores the pending error in a dedicated netcontext.sockerror field and converts every producer and consumer to sockseterror()/sockgeterror(), leaving userdata untouched. As a side effect it also stops getsockopt(SOERROR) — which is evaluated unconditionally — from returning the kernel address held in userdata to a userspace application.