nfc_llcp_socket_purge() walks local->tx_queue with
skb_queue_walk_safe() and unlinks matching skbs. The walk does not
hold local->tx_queue.lock. tx_work, the LLCP send path, and other
purge or dequeue sites mutate that queue under the lock, so the
walker can follow an skb that another CPU has already freed. That
is a use-after-free and can corrupt the list.
Hold the queue lock across the walk, unlink with __skb_unlink(), and
free the detached skbs only after the lock is dropped.
Fixes: f31652a58bee ("NFC: Purge LLCP socket Tx queues when being disconnected")
Cc: <redacted>
Reported-by: Vega <redacted>
Assisted-by: LLM
Signed-off-by: Yuqi Xu <redacted>
Reviewed-by: Ren Wei <redacted>
---
net/nfc/llcp_core.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index 74bf817007cf..500c65e2a90f 100644
--- a/net/nfc/llcp_core.c
+++ b/net/nfc/llcp_core.c
@@ -47,7 +47,9 @@ void nfc_llcp_socket_remote_param_init(struct nfc_llcp_sock *sock)
static void nfc_llcp_socket_purge(struct nfc_llcp_sock *sock)
{
struct nfc_llcp_local *local = sock->local;
+ struct sk_buff_head to_free;
struct sk_buff *s, *tmp;
+ unsigned long flags;
skb_queue_purge(&sock->tx_queue);
skb_queue_purge(&sock->tx_pending_queue);@@ -55,14 +57,20 @@ static void nfc_llcp_socket_purge(struct nfc_llcp_sock *sock)
if (local == NULL)
return;
+ __skb_queue_head_init(&to_free);
+
/* Search for local pending SKBs that are related to this socket */
+ spin_lock_irqsave(&local->tx_queue.lock, flags);
skb_queue_walk_safe(&local->tx_queue, s, tmp) {
if (s->sk != &sock->sk)
continue;
- skb_unlink(s, &local->tx_queue);
- kfree_skb(s);
+ __skb_unlink(s, &local->tx_queue);
+ __skb_queue_tail(&to_free, s);
}
+ spin_unlock_irqrestore(&local->tx_queue.lock, flags);
+
+ __skb_queue_purge(&to_free);
}
static struct sock *nfc_llcp_sock_list_pop(struct llcp_sock_list *l)--
2.56.0