Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: sched: fix skb leak in dev_requeue_skb()

When dev_requeue_skb() is called with bulked skb list, only the
first skb of the list will be requeued to qdisc layer, and leak
the others without free them.

TCP is broken due to skb leak since no free skb will be considered
as still in the host queue and never be retransmitted. This happend
when dev_requeue_skb() called from qdisc_restart().
qdisc_restart
|-- dequeue_skb
|-- sch_direct_xmit()
|-- dev_requeue_skb() <-- skb may bluked

Fix dev_requeue_skb() to requeue the full bluked list. Also change
to use __skb_queue_tail() in __dev_requeue_skb() to avoid skb out
of order.

Fixes: a53851e2c321 ("net: sched: explicit locking in gso_cpu fallback")
Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Wei Yongjun and committed by
David S. Miller
9540d977 a9add194

+21 -8
+21 -8
net/sched/sch_generic.c
··· 112 112 113 113 static inline int __dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) 114 114 { 115 - __skb_queue_head(&q->gso_skb, skb); 116 - q->qstats.requeues++; 117 - qdisc_qstats_backlog_inc(q, skb); 118 - q->q.qlen++; /* it's still part of the queue */ 115 + while (skb) { 116 + struct sk_buff *next = skb->next; 117 + 118 + __skb_queue_tail(&q->gso_skb, skb); 119 + q->qstats.requeues++; 120 + qdisc_qstats_backlog_inc(q, skb); 121 + q->q.qlen++; /* it's still part of the queue */ 122 + 123 + skb = next; 124 + } 119 125 __netif_schedule(q); 120 126 121 127 return 0; ··· 132 126 spinlock_t *lock = qdisc_lock(q); 133 127 134 128 spin_lock(lock); 135 - __skb_queue_tail(&q->gso_skb, skb); 129 + while (skb) { 130 + struct sk_buff *next = skb->next; 131 + 132 + __skb_queue_tail(&q->gso_skb, skb); 133 + 134 + qdisc_qstats_cpu_requeues_inc(q); 135 + qdisc_qstats_cpu_backlog_inc(q, skb); 136 + qdisc_qstats_cpu_qlen_inc(q); 137 + 138 + skb = next; 139 + } 136 140 spin_unlock(lock); 137 141 138 - qdisc_qstats_cpu_requeues_inc(q); 139 - qdisc_qstats_cpu_backlog_inc(q, skb); 140 - qdisc_qstats_cpu_qlen_inc(q); 141 142 __netif_schedule(q); 142 143 143 144 return 0;