diff mbox series

[net] net/sched: sch_taprio: reset child qdiscs before freeing them

Message ID 63b6d79b0e830ebb0283e020db4df3cdfdfb2b94.1608142843.git.dcaratti@redhat.com (mailing list archive)
State Accepted
Delegated to: Netdev Maintainers
Headers show
Series [net] net/sched: sch_taprio: reset child qdiscs before freeing them | expand

Checks

Context Check Description
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for net
netdev/subject_prefix success Link
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit success Errors and warnings before: 2 this patch: 2
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/verify_fixes success Link
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 35 lines checked
netdev/build_allmodconfig_warn success Errors and warnings before: 2 this patch: 2
netdev/header_inline success Link
netdev/stable success Stable not CCed

Commit Message

Davide Caratti Dec. 16, 2020, 6:33 p.m. UTC
syzkaller shows that packets can still be dequeued while taprio_destroy()
is running. Let sch_taprio use the reset() function to cancel the advance
timer and drop all skbs from the child qdiscs.

Fixes: 5a781ccbd19e ("tc: Add support for configuring the taprio scheduler")
Link: https://syzkaller.appspot.com/bug?id=f362872379bf8f0017fb667c1ab158f2d1e764ae
Reported-by: syzbot+8971da381fb5a31f542d@syzkaller.appspotmail.com
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
---
 net/sched/sch_taprio.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

Comments

Vinicius Costa Gomes Dec. 16, 2020, 7:01 p.m. UTC | #1
Davide Caratti <dcaratti@redhat.com> writes:

> syzkaller shows that packets can still be dequeued while taprio_destroy()
> is running. Let sch_taprio use the reset() function to cancel the advance
> timer and drop all skbs from the child qdiscs.
>
> Fixes: 5a781ccbd19e ("tc: Add support for configuring the taprio scheduler")
> Link: https://syzkaller.appspot.com/bug?id=f362872379bf8f0017fb667c1ab158f2d1e764ae
> Reported-by: syzbot+8971da381fb5a31f542d@syzkaller.appspotmail.com
> Signed-off-by: Davide Caratti <dcaratti@redhat.com>
> ---

Acked-by: Vinicius Costa Gomes <vinicius.gomes@intel.com>


Cheers,
Jakub Kicinski Dec. 17, 2020, 7:04 p.m. UTC | #2
On Wed, 16 Dec 2020 11:01:54 -0800 Vinicius Costa Gomes wrote:
> Davide Caratti <dcaratti@redhat.com> writes:
> 
> > syzkaller shows that packets can still be dequeued while taprio_destroy()
> > is running. Let sch_taprio use the reset() function to cancel the advance
> > timer and drop all skbs from the child qdiscs.
> >
> > Fixes: 5a781ccbd19e ("tc: Add support for configuring the taprio scheduler")
> > Link: https://syzkaller.appspot.com/bug?id=f362872379bf8f0017fb667c1ab158f2d1e764ae
> > Reported-by: syzbot+8971da381fb5a31f542d@syzkaller.appspotmail.com
> > Signed-off-by: Davide Caratti <dcaratti@redhat.com>
>
> Acked-by: Vinicius Costa Gomes <vinicius.gomes@intel.com>

Applied thanks.
Jakub Kicinski Dec. 17, 2020, 7:05 p.m. UTC | #3
On Wed, 16 Dec 2020 19:33:29 +0100 Davide Caratti wrote:
> +	if (q->qdiscs) {
> +		for (i = 0; i < dev->num_tx_queues && q->qdiscs[i]; i++)
> +			qdisc_reset(q->qdiscs[i]);

Are you sure that we can't graft a NULL in the middle of the array?
Shouldn't this be:

	for (i = 0; i < dev->num_tx_queues; i++)
		if (q->qdiscs[i])
			qdisc_reset(q->qdiscs[i]);

?

If this is a problem it already exists in destroy so I'll apply anyway.
Davide Caratti Dec. 17, 2020, 8:32 p.m. UTC | #4
hello Jakub, and thanks for checking!

On Thu, 2020-12-17 at 11:05 -0800, Jakub Kicinski wrote:
> On Wed, 16 Dec 2020 19:33:29 +0100 Davide Caratti wrote:
> > +	if (q->qdiscs) {
> > +		for (i = 0; i < dev->num_tx_queues && q->qdiscs[i]; i++)
> > +			qdisc_reset(q->qdiscs[i]);
> 
> Are you sure that we can't graft a NULL in the middle of the array?

that should not happen, because child qdiscs are checked for being non-
NULL when they are created:

https://elixir.bootlin.com/linux/v5.10/source/net/sched/sch_taprio.c#L1674

and then assigned to q->qdiscs[i]. So, there might be NULL elements of
q->qdiscs[] in the middle of the array when taprio_reset() is called,
but it should be ok to finish the loop when we encounter the first one:
subsequent ones should be NULL as well.

> Shouldn't this be:
> 
> 	for (i = 0; i < dev->num_tx_queues; i++)
> 		if (q->qdiscs[i])
> 			qdisc_reset(q->qdiscs[i]);
> 
> ?

probably the above code is more robust, but - like you noticed - then we
should also change the same 'for' loop in taprio_destroy(), otherwise it
leaks resources. If you and Vinicius agree, I can post a follow-up patch
that makes ->reset() and ->destroy()  more consistent with ->enqueue() 
and ->dequeue(), and send it for net-next when it reopens. Ok?
Jakub Kicinski Dec. 17, 2020, 8:45 p.m. UTC | #5
On Thu, 17 Dec 2020 21:32:29 +0100 Davide Caratti wrote:
> hello Jakub, and thanks for checking!
> 
> On Thu, 2020-12-17 at 11:05 -0800, Jakub Kicinski wrote:
> > On Wed, 16 Dec 2020 19:33:29 +0100 Davide Caratti wrote:  
> > > +	if (q->qdiscs) {
> > > +		for (i = 0; i < dev->num_tx_queues && q->qdiscs[i]; i++)
> > > +			qdisc_reset(q->qdiscs[i]);  
> > 
> > Are you sure that we can't graft a NULL in the middle of the array?  
> 
> that should not happen, because child qdiscs are checked for being non-
> NULL when they are created:
> 
> https://elixir.bootlin.com/linux/v5.10/source/net/sched/sch_taprio.c#L1674
> 
> and then assigned to q->qdiscs[i]. So, there might be NULL elements of
> q->qdiscs[] in the middle of the array when taprio_reset() is called,
> but it should be ok to finish the loop when we encounter the first one:
> subsequent ones should be NULL as well.

Right, but that's init, look at taprio_graft(). The child qdiscs can be
replaced at any time. And the replacement can be NULL otherwise why
would graft check "if (new)"
Davide Caratti Dec. 17, 2020, 8:49 p.m. UTC | #6
On Thu, 2020-12-17 at 12:45 -0800, Jakub Kicinski wrote:
> Right, but that's init, look at taprio_graft(). The child qdiscs can be
> replaced at any time. And the replacement can be NULL otherwise why
> would graft check "if (new)"

good point, you are right. I'll send a follow-up patch right now.
thanks!

--  
davide
diff mbox series

Patch

diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 26fb8a62996b..c74817ec9964 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -1597,6 +1597,21 @@  static int taprio_change(struct Qdisc *sch, struct nlattr *opt,
 	return err;
 }
 
+static void taprio_reset(struct Qdisc *sch)
+{
+	struct taprio_sched *q = qdisc_priv(sch);
+	struct net_device *dev = qdisc_dev(sch);
+	int i;
+
+	hrtimer_cancel(&q->advance_timer);
+	if (q->qdiscs) {
+		for (i = 0; i < dev->num_tx_queues && q->qdiscs[i]; i++)
+			qdisc_reset(q->qdiscs[i]);
+	}
+	sch->qstats.backlog = 0;
+	sch->q.qlen = 0;
+}
+
 static void taprio_destroy(struct Qdisc *sch)
 {
 	struct taprio_sched *q = qdisc_priv(sch);
@@ -1607,7 +1622,6 @@  static void taprio_destroy(struct Qdisc *sch)
 	list_del(&q->taprio_list);
 	spin_unlock(&taprio_list_lock);
 
-	hrtimer_cancel(&q->advance_timer);
 
 	taprio_disable_offload(dev, q, NULL);
 
@@ -1954,6 +1968,7 @@  static struct Qdisc_ops taprio_qdisc_ops __read_mostly = {
 	.init		= taprio_init,
 	.change		= taprio_change,
 	.destroy	= taprio_destroy,
+	.reset		= taprio_reset,
 	.peek		= taprio_peek,
 	.dequeue	= taprio_dequeue,
 	.enqueue	= taprio_enqueue,