CVE-2026-19935: Use-after-free of an L2CAP CoC channel object in the Zephyr Bluetooth host: RX work item is not cancelled on channel teardown
The Bluetooth LE host queues received L2CAP connection-oriented channel (CoC) data for deferred processing through a struct kwork embedded in the channel object (lechan->rxwork, handler l2caprxprocess()) whenever the channel uses a dynamic PSM (0x0080-0x00FF). Channel teardown in l2capchandestroy() in subsys/bluetooth/host/l2cap.c cancels the retransmission-timeout work and drains the RX FIFO, but never cancels rxwork. Because that work item was submitted to the system workqueue while HCI receive processing runs on the dedicated Bluetooth RX workqueue (CONFIGBTRECVWORKQBT, the default), a queued rxwork item can outlive the channel it points into.
A remote, unauthenticated peer with an established CoC channel triggers this by sending a data K-frame immediately followed by an L2CAP Disconnect Request. The K-frame submits rxwork to the system workqueue; because both workqueue threads are cooperative and the Bluetooth RX workqueue runs at the higher priority (KPRIOCOOP(CONFIGBTRXPRIO) versus CONFIGSYSTEMWORKQUEUEPRIORITY), the pending item cannot run before the following Disconnect Request is processed in ledisconnreq() -> l2capchandel() -> l2capchandestroy(). The stack then invokes the released() callback, which the API documents as meaning the stack has dropped all references and the application may free the channel memory.
The application therefore frees or re-accepts into an object that the system workqueue still holds in its pending list. If the memory is freed and reallocated, the workqueue later dereferences a list node and a handler function pointer read from reused memory; if the object is re-used for a later connection, l2capchanadd() calls kworkinit() on a still-enqueued work item, corrupting the workqueue's pending list so that unrelated work items are dropped or the queue spins on a looped list. A related variant lets l2caprxprocess() run concurrently with teardown, racing the netbuf unref of lechan->sdu and the clearing of chan->conn.
The fix routes the channel RX work to the Bluetooth workqueue - the same context in which every teardown path runs - and adds an explicit kworkcancel() of lechan->rxwork in l2capchandestroy(), so no reference to the channel survives the released() callback. Configurations without CONFIGBTL2CAPDYNAMICCHANNEL, or that only use SIG-assigned PSMs (EATT 0x0027, OTS 0x0025) which take the inline receive path, are not affected.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Compensating control
In the Zephyr Bluetooth host, route L2CAP channel RX work to the Bluetooth workqueue, and explicitly call k_work_cancel() for le_chan->rx_work in l2cap_chan_destroy() before the channel is released.
Event History
Frequently Asked Questions
Which deployments are exposed to this issue?
The affected path is used by Bluetooth LE L2CAP connection-oriented channels that use dynamic PSMs in the 0x0080 through 0x00FF range. The described race occurs when HCI receive processing uses the dedicated Bluetooth RX workqueue via CONFIG_BT_RECV_WORKQ_BT, which is identified as the default.
What must an attacker be able to do?
An unauthenticated remote peer needs an established L2CAP CoC channel. It can trigger the condition by sending a data K-frame and immediately following it with an L2CAP Disconnect Request.
Why does the timing condition occur reliably in the described configuration?
The received-data work is queued on the system workqueue, while the disconnect is processed on the dedicated Bluetooth RX workqueue. Both are cooperative, and the Bluetooth RX workqueue has higher priority, allowing channel teardown to occur before the queued receive work runs.