Skip to content

Commit 3b0d281

Browse files
author
Paolo Abeni
committed
Merge branch 'net-sched-fix-race-conditions-in-mini_qdisc_pair_swap'
Peilin Ye says: ==================== net/sched: Fix race conditions in mini_qdisc_pair_swap() These 2 patches fix race conditions for ingress and clsact Qdiscs as reported [1] by syzbot, split out from another [2] series (last 2 patches of it). Per-patch changelog omitted. Patch 1 hasn't been touched since last version; I just included everybody's tag. Patch 2 bases on patch 6 v1 of [2], with comments and commit log slightly changed. We also need rtnl_dereference() to load ->qdisc_sleeping since commit d636fc5 ("net: sched: add rcu annotations around qdisc->qdisc_sleeping"), so I changed that; please take yet another look, thanks! Patch 2 has been tested with the new reproducer Pedro posted [3]. [1] https://syzkaller.appspot.com/bug?extid=b53a9c0d1ea4ad62da8b [2] https://lore.kernel.org/r/cover.1684887977.git.peilin.ye@bytedance.com/ [3] https://lore.kernel.org/r/7879f218-c712-e9cc-57ba-665990f5f4c9@mojatatu.com/ ==================== Link: https://lore.kernel.org/r/cover.1686355297.git.peilin.ye@bytedance.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
2 parents 41f2c7c + 84ad0af commit 3b0d281

3 files changed

Lines changed: 50 additions & 16 deletions

File tree

include/net/sch_generic.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ static inline void qdisc_refcount_inc(struct Qdisc *qdisc)
137137
refcount_inc(&qdisc->refcnt);
138138
}
139139

140+
static inline bool qdisc_refcount_dec_if_one(struct Qdisc *qdisc)
141+
{
142+
if (qdisc->flags & TCQ_F_BUILTIN)
143+
return true;
144+
return refcount_dec_if_one(&qdisc->refcnt);
145+
}
146+
140147
/* Intended to be used by unlocked users, when concurrent qdisc release is
141148
* possible.
142149
*/
@@ -652,6 +659,7 @@ void dev_deactivate_many(struct list_head *head);
652659
struct Qdisc *dev_graft_qdisc(struct netdev_queue *dev_queue,
653660
struct Qdisc *qdisc);
654661
void qdisc_reset(struct Qdisc *qdisc);
662+
void qdisc_destroy(struct Qdisc *qdisc);
655663
void qdisc_put(struct Qdisc *qdisc);
656664
void qdisc_put_unlocked(struct Qdisc *qdisc);
657665
void qdisc_tree_reduce_backlog(struct Qdisc *qdisc, int n, int len);

net/sched/sch_api.c

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,17 +1079,29 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
10791079

10801080
if (parent == NULL) {
10811081
unsigned int i, num_q, ingress;
1082+
struct netdev_queue *dev_queue;
10821083

10831084
ingress = 0;
10841085
num_q = dev->num_tx_queues;
10851086
if ((q && q->flags & TCQ_F_INGRESS) ||
10861087
(new && new->flags & TCQ_F_INGRESS)) {
1087-
num_q = 1;
10881088
ingress = 1;
1089-
if (!dev_ingress_queue(dev)) {
1089+
dev_queue = dev_ingress_queue(dev);
1090+
if (!dev_queue) {
10901091
NL_SET_ERR_MSG(extack, "Device does not have an ingress queue");
10911092
return -ENOENT;
10921093
}
1094+
1095+
q = rtnl_dereference(dev_queue->qdisc_sleeping);
1096+
1097+
/* This is the counterpart of that qdisc_refcount_inc_nz() call in
1098+
* __tcf_qdisc_find() for filter requests.
1099+
*/
1100+
if (!qdisc_refcount_dec_if_one(q)) {
1101+
NL_SET_ERR_MSG(extack,
1102+
"Current ingress or clsact Qdisc has ongoing filter requests");
1103+
return -EBUSY;
1104+
}
10931105
}
10941106

10951107
if (dev->flags & IFF_UP)
@@ -1100,18 +1112,26 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
11001112
if (new && new->ops->attach && !ingress)
11011113
goto skip;
11021114

1103-
for (i = 0; i < num_q; i++) {
1104-
struct netdev_queue *dev_queue = dev_ingress_queue(dev);
1105-
1106-
if (!ingress)
1115+
if (!ingress) {
1116+
for (i = 0; i < num_q; i++) {
11071117
dev_queue = netdev_get_tx_queue(dev, i);
1118+
old = dev_graft_qdisc(dev_queue, new);
11081119

1109-
old = dev_graft_qdisc(dev_queue, new);
1110-
if (new && i > 0)
1111-
qdisc_refcount_inc(new);
1112-
1113-
if (!ingress)
1120+
if (new && i > 0)
1121+
qdisc_refcount_inc(new);
11141122
qdisc_put(old);
1123+
}
1124+
} else {
1125+
old = dev_graft_qdisc(dev_queue, NULL);
1126+
1127+
/* {ingress,clsact}_destroy() @old before grafting @new to avoid
1128+
* unprotected concurrent accesses to net_device::miniq_{in,e}gress
1129+
* pointer(s) in mini_qdisc_pair_swap().
1130+
*/
1131+
qdisc_notify(net, skb, n, classid, old, new, extack);
1132+
qdisc_destroy(old);
1133+
1134+
dev_graft_qdisc(dev_queue, new);
11151135
}
11161136

11171137
skip:
@@ -1125,8 +1145,6 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
11251145

11261146
if (new && new->ops->attach)
11271147
new->ops->attach(new);
1128-
} else {
1129-
notify_and_destroy(net, skb, n, classid, old, new, extack);
11301148
}
11311149

11321150
if (dev->flags & IFF_UP)

net/sched/sch_generic.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ static void qdisc_free_cb(struct rcu_head *head)
10461046
qdisc_free(q);
10471047
}
10481048

1049-
static void qdisc_destroy(struct Qdisc *qdisc)
1049+
static void __qdisc_destroy(struct Qdisc *qdisc)
10501050
{
10511051
const struct Qdisc_ops *ops = qdisc->ops;
10521052

@@ -1070,6 +1070,14 @@ static void qdisc_destroy(struct Qdisc *qdisc)
10701070
call_rcu(&qdisc->rcu, qdisc_free_cb);
10711071
}
10721072

1073+
void qdisc_destroy(struct Qdisc *qdisc)
1074+
{
1075+
if (qdisc->flags & TCQ_F_BUILTIN)
1076+
return;
1077+
1078+
__qdisc_destroy(qdisc);
1079+
}
1080+
10731081
void qdisc_put(struct Qdisc *qdisc)
10741082
{
10751083
if (!qdisc)
@@ -1079,7 +1087,7 @@ void qdisc_put(struct Qdisc *qdisc)
10791087
!refcount_dec_and_test(&qdisc->refcnt))
10801088
return;
10811089

1082-
qdisc_destroy(qdisc);
1090+
__qdisc_destroy(qdisc);
10831091
}
10841092
EXPORT_SYMBOL(qdisc_put);
10851093

@@ -1094,7 +1102,7 @@ void qdisc_put_unlocked(struct Qdisc *qdisc)
10941102
!refcount_dec_and_rtnl_lock(&qdisc->refcnt))
10951103
return;
10961104

1097-
qdisc_destroy(qdisc);
1105+
__qdisc_destroy(qdisc);
10981106
rtnl_unlock();
10991107
}
11001108
EXPORT_SYMBOL(qdisc_put_unlocked);

0 commit comments

Comments
 (0)