mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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 commit021a17ed79("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:021a17ed79("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>
This commit is contained in:
		
							parent
							
								
									1609d7604b
								
							
						
					
					
						commit
						d518d2ed86
					
				
					 2 changed files with 16 additions and 7 deletions
				
			
		| 
						 | 
					@ -118,6 +118,11 @@ void __qdisc_run(struct Qdisc *q);
 | 
				
			||||||
static inline void qdisc_run(struct Qdisc *q)
 | 
					static inline void qdisc_run(struct Qdisc *q)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if (qdisc_run_begin(q)) {
 | 
						if (qdisc_run_begin(q)) {
 | 
				
			||||||
 | 
							/* NOLOCK qdisc must check 'state' under the qdisc seqlock
 | 
				
			||||||
 | 
							 * to avoid racing with dev_qdisc_reset()
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							if (!(q->flags & TCQ_F_NOLOCK) ||
 | 
				
			||||||
 | 
							    likely(!test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
 | 
				
			||||||
			__qdisc_run(q);
 | 
								__qdisc_run(q);
 | 
				
			||||||
		qdisc_run_end(q);
 | 
							qdisc_run_end(q);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3467,18 +3467,22 @@ static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
 | 
				
			||||||
	qdisc_calculate_pkt_len(skb, q);
 | 
						qdisc_calculate_pkt_len(skb, q);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (q->flags & TCQ_F_NOLOCK) {
 | 
						if (q->flags & TCQ_F_NOLOCK) {
 | 
				
			||||||
		if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {
 | 
							if ((q->flags & TCQ_F_CAN_BYPASS) && q->empty &&
 | 
				
			||||||
 | 
							    qdisc_run_begin(q)) {
 | 
				
			||||||
 | 
								if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED,
 | 
				
			||||||
 | 
										      &q->state))) {
 | 
				
			||||||
				__qdisc_drop(skb, &to_free);
 | 
									__qdisc_drop(skb, &to_free);
 | 
				
			||||||
				rc = NET_XMIT_DROP;
 | 
									rc = NET_XMIT_DROP;
 | 
				
			||||||
		} else if ((q->flags & TCQ_F_CAN_BYPASS) && q->empty &&
 | 
									goto end_run;
 | 
				
			||||||
			   qdisc_run_begin(q)) {
 | 
								}
 | 
				
			||||||
			qdisc_bstats_cpu_update(q, skb);
 | 
								qdisc_bstats_cpu_update(q, skb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								rc = NET_XMIT_SUCCESS;
 | 
				
			||||||
			if (sch_direct_xmit(skb, q, dev, txq, NULL, true))
 | 
								if (sch_direct_xmit(skb, q, dev, txq, NULL, true))
 | 
				
			||||||
				__qdisc_run(q);
 | 
									__qdisc_run(q);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end_run:
 | 
				
			||||||
			qdisc_run_end(q);
 | 
								qdisc_run_end(q);
 | 
				
			||||||
			rc = NET_XMIT_SUCCESS;
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK;
 | 
								rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK;
 | 
				
			||||||
			qdisc_run(q);
 | 
								qdisc_run(q);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue