Re: [PATCH RFC] Bluetooth: hci_core: Check HCI_UP before queuing tx_work
Krystian Kaniewski <[email protected]>
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
Replace the producer-local HCI_UP checks with lifecycle exclusion on the
shared
hdev->tx_work item. In hci_dev_close_sync(), disable tx_work
synchronously after
the successful HCI_UP transition and before drain_workqueue(). Keep it
disabled
through connection teardown, the final raw queue purge, HCI_RUNNING
clearing,
the driver close callback, and volatile flag cleanup. Balance only the close
path's disable count near the end of a successful close. Preserve the early
already-down return and the outer disable count held by
hci_unregister_dev().
Do not re-enable tx_work immediately after hci_conn_hash_flush(). A raw HCI
sender can pass its earlier HCI_UP check, resume after that early
enable, append
to raw_q, and schedule tx_work while the device is down and close is still
active. Moving enable_work() after the purge alone is also insufficient
because
enable_work() does not replay a queue attempt rejected while work was
disabled.
Such a sender could otherwise leave an skb in raw_q after the final purge.
Protect the raw and user HCI send path from its authoritative HCI_UP check
through packet validation, queue insertion, and the matching
queue_work() call
with an RCU read-side section. Release RCU on every error and success
path. In
hci_dev_close_sync(), wait for these readers after HCI_UP is clear and
tx_work
is disabled, before final queue cleanup. Keep allocation and user copying
outside the read-side section, and do not add any sleeping operation
inside it.
Preserve the established drain_workqueue() and hci_conn_hash_flush()
ordering,
normal ACL, SCO, ISO, raw and user HCI validation, existing socket
errors, skb
ownership, UAPI, and the current Fixes and syzbot provenance tags.
Update the
description to explain both the shared work exclusion interval and the
raw HCI
producer quiescence.
On 8/8/2026 11:29 PM, syzbot wrote:
> During the shutdown process of a Bluetooth device, hci_dev_close_sync() is
> called. This function clears the HCI_UP flag, flushes pending RX and TX
> works, and drains the workqueue to prevent lockdep issues during cleanup.
> After the workqueue is drained, it flushes the connections.
>
> Because the connections are flushed after the workqueue is drained, there
> is a race window where the workqueue is draining but the sockets are still
> in the BT_CONNECTED state. If a concurrent thread calls sendmsg() on an
> active L2CAP, SCO, or ISO socket during this window, the socket state is
> still considered connected. The sendmsg() call will eventually reach
> hci_send_acl(), hci_send_sco(), or hci_send_iso(), which unconditionally
> attempt to queue the transmission work (hdev->tx_work) on the draining
> workqueue. This triggers a warning in __queue_work() because non-chained
> work cannot be queued on a draining workqueue.
>
> [ cut here ]
> workqueue: cannot queue hci_tx_work on wq hci0
> WARNING: kernel/workqueue.c:2306 at __queue_work+0xd4a/0x1090
> kernel/workqueue.c:2305
> RIP: 0010:__queue_work+0xd66/0x1090 kernel/workqueue.c:2305
> Call Trace:
> <TASK>
> queue_work_on+0x106/0x1c0 kernel/workqueue.c:2452
> l2cap_chan_send+0x168a/0x22f0 net/bluetooth/l2cap_core.c:-1
> l2cap_sock_sendmsg+0x33a/0x4d0 net/bluetooth/l2cap_sock.c:1180
> sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
> __sock_sendmsg net/socket.c:790 [inline]
> ____sys_sendmsg+0x54e/0x850 net/socket.c:2684
> ___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
> __sys_sendmmsg+0x273/0x4d0 net/socket.c:2827
> __do_sys_sendmmsg net/socket.c:2854 [inline]
> __se_sys_sendmmsg net/socket.c:2851 [inline]
> __x64_sys_sendmmsg+0xa0/0xc0 net/socket.c:2851
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x15f/0x560 arch/x86/entry/syscall_64.c:94
> </TASK>
>
> To fix this issue, modify hci_send_acl(), hci_send_sco(), and
> hci_send_iso() to check if the HCI_UP flag is set on the device before
> attempting to queue the transmission work. The HCI_UP flag is cleared early
> in hci_dev_close_sync(), well before the workqueue is drained. If the flag
> is not set, the work is not queued. The skb is safely appended to the
> channel's or connection's data_q, which will be safely purged shortly after
> when hci_conn_hash_flush() executes, ensuring no memory leaks occur.
>
> Fixes: 76727c02c1e1 ("Bluetooth: Call drain_workqueue() before resetting state")
> Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
> Reported-by: [email protected]
> Closes: https://syzkaller.appspot.com/bug?extid=b6919040d9958e2fc1ae
> Link: https://syzkaller.appspot.com/ai_job?id=59121999-1293-40a6-aeeb-713e2d7dc16b
> To: <[email protected]>
> To: "Luiz Augusto von Dentz" <[email protected]>
> To: "Marcel Holtmann" <[email protected]>
> To: "Johan Hedberg" <[email protected]>
> Cc: <[email protected]>
>
> ---
> diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
> index 5ba9fe826..d9f091af3 100644
> --- a/net/bluetooth/hci_core.c
> +++ b/net/bluetooth/hci_core.c
> @@ -3242,7 +3242,8 @@ void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags)
>
> hci_queue_acl(chan, &chan->data_q, skb, flags);
>
> - queue_work(hdev->workqueue, &hdev->tx_work);
> + if (test_bit(HCI_UP, &hdev->flags))
> + queue_work(hdev->workqueue, &hdev->tx_work);
> }
>
> /* Send SCO data */
> @@ -3267,7 +3268,8 @@ void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb)
> bt_dev_dbg(hdev, "hcon %p queued %d", conn,
> skb_queue_len(&conn->data_q));
>
> - queue_work(hdev->workqueue, &hdev->tx_work);
> + if (test_bit(HCI_UP, &hdev->flags))
> + queue_work(hdev->workqueue, &hdev->tx_work);
> }
>
> /* Send ISO data */
> @@ -3338,7 +3340,8 @@ void hci_send_iso(struct hci_conn *conn, struct sk_buff *skb)
>
> hci_queue_iso(conn, &conn->data_q, skb);
>
> - queue_work(hdev->workqueue, &hdev->tx_work);
> + if (test_bit(HCI_UP, &hdev->flags))
> + queue_work(hdev->workqueue, &hdev->tx_work);
> }
>
> /* ---- HCI TX task (outgoing data) ---- */
>
>
> base-commit: 075b74841bd0065a3bda3440873c747938e69b68