Blob Blame History Raw
From: Vlad Buslov <vladbu@mellanox.com>
Date: Mon, 24 Sep 2018 19:22:53 +0300
Subject: net: sched: use Qdisc rcu API instead of relying on rtnl lock
Patch-mainline: v4.20-rc1
Git-commit: e368fdb61d8e7c67ac70791b23345b26d7bbc661
References: bsc#1196973 CVE-2021-39713

As a preparation from removing rtnl lock dependency from rules update path,
use Qdisc rcu and reference counting capabilities instead of relying on
rtnl lock while working with Qdiscs. Create new tcf_block_release()
function, and use it to free resources taken by tcf_block_find().
Currently, this function only releases Qdisc and it is extended in next
patches in this series.

Signed-off-by: Vlad Buslov <vladbu@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
---
 net/sched/cls_api.c |   53 +++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 42 insertions(+), 11 deletions(-)

--- a/net/sched/cls_api.c
+++ b/net/sched/cls_api.c
@@ -487,34 +487,63 @@ replay:
 
 	/* Find head of filter chain. */
 
+	rcu_read_lock();
+
 	/* Find link */
-	dev = __dev_get_by_index(net, t->tcm_ifindex);
-	if (dev == NULL)
+	dev = dev_get_by_index_rcu(net, t->tcm_ifindex);
+	if (dev == NULL) {
+		rcu_read_unlock();
 		return -ENODEV;
+	}
 
 	/* Find qdisc */
 	if (!parent) {
 		q = dev->qdisc;
 		parent = q->handle;
 	} else {
-		q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent));
-		if (q == NULL)
-			return -EINVAL;
+		q = qdisc_lookup_rcu(dev, TC_H_MAJ(t->tcm_parent));
+		if (q == NULL) {
+			err = -EINVAL;
+			rcu_read_unlock();
+			goto errout;
+		}
+	}
+
+	q = qdisc_refcount_inc_nz(q);
+	if (!q) {
+		err = -EINVAL;
+		rcu_read_unlock();
+		goto errout;
 	}
 
 	/* Is it classful? */
 	cops = q->ops->cl_ops;
-	if (!cops)
-		return -EINVAL;
+	if (!cops) {
+		err = -EINVAL;
+		rcu_read_unlock();
+		goto errout;
+	}
 
-	if (!cops->tcf_block)
-		return -EOPNOTSUPP;
+	if (!cops->tcf_block) {
+		err = -EOPNOTSUPP;
+		rcu_read_unlock();
+		goto errout;
+	}
+
+	/* At this point we know that qdisc is not noop_qdisc,
+	 * which means that qdisc holds a reference to net_device
+	 * and we hold a reference to qdisc, so it is safe to release
+	 * rcu read lock.
+	 */
+	rcu_read_unlock();
 
 	/* Do we search for filter, attached to class? */
 	if (TC_H_MIN(parent)) {
 		cl = cops->get(q, parent);
-		if (cl == 0)
-			return -ENOENT;
+		if (cl == 0) {
+			err = -ENOENT;
+			goto errout;
+		}
 	}
 
 	/* And the last stroke */
@@ -645,6 +674,8 @@ errout:
 		tcf_chain_put(chain);
 	if (cl)
 		cops->put(q, cl);
+	if (q)
+		qdisc_put(q);
 	if (err == -EAGAIN)
 		/* Replay the request. */
 		goto replay;