Commit 6ab0f866 authored by Kuniyuki Iwashima's avatar Kuniyuki Iwashima Committed by Paolo Abeni
Browse files

rtnetlink: Protect struct rtnl_af_ops with SRCU.



Once RTNL is replaced with rtnl_net_lock(), we need a mechanism to
guarantee that rtnl_af_ops is alive during inflight RTM_SETLINK
even when its module is being unloaded.

Let's use SRCU to protect ops.

rtnl_af_lookup() now iterates rtnl_af_ops under RCU and returns
SRCU-protected ops pointer.  The caller must call rtnl_af_put()
to release the pointer after the use.

Also, rtnl_af_unregister() unlinks the ops first and calls
synchronize_srcu() to wait for inflight RTM_SETLINK requests to
complete.

Note that rtnl_af_ops needs to be protected by its dedicated lock
when RTNL is removed.

Note also that BUG_ON() in do_setlink() is changed to the normal
error handling as a different af_ops might be found after
validate_linkmsg().

Signed-off-by: default avatarKuniyuki Iwashima <kuniyu@amazon.com>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent 26eebdc4
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -172,7 +172,8 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops);
/**
 * 	struct rtnl_af_ops - rtnetlink address family operations
 *
 *	@list: Used internally
 *	@list: Used internally, protected by RTNL and SRCU
 *	@srcu: Used internally
 * 	@family: Address family
 * 	@fill_link_af: Function to fill IFLA_AF_SPEC with address family
 * 		       specific netlink attributes.
@@ -185,6 +186,8 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops);
 */
struct rtnl_af_ops {
	struct list_head	list;
	struct srcu_struct	srcu;

	int			family;

	int			(*fill_link_af)(struct sk_buff *skb,
+47 −16
Original line number Diff line number Diff line
@@ -666,18 +666,31 @@ static size_t rtnl_link_get_size(const struct net_device *dev)

static LIST_HEAD(rtnl_af_ops);

static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
static struct rtnl_af_ops *rtnl_af_lookup(const int family, int *srcu_index)
{
	const struct rtnl_af_ops *ops;
	struct rtnl_af_ops *ops;

	ASSERT_RTNL();

	list_for_each_entry(ops, &rtnl_af_ops, list) {
		if (ops->family == family)
	rcu_read_lock();

	list_for_each_entry_rcu(ops, &rtnl_af_ops, list) {
		if (ops->family == family) {
			*srcu_index = srcu_read_lock(&ops->srcu);
			goto unlock;
		}
	}

	ops = NULL;
unlock:
	rcu_read_unlock();

	return ops;
}

	return NULL;
static void rtnl_af_put(struct rtnl_af_ops *ops, int srcu_index)
{
	srcu_read_unlock(&ops->srcu, srcu_index);
}

/**
@@ -688,6 +701,11 @@ static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
 */
int rtnl_af_register(struct rtnl_af_ops *ops)
{
	int err = init_srcu_struct(&ops->srcu);

	if (err)
		return err;

	rtnl_lock();
	list_add_tail_rcu(&ops->list, &rtnl_af_ops);
	rtnl_unlock();
@@ -707,6 +725,8 @@ void rtnl_af_unregister(struct rtnl_af_ops *ops)
	rtnl_unlock();

	synchronize_rcu();
	synchronize_srcu(&ops->srcu);
	cleanup_srcu_struct(&ops->srcu);
}
EXPORT_SYMBOL_GPL(rtnl_af_unregister);

@@ -2579,22 +2599,26 @@ static int validate_linkmsg(struct net_device *dev, struct nlattr *tb[],
		int rem, err;

		nla_for_each_nested(af, tb[IFLA_AF_SPEC], rem) {
			const struct rtnl_af_ops *af_ops;
			struct rtnl_af_ops *af_ops;
			int af_ops_srcu_index;

			af_ops = rtnl_af_lookup(nla_type(af));
			af_ops = rtnl_af_lookup(nla_type(af), &af_ops_srcu_index);
			if (!af_ops)
				return -EAFNOSUPPORT;

			if (!af_ops->set_link_af)
				return -EOPNOTSUPP;

			if (af_ops->validate_link_af) {
				err = -EOPNOTSUPP;
			else if (af_ops->validate_link_af)
				err = af_ops->validate_link_af(dev, af, extack);
			else
				err = 0;

			rtnl_af_put(af_ops, af_ops_srcu_index);

			if (err < 0)
				return err;
		}
	}
	}

	return 0;
}
@@ -3172,11 +3196,18 @@ static int do_setlink(const struct sk_buff *skb, struct net_device *dev,
		int rem;

		nla_for_each_nested(af, tb[IFLA_AF_SPEC], rem) {
			const struct rtnl_af_ops *af_ops;
			struct rtnl_af_ops *af_ops;
			int af_ops_srcu_index;

			BUG_ON(!(af_ops = rtnl_af_lookup(nla_type(af))));
			af_ops = rtnl_af_lookup(nla_type(af), &af_ops_srcu_index);
			if (!af_ops) {
				err = -EAFNOSUPPORT;
				goto errout;
			}

			err = af_ops->set_link_af(dev, af, extack);
			rtnl_af_put(af_ops, af_ops_srcu_index);

			if (err < 0)
				goto errout;