GHSA-6pxv-r7ww-3g2q: Use-after-free via dynamically allocated message queue retained by the CAN RX filter
The canaddrxfiltermsgq() system call installs a CAN RX filter whose callback (canmsgqput() in drivers/can/cancommon.c) is handed the caller's struct kmsgq as userdata. The controller driver stores that raw pointer in its filter table and dereferences it from the RX path every time a matching frame arrives. The system call verifier zvrfycanaddrxfiltermsgq() in drivers/can/canhandlers.c validated the queue only with KSYSCALLOBJ(), which is a point-in-time check of type, initialization and caller permission and says nothing about the object's lifetime.
On a build with CONFIGUSERSPACE=y and CONFIGDYNAMICOBJECTS=y, an unprivileged user-mode thread that has been granted access to a CAN device can obtain a message queue whose lifetime it fully controls: kobjectalloc(KOBJMSGQ) followed by kmsgqallocinit(). The lifetime of such an object is driven solely by its permission bitmask, and a pointer retained by the kernel is not represented there. After registering the filter, the thread calls kobjectrelease() — or simply terminates — and unrefcheck() frees the struct kmsgq storage while the CAN driver still holds the pointer.
The next matching CAN frame causes kmsgqput() to run in supervisor context against freed memory that the attacker can have groomed with another kernel allocation: the frame payload is memcpy()d through the reused block's writeptr, and the queue's waitq (and pollevents under CONFIGPOLL) list nodes are manipulated. This yields kernel-heap corruption reachable from a user-mode thread, defeating the isolation CONFIGUSERSPACE is intended to provide; the same dangling pointer also arises benignly when an application thread exits without calling canremoverxfilter().
The fix validates the queue with KSYSCALLOBJSTATIC(), which additionally rejects objects carrying KOBJFLAGALLOC, so only statically defined queues (CANMSGQDEFINE()) may be registered from user mode. Supervisor-mode callers bypass the verifier and must themselves keep the queue valid until the filter is removed.
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Compensating control
For user-mode CAN RX message-queue filters, register only statically defined queues created with CAN_MSGQ_DEFINE(); do not use k_object_alloc(K_OBJ_MSGQ) followed by k_msgq_alloc_init().
- Operational
Call can_remove_rx_filter() before releasing the message queue or terminating the thread that owns it, and keep the queue valid until the filter is removed.
Event History
Frequently Asked Questions
Which systems are exposed to this issue?
Exposure requires a build with CONFIG_USERSPACE=y and CONFIG_DYNAMIC_OBJECTS=y. An unprivileged user-mode thread must also have been granted access to a CAN device.
What does an attacker need to do to trigger the stale pointer dereference?
The attacker needs to allocate and initialize a dynamic message queue, register it as a CAN RX filter target, and then release the queue or terminate the thread. A subsequent CAN frame matching that filter causes the driver RX path to dereference the retained pointer.