diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index e53cbaef819c..55fc64db495a 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2904,8 +2904,14 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) { u8 dev_pkt_type; + /* Pairs with synchronize_rcu() in hci_dev_close_sync(), so rx_work is + * not queued once the workqueue is being drained. + */ + rcu_read_lock(); + if (!hdev || (!test_bit(HCI_UP, &hdev->flags) && !test_bit(HCI_INIT, &hdev->flags))) { + rcu_read_unlock(); kfree_skb(skb); return -ENXIO; } @@ -2941,6 +2947,7 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) case HCI_DRV_PKT: break; default: + rcu_read_unlock(); kfree_skb(skb); return -EINVAL; } @@ -2954,6 +2961,8 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) skb_queue_tail(&hdev->rx_q, skb); queue_work(hdev->workqueue, &hdev->rx_work); + rcu_read_unlock(); + return 0; } EXPORT_SYMBOL(hci_recv_frame); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c9e80b957455..93dfe2e9bb0e 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5667,6 +5667,11 @@ int hci_dev_close_sync(struct hci_dev *hdev) return err; } + /* Wait for hci_recv_frame() callers that still saw HCI_UP, so that + * none of them queues rx_work while the workqueue is drained below. + */ + synchronize_rcu(); + hci_leds_update_powered(hdev, false); /* Flush RX and TX works */