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

Commit 1414edd2 authored by Yan Yan's avatar Yan Yan Committed by Greg Kroah-Hartman
Browse files

xfrm: Check if_id in xfrm_migrate

[ Upstream commit c1aca3080e382886e2e58e809787441984a2f89b ]

This patch enables distinguishing SAs and SPs based on if_id during
the xfrm_migrate flow. This ensures support for xfrm interfaces
throughout the SA/SP lifecycle.

When there are multiple existing SPs with the same direction,
the same xfrm_selector and different endpoint addresses,
xfrm_migrate might fail with ENODATA.

Specifically, the code path for performing xfrm_migrate is:
  Stage 1: find policy to migrate with
    xfrm_migrate_policy_find(sel, dir, type, net)
  Stage 2: find and update state(s) with
    xfrm_migrate_state_find(mp, net)
  Stage 3: update endpoint address(es) of template(s) with
    xfrm_policy_migrate(pol, m, num_migrate)

Currently "Stage 1" always returns the first xfrm_policy that
matches, and "Stage 3" looks for the xfrm_tmpl that matches the
old endpoint address. Thus if there are multiple xfrm_policy
with same selector, direction, type and net, "Stage 1" might
rertun a wrong xfrm_policy and "Stage 3" will fail with ENODATA
because it cannot find a xfrm_tmpl with the matching endpoint
address.

The fix is to allow userspace to pass an if_id and add if_id
to the matching rule in Stage 1 and Stage 2 since if_id is a
unique ID for xfrm_policy and xfrm_state. For compatibility,
if_id will only be checked if the attribute is set.

Tested with additions to Android's kernel unit test suite:
https://android-review.googlesource.com/c/kernel/tests/+/1668886



Signed-off-by: default avatarYan Yan <evitayan@google.com>
Signed-off-by: default avatarSteffen Klassert <steffen.klassert@secunet.com>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 0ad6f021
Loading
Loading
Loading
Loading
+3 −2
Original line number Original line Diff line number Diff line
@@ -1763,14 +1763,15 @@ int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
	       const struct xfrm_migrate *m, int num_bundles,
	       const struct xfrm_migrate *m, int num_bundles,
	       const struct xfrm_kmaddress *k,
	       const struct xfrm_kmaddress *k,
	       const struct xfrm_encap_tmpl *encap);
	       const struct xfrm_encap_tmpl *encap);
struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net);
struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net,
						u32 if_id);
struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
				      struct xfrm_migrate *m,
				      struct xfrm_migrate *m,
				      struct xfrm_encap_tmpl *encap);
				      struct xfrm_encap_tmpl *encap);
int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
		 struct xfrm_migrate *m, int num_bundles,
		 struct xfrm_migrate *m, int num_bundles,
		 struct xfrm_kmaddress *k, struct net *net,
		 struct xfrm_kmaddress *k, struct net *net,
		 struct xfrm_encap_tmpl *encap);
		 struct xfrm_encap_tmpl *encap, u32 if_id);
#endif
#endif


int km_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr, __be16 sport);
int km_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr, __be16 sport);
+1 −1
Original line number Original line Diff line number Diff line
@@ -2633,7 +2633,7 @@ static int pfkey_migrate(struct sock *sk, struct sk_buff *skb,
	}
	}


	return xfrm_migrate(&sel, dir, XFRM_POLICY_TYPE_MAIN, m, i,
	return xfrm_migrate(&sel, dir, XFRM_POLICY_TYPE_MAIN, m, i,
			    kma ? &k : NULL, net, NULL);
			    kma ? &k : NULL, net, NULL, 0);


 out:
 out:
	return err;
	return err;
+8 −6
Original line number Original line Diff line number Diff line
@@ -3050,7 +3050,7 @@ static bool xfrm_migrate_selector_match(const struct xfrm_selector *sel_cmp,
}
}


static struct xfrm_policy *xfrm_migrate_policy_find(const struct xfrm_selector *sel,
static struct xfrm_policy *xfrm_migrate_policy_find(const struct xfrm_selector *sel,
						    u8 dir, u8 type, struct net *net)
						    u8 dir, u8 type, struct net *net, u32 if_id)
{
{
	struct xfrm_policy *pol, *ret = NULL;
	struct xfrm_policy *pol, *ret = NULL;
	struct hlist_head *chain;
	struct hlist_head *chain;
@@ -3059,7 +3059,8 @@ static struct xfrm_policy *xfrm_migrate_policy_find(const struct xfrm_selector *
	spin_lock_bh(&net->xfrm.xfrm_policy_lock);
	spin_lock_bh(&net->xfrm.xfrm_policy_lock);
	chain = policy_hash_direct(net, &sel->daddr, &sel->saddr, sel->family, dir);
	chain = policy_hash_direct(net, &sel->daddr, &sel->saddr, sel->family, dir);
	hlist_for_each_entry(pol, chain, bydst) {
	hlist_for_each_entry(pol, chain, bydst) {
		if (xfrm_migrate_selector_match(sel, &pol->selector) &&
		if ((if_id == 0 || pol->if_id == if_id) &&
		    xfrm_migrate_selector_match(sel, &pol->selector) &&
		    pol->type == type) {
		    pol->type == type) {
			ret = pol;
			ret = pol;
			priority = ret->priority;
			priority = ret->priority;
@@ -3071,7 +3072,8 @@ static struct xfrm_policy *xfrm_migrate_policy_find(const struct xfrm_selector *
		if ((pol->priority >= priority) && ret)
		if ((pol->priority >= priority) && ret)
			break;
			break;


		if (xfrm_migrate_selector_match(sel, &pol->selector) &&
		if ((if_id == 0 || pol->if_id == if_id) &&
		    xfrm_migrate_selector_match(sel, &pol->selector) &&
		    pol->type == type) {
		    pol->type == type) {
			ret = pol;
			ret = pol;
			break;
			break;
@@ -3187,7 +3189,7 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate)
int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
		 struct xfrm_migrate *m, int num_migrate,
		 struct xfrm_migrate *m, int num_migrate,
		 struct xfrm_kmaddress *k, struct net *net,
		 struct xfrm_kmaddress *k, struct net *net,
		 struct xfrm_encap_tmpl *encap)
		 struct xfrm_encap_tmpl *encap, u32 if_id)
{
{
	int i, err, nx_cur = 0, nx_new = 0;
	int i, err, nx_cur = 0, nx_new = 0;
	struct xfrm_policy *pol = NULL;
	struct xfrm_policy *pol = NULL;
@@ -3206,14 +3208,14 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
	}
	}


	/* Stage 1 - find policy */
	/* Stage 1 - find policy */
	if ((pol = xfrm_migrate_policy_find(sel, dir, type, net)) == NULL) {
	if ((pol = xfrm_migrate_policy_find(sel, dir, type, net, if_id)) == NULL) {
		err = -ENOENT;
		err = -ENOENT;
		goto out;
		goto out;
	}
	}


	/* Stage 2 - find and update state(s) */
	/* Stage 2 - find and update state(s) */
	for (i = 0, mp = m; i < num_migrate; i++, mp++) {
	for (i = 0, mp = m; i < num_migrate; i++, mp++) {
		if ((x = xfrm_migrate_state_find(mp, net))) {
		if ((x = xfrm_migrate_state_find(mp, net, if_id))) {
			x_cur[nx_cur] = x;
			x_cur[nx_cur] = x;
			nx_cur++;
			nx_cur++;
			xc = xfrm_state_migrate(x, mp, encap);
			xc = xfrm_state_migrate(x, mp, encap);
+6 −1
Original line number Original line Diff line number Diff line
@@ -1466,7 +1466,8 @@ static struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig,
	return NULL;
	return NULL;
}
}


struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net)
struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net,
						u32 if_id)
{
{
	unsigned int h;
	unsigned int h;
	struct xfrm_state *x = NULL;
	struct xfrm_state *x = NULL;
@@ -1482,6 +1483,8 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
				continue;
				continue;
			if (m->reqid && x->props.reqid != m->reqid)
			if (m->reqid && x->props.reqid != m->reqid)
				continue;
				continue;
			if (if_id != 0 && x->if_id != if_id)
				continue;
			if (!xfrm_addr_equal(&x->id.daddr, &m->old_daddr,
			if (!xfrm_addr_equal(&x->id.daddr, &m->old_daddr,
					     m->old_family) ||
					     m->old_family) ||
			    !xfrm_addr_equal(&x->props.saddr, &m->old_saddr,
			    !xfrm_addr_equal(&x->props.saddr, &m->old_saddr,
@@ -1497,6 +1500,8 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
			if (x->props.mode != m->mode ||
			if (x->props.mode != m->mode ||
			    x->id.proto != m->proto)
			    x->id.proto != m->proto)
				continue;
				continue;
			if (if_id != 0 && x->if_id != if_id)
				continue;
			if (!xfrm_addr_equal(&x->id.daddr, &m->old_daddr,
			if (!xfrm_addr_equal(&x->id.daddr, &m->old_daddr,
					     m->old_family) ||
					     m->old_family) ||
			    !xfrm_addr_equal(&x->props.saddr, &m->old_saddr,
			    !xfrm_addr_equal(&x->props.saddr, &m->old_saddr,
+5 −1
Original line number Original line Diff line number Diff line
@@ -2369,6 +2369,7 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
	int n = 0;
	int n = 0;
	struct net *net = sock_net(skb->sk);
	struct net *net = sock_net(skb->sk);
	struct xfrm_encap_tmpl  *encap = NULL;
	struct xfrm_encap_tmpl  *encap = NULL;
	u32 if_id = 0;


	if (attrs[XFRMA_MIGRATE] == NULL)
	if (attrs[XFRMA_MIGRATE] == NULL)
		return -EINVAL;
		return -EINVAL;
@@ -2393,7 +2394,10 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
			return 0;
			return 0;
	}
	}


	err = xfrm_migrate(&pi->sel, pi->dir, type, m, n, kmp, net, encap);
	if (attrs[XFRMA_IF_ID])
		if_id = nla_get_u32(attrs[XFRMA_IF_ID]);

	err = xfrm_migrate(&pi->sel, pi->dir, type, m, n, kmp, net, encap, if_id);


	kfree(encap);
	kfree(encap);