CVE-2026-19936: Bluetooth Mesh delayable message queue: unsynchronized list access leads to NULL pointer dereference
The Bluetooth Mesh Access-layer delayable message queue in subsys/bluetooth/mesh/delayablemsg.c shares three intrusive lists (busyctx, freectx, freechunks) and one kworkdelayable between two cooperative threads with no synchronization: btmeshdelayablemsgmanage() runs on the Bluetooth host RX thread, while delayablemsghandler() runs on the system workqueue. Both block while the lists are in an intermediate state — the enqueuer in btrand(), both in btmeshaccesssend() — which lets the other context observe torn state. delayablemsghandler() used the result of sysslistget() on busyctx without a NULL check, and allocatedelayablemsgctx() tested sysslistisempty() and then assumed a separate sysslistget() would succeed.
Because CONFIGBTMESHACCESSDELAYABLEMSG and CONFIGBTMESHACCESSDELAYABLEMSGCTXENABLED default to y, every model response to a received mesh message is parked on this queue, so the enqueuer runs as a direct consequence of inbound traffic. A peer that can get access-layer messages accepted by a model (it must hold the NetKey and a bound AppKey, so a legitimate or compromised network member) controls the arrival rate, the size of the elicited responses and the destination address, and thereby controls occupancy of the 4-entry context pool and the 40-entry chunk pool. Exhausting the chunk pool forces btmeshdelayablemsgmanage() down the purge path, which sends and releases the only queued message and leaves busyctx empty while the delayable work item is still armed; if the enqueuer then yields, the handler runs against the empty list.
The result is a read-modify-write through a NULL-based pointer (pendingmsg->firedtime += 10) at a fixed low address on the system workqueue thread. On typical targets that faults, aborting the system workqueue thread — which stops all workqueue-driven functionality, including the mesh timing machinery — or resetting the node, so the impact is loss of availability of the affected node. The write value and address are fixed and no attacker-controlled length or index is involved, so this is not exploitable for memory corruption or code execution. The same unsynchronized window also allowed one message context to be selected and released by two contexts at once, producing duplicated transmissions, duplicated send-completion callbacks and crossed chunk ownership.
The fix serializes every list mutation with a kspinlock that is never held across a blocking call, makes pushmsgfromdelayablemsgs() detach the queue head atomically so a message being sent is on no list, removes the unchecked sysslistget() from delayablemsghandler(), and drops any message that reaches the front of the queue while the stack is suspended or reset.
Affected Software
Event History
Frequently Asked Questions
Which deployments are exposed by the default configuration?
Deployments using Bluetooth Mesh are exposed when the default CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG and CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CTX_ENABLED options are enabled. These options default to y, causing model responses to received mesh messages to use the affected queue.
What access does an attacker need to trigger the issue?
An attacker must be able to send access-layer messages that a model accepts. This requires possession of the NetKey and a bound AppKey, meaning the attacker must be a legitimate mesh member or have compromised one.
What is the likely impact of successful exploitation?
The race can cause a NULL pointer dereference in the delayable message handler. The reported impact is availability loss, consistent with a denial of service.
How can teams determine whether their device may be affected?
Check whether the device uses Bluetooth Mesh and has CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG and CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CTX_ENABLED enabled. Also determine whether the code includes the referenced fix commit 0a6da68955f454edc41e4b916e05c3b974209fe4.