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: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei --- 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