Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ae214e04 authored by Vlad Buslov's avatar Vlad Buslov Committed by Greg Kroah-Hartman
Browse files

net: sched: use Qdisc rcu API instead of relying on rtnl lock



[ Upstream commit e368fdb61d8e7c67ac70791b23345b26d7bbc661 ]

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: default avatarVlad Buslov <vladbu@mellanox.com>
Acked-by: default avatarJiri Pirko <jiri@mellanox.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
[Lee: Sent to Stable]
Link: https://syzkaller.appspot.com/bug?id=d7e411c5472dd5da33d8cc921ccadc747743a568


Reported-by: default avatar <syzbot+5f229e48cccc804062c0@syzkaller.appspotmail.com>
Signed-off-by: default avatarLee Jones <lee.jones@linaro.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent da1d3240
Loading
Loading
Loading
Loading
+64 −15
Original line number Diff line number Diff line
@@ -539,6 +539,7 @@ static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q,
					struct netlink_ext_ack *extack)
{
	struct tcf_block *block;
	int err = 0;

	if (ifindex == TCM_IFINDEX_MAGIC_BLOCK) {
		block = tcf_block_lookup(net, block_index);
@@ -550,55 +551,93 @@ static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q,
		const struct Qdisc_class_ops *cops;
		struct net_device *dev;

		rcu_read_lock();

		/* Find link */
		dev = __dev_get_by_index(net, ifindex);
		if (!dev)
		dev = dev_get_by_index_rcu(net, ifindex);
		if (!dev) {
			rcu_read_unlock();
			return ERR_PTR(-ENODEV);
		}

		/* Find qdisc */
		if (!*parent) {
			*q = dev->qdisc;
			*parent = (*q)->handle;
		} else {
			*q = qdisc_lookup(dev, TC_H_MAJ(*parent));
			*q = qdisc_lookup_rcu(dev, TC_H_MAJ(*parent));
			if (!*q) {
				NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");
				return ERR_PTR(-EINVAL);
				err = -EINVAL;
				goto errout_rcu;
			}
		}

		*q = qdisc_refcount_inc_nz(*q);
		if (!*q) {
			NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");
			err = -EINVAL;
			goto errout_rcu;
		}

		/* Is it classful? */
		cops = (*q)->ops->cl_ops;
		if (!cops) {
			NL_SET_ERR_MSG(extack, "Qdisc not classful");
			return ERR_PTR(-EINVAL);
			err = -EINVAL;
			goto errout_rcu;
		}

		if (!cops->tcf_block) {
			NL_SET_ERR_MSG(extack, "Class doesn't support blocks");
			return ERR_PTR(-EOPNOTSUPP);
			err = -EOPNOTSUPP;
			goto errout_rcu;
		}

		/* 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->find(*q, *parent);
			if (*cl == 0) {
				NL_SET_ERR_MSG(extack, "Specified class doesn't exist");
				return ERR_PTR(-ENOENT);
				err = -ENOENT;
				goto errout_qdisc;
			}
		}

		/* And the last stroke */
		block = cops->tcf_block(*q, *cl, extack);
		if (!block)
			return ERR_PTR(-EINVAL);
		if (!block) {
			err = -EINVAL;
			goto errout_qdisc;
		}
		if (tcf_block_shared(block)) {
			NL_SET_ERR_MSG(extack, "This filter block is shared. Please use the block index to manipulate the filters");
			return ERR_PTR(-EOPNOTSUPP);
			err = -EOPNOTSUPP;
			goto errout_qdisc;
		}
	}

	return block;

errout_rcu:
	rcu_read_unlock();
errout_qdisc:
	if (*q)
		qdisc_put(*q);
	return ERR_PTR(err);
}

static void tcf_block_release(struct Qdisc *q, struct tcf_block *block)
{
	if (q)
		qdisc_put(q);
}

struct tcf_block_owner_item {
@@ -1336,6 +1375,7 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
errout:
	if (chain)
		tcf_chain_put(chain);
	tcf_block_release(q, block);
	if (err == -EAGAIN)
		/* Replay the request. */
		goto replay;
@@ -1457,6 +1497,7 @@ static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
errout:
	if (chain)
		tcf_chain_put(chain);
	tcf_block_release(q, block);
	return err;
}

@@ -1542,6 +1583,7 @@ static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
errout:
	if (chain)
		tcf_chain_put(chain);
	tcf_block_release(q, block);
	return err;
}

@@ -1858,7 +1900,8 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
	chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
	if (chain_index > TC_ACT_EXT_VAL_MASK) {
		NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
		return -EINVAL;
		err = -EINVAL;
		goto errout_block;
	}
	chain = tcf_chain_lookup(block, chain_index);
	if (n->nlmsg_type == RTM_NEWCHAIN) {
@@ -1870,23 +1913,27 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
				tcf_chain_hold(chain);
			} else {
				NL_SET_ERR_MSG(extack, "Filter chain already exists");
				return -EEXIST;
				err = -EEXIST;
				goto errout_block;
			}
		} else {
			if (!(n->nlmsg_flags & NLM_F_CREATE)) {
				NL_SET_ERR_MSG(extack, "Need both RTM_NEWCHAIN and NLM_F_CREATE to create a new chain");
				return -ENOENT;
				err = -ENOENT;
				goto errout_block;
			}
			chain = tcf_chain_create(block, chain_index);
			if (!chain) {
				NL_SET_ERR_MSG(extack, "Failed to create filter chain");
				return -ENOMEM;
				err = -ENOMEM;
				goto errout_block;
			}
		}
	} else {
		if (!chain || tcf_chain_held_by_acts_only(chain)) {
			NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
			return -EINVAL;
			err = -EINVAL;
			goto errout_block;
		}
		tcf_chain_hold(chain);
	}
@@ -1930,6 +1977,8 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,

errout:
	tcf_chain_put(chain);
errout_block:
	tcf_block_release(q, block);
	if (err == -EAGAIN)
		/* Replay the request. */
		goto replay;