Skip to content

Commit d518d2e

Browse files
Paolo Abenidavem330
authored andcommitted
net/sched: fix race between deactivation and dequeue for NOLOCK qdisc
The test implemented by some_qdisc_is_busy() is somewhat loosy for NOLOCK qdisc, as we may hit the following scenario: CPU1 CPU2 // in net_tx_action() clear_bit(__QDISC_STATE_SCHED...); // in some_qdisc_is_busy() val = (qdisc_is_running(q) || test_bit(__QDISC_STATE_SCHED, &q->state)); // here val is 0 but... qdisc_run(q) // ... CPU1 is going to run the qdisc next As a conseguence qdisc_run() in net_tx_action() can race with qdisc_reset() in dev_qdisc_reset(). Such race is not possible for !NOLOCK qdisc as both the above bit operations are under the root qdisc lock(). After commit 021a17e ("pfifo_fast: drop unneeded additional lock on dequeue") the race can cause use after free and/or null ptr dereference, but the root cause is likely older. This patch addresses the issue explicitly checking for deactivation under the seqlock for NOLOCK qdisc, so that the qdisc_run() in the critical scenario becomes a no-op. Note that the enqueue() op can still execute concurrently with dev_qdisc_reset(), but that is safe due to the skb_array() locking, and we can't avoid that for NOLOCK qdiscs. Fixes: 021a17e ("pfifo_fast: drop unneeded additional lock on dequeue") Reported-by: Li Shuang <shuali@redhat.com> Reported-and-tested-by: Davide Caratti <dcaratti@redhat.com> Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent 1609d76 commit d518d2e

File tree

2 files changed

+16
-7
lines changed

2 files changed

+16
-7
lines changed

include/net/pkt_sched.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,12 @@ void __qdisc_run(struct Qdisc *q);
118118
static inline void qdisc_run(struct Qdisc *q)
119119
{
120120
if (qdisc_run_begin(q)) {
121-
__qdisc_run(q);
121+
/* NOLOCK qdisc must check 'state' under the qdisc seqlock
122+
* to avoid racing with dev_qdisc_reset()
123+
*/
124+
if (!(q->flags & TCQ_F_NOLOCK) ||
125+
likely(!test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
126+
__qdisc_run(q);
122127
qdisc_run_end(q);
123128
}
124129
}

net/core/dev.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3467,18 +3467,22 @@ static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
34673467
qdisc_calculate_pkt_len(skb, q);
34683468

34693469
if (q->flags & TCQ_F_NOLOCK) {
3470-
if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {
3471-
__qdisc_drop(skb, &to_free);
3472-
rc = NET_XMIT_DROP;
3473-
} else if ((q->flags & TCQ_F_CAN_BYPASS) && q->empty &&
3474-
qdisc_run_begin(q)) {
3470+
if ((q->flags & TCQ_F_CAN_BYPASS) && q->empty &&
3471+
qdisc_run_begin(q)) {
3472+
if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED,
3473+
&q->state))) {
3474+
__qdisc_drop(skb, &to_free);
3475+
rc = NET_XMIT_DROP;
3476+
goto end_run;
3477+
}
34753478
qdisc_bstats_cpu_update(q, skb);
34763479

3480+
rc = NET_XMIT_SUCCESS;
34773481
if (sch_direct_xmit(skb, q, dev, txq, NULL, true))
34783482
__qdisc_run(q);
34793483

3484+
end_run:
34803485
qdisc_run_end(q);
3481-
rc = NET_XMIT_SUCCESS;
34823486
} else {
34833487
rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK;
34843488
qdisc_run(q);

0 commit comments

Comments
 (0)