xfrm: Check if_id in xfrm_migrate

[ Upstream commit c1aca3080e ]

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: Yan Yan <evitayan@google.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Yan Yan 2022-01-18 16:00:13 -08:00 committed by Greg Kroah-Hartman
parent 8918ae9741
commit e6d7e51e10
5 changed files with 23 additions and 11 deletions

View file

@ -1679,14 +1679,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);

View file

@ -2623,7 +2623,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;

View file

@ -4259,7 +4259,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;
@ -4268,7 +4268,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;
@ -4280,7 +4281,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;
@ -4396,7 +4398,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;
@ -4415,14 +4417,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);

View file

@ -1605,7 +1605,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;
@ -1621,6 +1622,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,
@ -1636,6 +1639,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,

View file

@ -2592,6 +2592,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;
@ -2616,7 +2617,10 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
return -ENOMEM; return -ENOMEM;
} }
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);