net/sched: qdisc_destroy() old ingress and clsact Qdiscs before grafting
[platform/kernel/linux-starfive.git] / net / sched / sch_api.c
index 094ca3a..aa6b1fe 100644 (file)
@@ -1086,10 +1086,22 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
                if ((q && q->flags & TCQ_F_INGRESS) ||
                    (new && new->flags & TCQ_F_INGRESS)) {
                        ingress = 1;
-                       if (!dev_ingress_queue(dev)) {
+                       dev_queue = dev_ingress_queue(dev);
+                       if (!dev_queue) {
                                NL_SET_ERR_MSG(extack, "Device does not have an ingress queue");
                                return -ENOENT;
                        }
+
+                       q = rtnl_dereference(dev_queue->qdisc_sleeping);
+
+                       /* This is the counterpart of that qdisc_refcount_inc_nz() call in
+                        * __tcf_qdisc_find() for filter requests.
+                        */
+                       if (!qdisc_refcount_dec_if_one(q)) {
+                               NL_SET_ERR_MSG(extack,
+                                              "Current ingress or clsact Qdisc has ongoing filter requests");
+                               return -EBUSY;
+                       }
                }
 
                if (dev->flags & IFF_UP)
@@ -1110,8 +1122,16 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
                                qdisc_put(old);
                        }
                } else {
-                       dev_queue = dev_ingress_queue(dev);
-                       old = dev_graft_qdisc(dev_queue, new);
+                       old = dev_graft_qdisc(dev_queue, NULL);
+
+                       /* {ingress,clsact}_destroy() @old before grafting @new to avoid
+                        * unprotected concurrent accesses to net_device::miniq_{in,e}gress
+                        * pointer(s) in mini_qdisc_pair_swap().
+                        */
+                       qdisc_notify(net, skb, n, classid, old, new, extack);
+                       qdisc_destroy(old);
+
+                       dev_graft_qdisc(dev_queue, new);
                }
 
 skip:
@@ -1125,8 +1145,6 @@ skip:
 
                        if (new && new->ops->attach)
                                new->ops->attach(new);
-               } else {
-                       notify_and_destroy(net, skb, n, classid, old, new, extack);
                }
 
                if (dev->flags & IFF_UP)