nfc: nci: fix double completion race in nci_data_exchange_complete

nci_close_device() and nci_rx_work can both call
nci_data_exchange_complete() concurrently.  After commit 4527025d44
("nfc: nci: fix circular locking dependency in nci_close_device") moved
flush_workqueue(ndev->rx_wq) after mutex_unlock(&ndev->req_lock),
rx_work is no longer serialized with the explicit completion call in the
close path.  Both callers read the non-NULL callback pointer and invoke
rawsock_data_exchange_complete(), which calls sock_put() -- but only one
sock_hold() was taken, so the second sock_put() underflows the refcount
and frees the socket while it is still in use.

Replace the bare clear_bit(NCI_DATA_EXCHANGE) with
test_and_clear_bit() so that only the first caller proceeds to invoke
the callback.

Fixes: 4527025d44 ("nfc: nci: fix circular locking dependency in nci_close_device")
Signed-off-by: Zhenghang Xiao <kipreyyy@gmail.com>
Link: https://patch.msgid.link/20260526103121.47957-1-kipreyyy@gmail.com
Signed-off-by: David Heidelberg <david@ixit.cz>
This commit is contained in:
Zhenghang Xiao 2026-05-26 18:31:21 +08:00 committed by David Heidelberg
parent 3681252705
commit 8265a626cc
No known key found for this signature in database
GPG Key ID: 60023FC4D3492072

View File

@ -46,11 +46,11 @@ void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb,
timer_delete_sync(&ndev->data_timer);
clear_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags);
/* Mark the exchange as done before calling the callback.
* The callback (e.g. rawsock_data_exchange_complete) may
* want to immediately queue another data exchange.
*/
clear_bit(NCI_DATA_EXCHANGE, &ndev->flags);
/* Claim completion atomically -- both close and rx_work may race here */
if (!test_and_clear_bit(NCI_DATA_EXCHANGE, &ndev->flags)) {
kfree_skb(skb);
return;
}
if (cb) {
/* forward skb to nfc core */