From 67ae1b80cf73f592b35e4b5e5001a24d6d6c8637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Sat, 24 Oct 2026 11:42:08 +0300 Subject: [PATCH] usbnet: fix EVENT_RX_PAUSED race in usbnet_skb_return() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On SMP, usbnet_skb_return() can observe EVENT_RX_PAUSED as set on CPU0 while usbnet_resume_rx() clears the flag and drains rxq_pause on CPU1. The skb can then be queued after the drain has completed and remain stranded until the next MTU change or device stop. Synchronize the EVENT_RX_PAUSED state transition and paused-queue dequeue operations with rxq_pause.lock. In usbnet_resume_rx() splice the paused queue into a local queue under the lock, then drain the local queue lock-free. In usbnet_skb_return() use a double-check pattern under rxq_pause.lock on the slow path, keeping the normal RX fast path lock-free. Use spin_lock_irqsave() to match the lockdep class shared with other sk_buff_head locks taken from hardirq context in defer_bh(). Discovered by Sashiko, not triggered, not tested on real hardware. Fixes: 43daa96b166c ("usbnet: Stop RX Q on MTU change") Signed-off-by: Ă–mer Mete Kaya Link: https://patch.msgid.link/20261024084538.256596-1-omermetekaya0@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/usbnet.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 84f97f448b2d..97a30dd22c54 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -329,9 +329,14 @@ void usbnet_skb_return(struct usbnet *dev, struct sk_buff *skb) unsigned long flags; int status; - if (test_bit(EVENT_RX_PAUSED, &dev->flags)) { - skb_queue_tail(&dev->rxq_pause, skb); - return; + if (unlikely(test_bit(EVENT_RX_PAUSED, &dev->flags))) { + spin_lock_irqsave(&dev->rxq_pause.lock, flags); + if (test_bit(EVENT_RX_PAUSED, &dev->flags)) { + __skb_queue_tail(&dev->rxq_pause, skb); + spin_unlock_irqrestore(&dev->rxq_pause.lock, flags); + return; + } + spin_unlock_irqrestore(&dev->rxq_pause.lock, flags); } /* only update if unset to allow minidriver rx_fixup override */ @@ -698,13 +703,20 @@ EXPORT_SYMBOL_GPL(usbnet_pause_rx); void usbnet_resume_rx(struct usbnet *dev) { + struct sk_buff_head q; struct sk_buff *skb; + unsigned long flags; int num = 0; - local_bh_disable(); - clear_bit(EVENT_RX_PAUSED, &dev->flags); + __skb_queue_head_init(&q); - while ((skb = skb_dequeue(&dev->rxq_pause)) != NULL) { + local_bh_disable(); + spin_lock_irqsave(&dev->rxq_pause.lock, flags); + clear_bit(EVENT_RX_PAUSED, &dev->flags); + skb_queue_splice_init(&dev->rxq_pause, &q); + spin_unlock_irqrestore(&dev->rxq_pause.lock, flags); + + while ((skb = __skb_dequeue(&q))) { usbnet_skb_return(dev, skb); num++; }