mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
usbnet: fix EVENT_RX_PAUSED race in usbnet_skb_return()
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: 43daa96b16 ("usbnet: Stop RX Q on MTU change")
Signed-off-by: Ömer Mete Kaya <omermetekaya0@gmail.com>
Link: https://patch.msgid.link/20261024084538.256596-1-omermetekaya0@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
7ec720f62b
commit
67ae1b80cf
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user