[PATCH RFC v2] Bluetooth: hci_sync: Fix tx_work queuing race during device close
"syzbot" <[email protected]>
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
During the shutdown process of a Bluetooth device in hci_dev_close_sync(),
tx_work can still be queued by concurrent transmission paths or raw HCI
socket operations after HCI_UP has been checked or while workqueues are
being flushed. This causes __queue_work() to trigger a warning when
attempting to queue work on a draining workqueue:
workqueue: cannot queue hci_tx_work on wq hci0
WARNING: kernel/workqueue.c:2306 at __queue_work+0xd4a/0x1090
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
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmmsg+0x273/0x4d0 net/socket.c:2827
</TASK>
To fix this, replace flush_work() with disable_work_sync() and re-enable it
with enable_work() in hci_dev_close_sync() to establish a work exclusion
interval for hdev->tx_work while the device is closing. Additionally,
ensure raw HCI socket producers in hci_sock_sendmsg() are quiesced by
holding rcu_read_lock() while checking HCI_UP, paired with
synchronize_rcu() in hci_dev_close_sync() right after disabling tx_work.
Fixes: 76727c02c1e1 ("Bluetooth: Call drain_workqueue() before resetting state")
Assisted-by: Gemini:gemini-3.6-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=bb132695-7f6d-4df7-8464-0dd1708b1661
To: <[email protected]>
To: "Luiz Augusto von Dentz" <[email protected]>
To: "Marcel Holtmann" <[email protected]>
To: "Johan Hedberg" <[email protected]>
Cc: <[email protected]>
---
v2:
- Replaced per-send HCI_UP checks in hci_core.c with disable_work_sync() and enable_work() on hdev->tx_work in hci_dev_close_sync() to establish a work exclusion interval during shutdown.
- Added rcu_read_lock() protection around the HCI_UP check in hci_sock_sendmsg() and synchronize_rcu() in hci_dev_close_sync() to quiesce raw HCI producers.
v1:
https://lore.kernel.org/all/[email protected]/T/
---
diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
index 070ca388f..8345ebbbd 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -1850,9 +1850,10 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
goto drop;
}
+ rcu_read_lock();
if (!test_bit(HCI_UP, &hdev->flags)) {
err = -ENETDOWN;
- goto drop;
+ goto drop_rcu;
}
hci_skb_pkt_type(skb) = skb->data[0];
@@ -1870,7 +1871,7 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
hci_skb_pkt_type(skb) != HCI_ISODATA_PKT &&
hci_skb_pkt_type(skb) != HCI_DRV_PKT) {
err = -EINVAL;
- goto drop;
+ goto drop_rcu;
}
skb_queue_tail(&hdev->raw_q, skb);
@@ -1885,7 +1886,7 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
&hci_sec_filter.ocf_mask[ogf])) &&
!capable(CAP_NET_RAW)) {
err = -EPERM;
- goto drop;
+ goto drop_rcu;
}
/* Since the opcode has already been extracted here, store
@@ -1908,14 +1909,14 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
} else {
if (!capable(CAP_NET_RAW)) {
err = -EPERM;
- goto drop;
+ goto drop_rcu;
}
if (hci_skb_pkt_type(skb) != HCI_ACLDATA_PKT &&
hci_skb_pkt_type(skb) != HCI_SCODATA_PKT &&
hci_skb_pkt_type(skb) != HCI_ISODATA_PKT) {
err = -EINVAL;
- goto drop;
+ goto drop_rcu;
}
skb_queue_tail(&hdev->raw_q, skb);
@@ -1923,14 +1924,16 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
}
err = len;
+ rcu_read_unlock();
+ goto done;
+drop_rcu:
+ rcu_read_unlock();
+drop:
+ kfree_skb(skb);
done:
release_sock(sk);
return err;
-
-drop:
- kfree_skb(skb);
- goto done;
}
static int hci_sock_setsockopt_old(struct socket *sock, int level, int optname,
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index c8d14128c..bad83313b 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -5472,9 +5472,11 @@ int hci_dev_close_sync(struct hci_dev *hdev)
hci_leds_update_powered(hdev, false);
/* Flush RX and TX works */
- flush_work(&hdev->tx_work);
+ disable_work_sync(&hdev->tx_work);
flush_work(&hdev->rx_work);
+ synchronize_rcu();
+
if (hdev->discov_timeout > 0) {
hdev->discov_timeout = 0;
hci_dev_clear_flag(hdev, HCI_DISCOVERABLE);
@@ -5576,6 +5578,8 @@ int hci_dev_close_sync(struct hci_dev *hdev)
bacpy(&hdev->random_addr, BDADDR_ANY);
hci_codec_list_clear(&hdev->local_codecs);
+ enable_work(&hdev->tx_work);
+
hci_dev_put(hdev);
return err;
}
base-commit: 075b74841bd0065a3bda3440873c747938e69b68
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].