netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [Patch net 0/2] net_sched: fix a regression in sch_htb
@ 2025-04-28 23:29 Cong Wang
  2025-04-28 23:29 ` [Patch net 1/2] sch_htb: make htb_deactivate() idempotent Cong Wang
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Cong Wang @ 2025-04-28 23:29 UTC (permalink / raw)
  To: netdev; +Cc: jhs, jiri, alan, Cong Wang

This patchset contains a fix for the regression reported by Alan and a
selftest to cover that case. Please see each patch description for more
details.

---

Cong Wang (2):
  sch_htb: make htb_deactivate() idempotent
  selftests/tc-testing: Add a test case to cover basic HTB+FQ_CODEL case

 net/sched/sch_htb.c                           | 15 ++++----
 .../tc-testing/tc-tests/infra/qdiscs.json     | 35 +++++++++++++++++++
 2 files changed, 41 insertions(+), 9 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Patch net 1/2] sch_htb: make htb_deactivate() idempotent
  2025-04-28 23:29 [Patch net 0/2] net_sched: fix a regression in sch_htb Cong Wang
@ 2025-04-28 23:29 ` Cong Wang
  2025-04-30 15:19   ` Victor Nogueira
  2025-04-28 23:29 ` [Patch net 2/2] selftests/tc-testing: Add a test case to cover basic HTB+FQ_CODEL case Cong Wang
  2025-05-05 23:51 ` [Patch net 0/2] net_sched: fix a regression in sch_htb patchwork-bot+netdevbpf
  2 siblings, 1 reply; 8+ messages in thread
From: Cong Wang @ 2025-04-28 23:29 UTC (permalink / raw)
  To: netdev; +Cc: jhs, jiri, alan, Cong Wang

Alan reported a NULL pointer dereference in htb_next_rb_node()
after we made htb_qlen_notify() idempotent.

It turns out in the following case it introduced some regression:

htb_dequeue_tree():
  |-> fq_codel_dequeue()
    |-> qdisc_tree_reduce_backlog()
      |-> htb_qlen_notify()
        |-> htb_deactivate()
  |-> htb_next_rb_node()
  |-> htb_deactivate()

For htb_next_rb_node(), after calling the 1st htb_deactivate(), the
clprio[prio]->ptr could be already set to  NULL, which means
htb_next_rb_node() is vulnerable here.

For htb_deactivate(), although we checked qlen before calling it, in
case of qlen==0 after qdisc_tree_reduce_backlog(), we may call it again
which triggers the warning inside.

To fix the issues here, we need to:

1) Make htb_deactivate() idempotent, that is, simply return if we
   already call it before.
2) Make htb_next_rb_node() safe against ptr==NULL.

Many thanks to Alan for testing and for the reproducer.

Fixes: 5ba8b837b522 ("sch_htb: make htb_qlen_notify() idempotent")
Reported-by: Alan J. Wylie <alan@wylie.me.uk>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
---
 net/sched/sch_htb.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c
index 4b9a639b642e..14bf71f57057 100644
--- a/net/sched/sch_htb.c
+++ b/net/sched/sch_htb.c
@@ -348,7 +348,8 @@ static void htb_add_to_wait_tree(struct htb_sched *q,
  */
 static inline void htb_next_rb_node(struct rb_node **n)
 {
-	*n = rb_next(*n);
+	if (*n)
+		*n = rb_next(*n);
 }
 
 /**
@@ -609,8 +610,8 @@ static inline void htb_activate(struct htb_sched *q, struct htb_class *cl)
  */
 static inline void htb_deactivate(struct htb_sched *q, struct htb_class *cl)
 {
-	WARN_ON(!cl->prio_activity);
-
+	if (!cl->prio_activity)
+		return;
 	htb_deactivate_prios(q, cl);
 	cl->prio_activity = 0;
 }
@@ -1485,8 +1486,6 @@ static void htb_qlen_notify(struct Qdisc *sch, unsigned long arg)
 {
 	struct htb_class *cl = (struct htb_class *)arg;
 
-	if (!cl->prio_activity)
-		return;
 	htb_deactivate(qdisc_priv(sch), cl);
 }
 
@@ -1740,8 +1739,7 @@ static int htb_delete(struct Qdisc *sch, unsigned long arg,
 	if (cl->parent)
 		cl->parent->children--;
 
-	if (cl->prio_activity)
-		htb_deactivate(q, cl);
+	htb_deactivate(q, cl);
 
 	if (cl->cmode != HTB_CAN_SEND)
 		htb_safe_rb_erase(&cl->pq_node,
@@ -1949,8 +1947,7 @@ static int htb_change_class(struct Qdisc *sch, u32 classid,
 			/* turn parent into inner node */
 			qdisc_purge_queue(parent->leaf.q);
 			parent_qdisc = parent->leaf.q;
-			if (parent->prio_activity)
-				htb_deactivate(q, parent);
+			htb_deactivate(q, parent);
 
 			/* remove from evt list because of level change */
 			if (parent->cmode != HTB_CAN_SEND) {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [Patch net 2/2] selftests/tc-testing: Add a test case to cover basic HTB+FQ_CODEL case
  2025-04-28 23:29 [Patch net 0/2] net_sched: fix a regression in sch_htb Cong Wang
  2025-04-28 23:29 ` [Patch net 1/2] sch_htb: make htb_deactivate() idempotent Cong Wang
@ 2025-04-28 23:29 ` Cong Wang
  2025-05-05 23:51 ` [Patch net 0/2] net_sched: fix a regression in sch_htb patchwork-bot+netdevbpf
  2 siblings, 0 replies; 8+ messages in thread
From: Cong Wang @ 2025-04-28 23:29 UTC (permalink / raw)
  To: netdev; +Cc: jhs, jiri, alan, Cong Wang

Integrate the reproducer from Alan into TC selftests and use scapy to
generate TCP traffic instead of relying on ping command.

Cc: Alan J. Wylie <alan@wylie.me.uk>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
---
 .../tc-testing/tc-tests/infra/qdiscs.json     | 35 +++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
index e26bbc169783..5a8c21735b3a 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
@@ -352,5 +352,40 @@
             "$TC qdisc del dev $DUMMY handle 1:0 root",
             "$IP addr del 10.10.10.10/24 dev $DUMMY || true"
         ]
+    },
+    {
+        "id": "62c4",
+        "name": "Test HTB with FQ_CODEL - basic functionality",
+        "category": [
+            "qdisc",
+            "htb",
+            "fq_codel"
+        ],
+        "plugins": {
+            "requires": [
+                "nsPlugin",
+                "scapyPlugin"
+            ]
+        },
+        "setup": [
+            "$TC qdisc add dev $DEV1 root handle 1: htb default 11",
+            "$TC class add dev $DEV1 parent 1: classid 1:1 htb rate 10kbit",
+            "$TC class add dev $DEV1 parent 1:1 classid 1:11 htb rate 10kbit prio 0 quantum 1486",
+            "$TC qdisc add dev $DEV1 parent 1:11 fq_codel quantum 300 noecn",
+            "sleep 0.5"
+        ],
+        "scapy": {
+            "iface": "$DEV0",
+            "count": 5,
+            "packet": "Ether()/IP(dst='10.10.10.1', src='10.10.10.10')/TCP(sport=12345, dport=80)"
+        },
+        "cmdUnderTest": "$TC -s qdisc show dev $DEV1",
+        "expExitCode": "0",
+        "verifyCmd": "$TC -s qdisc show dev $DEV1 | grep -A 5 'qdisc fq_codel'",
+        "matchPattern": "Sent [0-9]+ bytes [0-9]+ pkt",
+        "matchCount": "1",
+        "teardown": [
+            "$TC qdisc del dev $DEV1 handle 1: root"
+        ]
     }
 ]
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [Patch net 1/2] sch_htb: make htb_deactivate() idempotent
  2025-04-28 23:29 ` [Patch net 1/2] sch_htb: make htb_deactivate() idempotent Cong Wang
@ 2025-04-30 15:19   ` Victor Nogueira
  2025-05-02  9:59     ` Paolo Abeni
  2025-05-04 20:18     ` Cong Wang
  0 siblings, 2 replies; 8+ messages in thread
From: Victor Nogueira @ 2025-04-30 15:19 UTC (permalink / raw)
  To: Cong Wang, netdev; +Cc: jhs, jiri, alan

On 4/28/25 20:29, Cong Wang wrote:
> Alan reported a NULL pointer dereference in htb_next_rb_node()
> after we made htb_qlen_notify() idempotent.
> 
> It turns out in the following case it introduced some regression:
> 
> htb_dequeue_tree():
>    |-> fq_codel_dequeue()
>      |-> qdisc_tree_reduce_backlog()
>        |-> htb_qlen_notify()
>          |-> htb_deactivate()
>    |-> htb_next_rb_node()
>    |-> htb_deactivate()
> 
> For htb_next_rb_node(), after calling the 1st htb_deactivate(), the
> clprio[prio]->ptr could be already set to  NULL, which means
> htb_next_rb_node() is vulnerable here.

If I'm not missing something, the issue seems to be that
fq_codel_dequeue or codel_qdisc_dequeue may call qdisc_tree_reduce_backlog
with sch->q.qlen == 0 after commit 342debc12183. This will cause
htb_qlen_notify to be called which will deactivate before we
call htb_next_rb_node further down in htb_dequeue_tree (as you
said above).

If that's so, couldn't we instead of doing:

> @@ -348,7 +348,8 @@ static void htb_add_to_wait_tree(struct htb_sched *q,
>    */
>   static inline void htb_next_rb_node(struct rb_node **n)
>   {
> -	*n = rb_next(*n);
> +	if (*n)
> +		*n = rb_next(*n);
>   }

do something like:

@@ -921,7 +921,9 @@ static struct sk_buff *htb_dequeue_tree(struct 
htb_sched *q, const int prio,
                 cl->leaf.deficit[level] -= qdisc_pkt_len(skb);
                 if (cl->leaf.deficit[level] < 0) {
                         cl->leaf.deficit[level] += cl->quantum;
-                       htb_next_rb_node(level ? 
&cl->parent->inner.clprio[prio].ptr :
+                       /* Account for (fq_)codel child deactivating 
after dequeue */
+                       if (likely(cl->prio_activity))
+                               htb_next_rb_node(level ? 
&cl->parent->inner.clprio[prio].ptr :
  
&q->hlevel[0].hprio[prio].ptr);
                 }
                 /* this used to be after charge_class but this constelation

That way it's clear that the issue is a corner case where the
child qdisc deactivates the parent. Otherwise it seems like
we need to check for (*n) being NULL for every call to
htb_next_rb_node.

cheers,
Victor

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Patch net 1/2] sch_htb: make htb_deactivate() idempotent
  2025-04-30 15:19   ` Victor Nogueira
@ 2025-05-02  9:59     ` Paolo Abeni
  2025-05-02 22:13       ` Victor Nogueira
  2025-05-04 20:18     ` Cong Wang
  1 sibling, 1 reply; 8+ messages in thread
From: Paolo Abeni @ 2025-05-02  9:59 UTC (permalink / raw)
  To: Victor Nogueira, Cong Wang, netdev; +Cc: jhs, jiri, alan

On 4/30/25 5:19 PM, Victor Nogueira wrote:
> If that's so, couldn't we instead of doing:
> 
>> @@ -348,7 +348,8 @@ static void htb_add_to_wait_tree(struct htb_sched *q,
>>    */
>>   static inline void htb_next_rb_node(struct rb_node **n)
>>   {
>> -	*n = rb_next(*n);
>> +	if (*n)
>> +		*n = rb_next(*n);
>>   }
> 
> do something like:
> 
> @@ -921,7 +921,9 @@ static struct sk_buff *htb_dequeue_tree(struct 
> htb_sched *q, const int prio,
>                  cl->leaf.deficit[level] -= qdisc_pkt_len(skb);
>                  if (cl->leaf.deficit[level] < 0) {
>                          cl->leaf.deficit[level] += cl->quantum;
> -                       htb_next_rb_node(level ? 
> &cl->parent->inner.clprio[prio].ptr :
> +                       /* Account for (fq_)codel child deactivating 
> after dequeue */
> +                       if (likely(cl->prio_activity))
> +                               htb_next_rb_node(level ? 
> &cl->parent->inner.clprio[prio].ptr :
>   
> &q->hlevel[0].hprio[prio].ptr);
>                  }
>                  /* this used to be after charge_class but this constelation
> 
> That way it's clear that the issue is a corner case where the
> child qdisc deactivates the parent. Otherwise it seems like
> we need to check for (*n) being NULL for every call to
> htb_next_rb_node.

@Victor, I think that the Cong's suggested patch is simpler to very/tie
to code change to the actually addressed issue. I started at your
suggest code for a bit, and out of sheer htb ignorance I'm less
confident about it fixing the thing.

Do you have strong feeling vs Cong's suggested approach?

Thanks,

Paolo



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Patch net 1/2] sch_htb: make htb_deactivate() idempotent
  2025-05-02  9:59     ` Paolo Abeni
@ 2025-05-02 22:13       ` Victor Nogueira
  0 siblings, 0 replies; 8+ messages in thread
From: Victor Nogueira @ 2025-05-02 22:13 UTC (permalink / raw)
  To: Paolo Abeni, Cong Wang, netdev; +Cc: jhs, jiri, alan

On 5/2/25 06:59, Paolo Abeni wrote:
> On 4/30/25 5:19 PM, Victor Nogueira wrote:
>> If that's so, couldn't we instead of doing:
>>
>>> @@ -348,7 +348,8 @@ static void htb_add_to_wait_tree(struct htb_sched *q,
>>>     */
>>>    static inline void htb_next_rb_node(struct rb_node **n)
>>>    {
>>> -	*n = rb_next(*n);
>>> +	if (*n)
>>> +		*n = rb_next(*n);
>>>    }
>>
>> do something like:
>>
>> @@ -921,7 +921,9 @@ static struct sk_buff *htb_dequeue_tree(struct
>> htb_sched *q, const int prio,
>>                   cl->leaf.deficit[level] -= qdisc_pkt_len(skb);
>>                   if (cl->leaf.deficit[level] < 0) {
>>                           cl->leaf.deficit[level] += cl->quantum;
>> -                       htb_next_rb_node(level ?
>> &cl->parent->inner.clprio[prio].ptr :
>> +                       /* Account for (fq_)codel child deactivating
>> after dequeue */
>> +                       if (likely(cl->prio_activity))
>> +                               htb_next_rb_node(level ?
>> &cl->parent->inner.clprio[prio].ptr :
>>    
>> &q->hlevel[0].hprio[prio].ptr);
>>                   }
>>                   /* this used to be after charge_class but this constelation
>>
>> That way it's clear that the issue is a corner case where the
>> child qdisc deactivates the parent. Otherwise it seems like
>> we need to check for (*n) being NULL for every call to
>> htb_next_rb_node.
> 
> @Victor, I think that the Cong's suggested patch is simpler to very/tie
> to code change to the actually addressed issue. I started at your
> suggest code for a bit, and out of sheer htb ignorance I'm less
> confident about it fixing the thing.
> 
> Do you have strong feeling vs Cong's suggested approach?

I have no strong feelings, I'm ok with merging the patch
as is.

cheers,
Victor

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Patch net 1/2] sch_htb: make htb_deactivate() idempotent
  2025-04-30 15:19   ` Victor Nogueira
  2025-05-02  9:59     ` Paolo Abeni
@ 2025-05-04 20:18     ` Cong Wang
  1 sibling, 0 replies; 8+ messages in thread
From: Cong Wang @ 2025-05-04 20:18 UTC (permalink / raw)
  To: Victor Nogueira; +Cc: netdev, jhs, jiri, alan

On Wed, Apr 30, 2025 at 12:19:03PM -0300, Victor Nogueira wrote:
> On 4/28/25 20:29, Cong Wang wrote:
> > Alan reported a NULL pointer dereference in htb_next_rb_node()
> > after we made htb_qlen_notify() idempotent.
> > 
> > It turns out in the following case it introduced some regression:
> > 
> > htb_dequeue_tree():
> >    |-> fq_codel_dequeue()
> >      |-> qdisc_tree_reduce_backlog()
> >        |-> htb_qlen_notify()
> >          |-> htb_deactivate()
> >    |-> htb_next_rb_node()
> >    |-> htb_deactivate()
> > 
> > For htb_next_rb_node(), after calling the 1st htb_deactivate(), the
> > clprio[prio]->ptr could be already set to  NULL, which means
> > htb_next_rb_node() is vulnerable here.
> 
> If I'm not missing something, the issue seems to be that
> fq_codel_dequeue or codel_qdisc_dequeue may call qdisc_tree_reduce_backlog
> with sch->q.qlen == 0 after commit 342debc12183. This will cause
> htb_qlen_notify to be called which will deactivate before we
> call htb_next_rb_node further down in htb_dequeue_tree (as you
> said above).
> 
> If that's so, couldn't we instead of doing:
> 
> > @@ -348,7 +348,8 @@ static void htb_add_to_wait_tree(struct htb_sched *q,
> >    */
> >   static inline void htb_next_rb_node(struct rb_node **n)
> >   {
> > -	*n = rb_next(*n);
> > +	if (*n)
> > +		*n = rb_next(*n);
> >   }
> 
> do something like:
> 
> @@ -921,7 +921,9 @@ static struct sk_buff *htb_dequeue_tree(struct htb_sched
> *q, const int prio,
>                 cl->leaf.deficit[level] -= qdisc_pkt_len(skb);
>                 if (cl->leaf.deficit[level] < 0) {
>                         cl->leaf.deficit[level] += cl->quantum;
> -                       htb_next_rb_node(level ?
> &cl->parent->inner.clprio[prio].ptr :
> +                       /* Account for (fq_)codel child deactivating after
> dequeue */
> +                       if (likely(cl->prio_activity))
> +                               htb_next_rb_node(level ?

It reads odd to me to check cl->prio_activity before htb_next_rb_node(),
and I don't see any existing pattern of using this.

My patch pretty much follows all the existing patterns of checking
either cl->prio_activity or cl->leaf.q->q.qlen. So, although it looks
bigger from diffstat, it is safer from this point of view.

Thanks!

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Patch net 0/2] net_sched: fix a regression in sch_htb
  2025-04-28 23:29 [Patch net 0/2] net_sched: fix a regression in sch_htb Cong Wang
  2025-04-28 23:29 ` [Patch net 1/2] sch_htb: make htb_deactivate() idempotent Cong Wang
  2025-04-28 23:29 ` [Patch net 2/2] selftests/tc-testing: Add a test case to cover basic HTB+FQ_CODEL case Cong Wang
@ 2025-05-05 23:51 ` patchwork-bot+netdevbpf
  2 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2025-05-05 23:51 UTC (permalink / raw)
  To: Cong Wang; +Cc: netdev, jhs, jiri, alan

Hello:

This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Mon, 28 Apr 2025 16:29:53 -0700 you wrote:
> This patchset contains a fix for the regression reported by Alan and a
> selftest to cover that case. Please see each patch description for more
> details.
> 
> ---
> 
> Cong Wang (2):
>   sch_htb: make htb_deactivate() idempotent
>   selftests/tc-testing: Add a test case to cover basic HTB+FQ_CODEL case
> 
> [...]

Here is the summary with links:
  - [net,1/2] sch_htb: make htb_deactivate() idempotent
    https://git.kernel.org/netdev/net/c/376947861013
  - [net,2/2] selftests/tc-testing: Add a test case to cover basic HTB+FQ_CODEL case
    https://git.kernel.org/netdev/net/c/63890286f557

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2025-05-05 23:50 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-28 23:29 [Patch net 0/2] net_sched: fix a regression in sch_htb Cong Wang
2025-04-28 23:29 ` [Patch net 1/2] sch_htb: make htb_deactivate() idempotent Cong Wang
2025-04-30 15:19   ` Victor Nogueira
2025-05-02  9:59     ` Paolo Abeni
2025-05-02 22:13       ` Victor Nogueira
2025-05-04 20:18     ` Cong Wang
2025-04-28 23:29 ` [Patch net 2/2] selftests/tc-testing: Add a test case to cover basic HTB+FQ_CODEL case Cong Wang
2025-05-05 23:51 ` [Patch net 0/2] net_sched: fix a regression in sch_htb patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).