]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
authorAntony Antony <antony.antony@secunet.com>
Tue, 26 May 2026 19:09:51 +0000 (21:09 +0200)
committerSteffen Klassert <steffen.klassert@secunet.com>
Thu, 4 Jun 2026 10:22:47 +0000 (12:22 +0200)
Add a new netlink method to migrate a single xfrm_state.
Unlike the existing migration mechanism (SA + policy), this
supports migrating only the SA and allows changing the reqid.

The SA is looked up via xfrm_usersa_id, which uniquely
identifies it, so old_saddr is not needed. old_daddr is carried in
xfrm_usersa_id.daddr.

The reqid is invariant in the old migration.

Signed-off-by: Antony Antony <antony.antony@secunet.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
include/net/xfrm.h
include/uapi/linux/xfrm.h
net/xfrm/xfrm_compat.c
net/xfrm/xfrm_policy.c
net/xfrm/xfrm_state.c
net/xfrm/xfrm_user.c
security/selinux/nlmsgtab.c

index 5515c7b10020270ec121e8d5d7b444228943f56a..e2cb2d0e5cee7c3b5733895af534c6b53d39f8f7 100644 (file)
@@ -684,12 +684,20 @@ struct xfrm_migrate {
        xfrm_address_t          new_saddr;
        struct xfrm_encap_tmpl *encap;
        struct xfrm_user_offload *xuo;
+       struct xfrm_mark        old_mark;
+       const struct xfrm_mark *new_mark;
+       struct xfrm_mark        smark;
        u8                      proto;
        u8                      mode;
-       u16                     reserved;
+       u16                     msg_type; /* XFRM_MSG_MIGRATE or XFRM_MSG_MIGRATE_STATE */
+       u32                     flags;
        u32                     old_reqid;
+       u32                     new_reqid;
+       u32                     nat_keepalive_interval;
+       u32                     mapping_maxage;
        u16                     old_family;
        u16                     new_family;
+       const struct xfrm_selector *new_sel;
 };
 
 #define XFRM_KM_TIMEOUT                30
index a23495c0e0a109c428080d207f59f7581336537f..051f8066efd1b8f6f2b4d9c3b0e2ab81ece51b74 100644 (file)
@@ -227,6 +227,9 @@ enum {
 #define XFRM_MSG_SETDEFAULT XFRM_MSG_SETDEFAULT
        XFRM_MSG_GETDEFAULT,
 #define XFRM_MSG_GETDEFAULT XFRM_MSG_GETDEFAULT
+
+       XFRM_MSG_MIGRATE_STATE,
+#define XFRM_MSG_MIGRATE_STATE XFRM_MSG_MIGRATE_STATE
        __XFRM_MSG_MAX
 };
 #define XFRM_MSG_MAX (__XFRM_MSG_MAX - 1)
@@ -507,6 +510,28 @@ struct xfrm_user_migrate {
        __u16                           new_family;
 };
 
+struct xfrm_user_migrate_state {
+       struct xfrm_usersa_id id;
+       xfrm_address_t new_daddr;
+       xfrm_address_t new_saddr;
+       struct xfrm_mark old_mark;
+       struct xfrm_selector new_sel;
+       __u32 new_reqid;
+       __u32 flags;
+       __u16 new_family;
+       __u16 reserved;
+};
+
+/* Flags for xfrm_user_migrate_state.flags */
+enum xfrm_migrate_state_flags {
+       XFRM_MIGRATE_STATE_CLEAR_OFFLOAD = 1, /* do not inherit offload from existing SA */
+       XFRM_MIGRATE_STATE_UPDATE_H2H_SEL = 2, /* update H2H selector from new daddr/saddr */
+};
+
+/* All flags defined as of this header version; unknown bits are rejected. */
+#define XFRM_MIGRATE_STATE_KNOWN_FLAGS \
+       (XFRM_MIGRATE_STATE_CLEAR_OFFLOAD | XFRM_MIGRATE_STATE_UPDATE_H2H_SEL)
+
 struct xfrm_user_mapping {
        struct xfrm_usersa_id           id;
        __u32                           reqid;
index b8d2e6930041a9e2894c62d225e207b70b0b24c7..670e3512fc090807db691619c6aa804a448b4ead 100644 (file)
@@ -94,7 +94,8 @@ static const int compat_msg_min[XFRM_NR_MSGTYPES] = {
        [XFRM_MSG_GETSADINFO  - XFRM_MSG_BASE] = sizeof(u32),
        [XFRM_MSG_NEWSPDINFO  - XFRM_MSG_BASE] = sizeof(u32),
        [XFRM_MSG_GETSPDINFO  - XFRM_MSG_BASE] = sizeof(u32),
-       [XFRM_MSG_MAPPING     - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping)
+       [XFRM_MSG_MAPPING        - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping),
+       [XFRM_MSG_MIGRATE_STATE  - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state),
 };
 
 static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
@@ -162,6 +163,7 @@ static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb,
        case XFRM_MSG_NEWAE:
        case XFRM_MSG_REPORT:
        case XFRM_MSG_MIGRATE:
+       case XFRM_MSG_MIGRATE_STATE:
        case XFRM_MSG_NEWSADINFO:
        case XFRM_MSG_NEWSPDINFO:
        case XFRM_MSG_MAPPING:
@@ -498,6 +500,7 @@ static int xfrm_xlate32(struct nlmsghdr *dst, const struct nlmsghdr *src,
        case XFRM_MSG_GETAE:
        case XFRM_MSG_REPORT:
        case XFRM_MSG_MIGRATE:
+       case XFRM_MSG_MIGRATE_STATE:
        case XFRM_MSG_NEWSADINFO:
        case XFRM_MSG_GETSADINFO:
        case XFRM_MSG_NEWSPDINFO:
index cf05d778e2dd44381101a72d08dd9a649da2af94..1c558362d3758e2a17ec8c42a219e1172715d6ad 100644 (file)
@@ -4643,6 +4643,21 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate,
        return 0;
 }
 
+/*
+ * Fill migrate fields that are invariant in XFRM_MSG_MIGRATE: inherited
+ * from the existing SA unchanged. XFRM_MSG_MIGRATE_STATE can update these.
+ */
+static void xfrm_migrate_copy_old(const struct xfrm_state *x,
+                                 struct xfrm_migrate *mp)
+{
+       mp->msg_type               = XFRM_MSG_MIGRATE;
+       mp->smark                  = x->props.smark;
+       mp->new_reqid              = x->props.reqid;
+       mp->nat_keepalive_interval = x->nat_keepalive_interval;
+       mp->mapping_maxage         = x->mapping_maxage;
+       mp->new_mark               = &x->mark;
+}
+
 int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
                 struct xfrm_migrate *m, int num_migrate,
                 struct xfrm_kmaddress *k, struct net *net,
@@ -4682,6 +4697,8 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
                        nx_cur++;
                        mp->encap = encap;
                        mp->xuo = xuo;
+                       xfrm_migrate_copy_old(x, mp);
+
                        xc = xfrm_state_migrate(x, mp, net, extack);
                        if (xc) {
                                x_new[nx_new] = xc;
index 043e573c4f32623994e8152a720ec4910422ff1e..2c738e980b3f02c54212d7d4e8d2cc6f20108715 100644 (file)
@@ -1974,11 +1974,25 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
                goto out;
 
        memcpy(&x->id, &orig->id, sizeof(x->id));
-       memcpy(&x->sel, &orig->sel, sizeof(x->sel));
+       if (m->msg_type == XFRM_MSG_MIGRATE_STATE) {
+               if (m->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) {
+                       u8 prefixlen = (m->new_family == AF_INET6) ? 128 : 32;
+
+                       x->sel             = orig->sel;
+                       x->sel.family      = m->new_family;
+                       x->sel.prefixlen_d = prefixlen;
+                       x->sel.prefixlen_s = prefixlen;
+                       x->sel.daddr       = m->new_daddr;
+                       x->sel.saddr       = m->new_saddr;
+               } else {
+                       x->sel = *m->new_sel;
+               }
+       } else {
+               x->sel = orig->sel;
+       }
        memcpy(&x->lft, &orig->lft, sizeof(x->lft));
        x->props.mode = orig->props.mode;
        x->props.replay_window = orig->props.replay_window;
-       x->props.reqid = orig->props.reqid;
 
        if (orig->aalg) {
                x->aalg = xfrm_algo_auth_clone(orig->aalg);
@@ -2011,8 +2025,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
                x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL);
                if (!x->encap)
                        goto error;
-               x->mapping_maxage = orig->mapping_maxage;
-               x->nat_keepalive_interval = orig->nat_keepalive_interval;
+               x->mapping_maxage = m->mapping_maxage;
+               x->nat_keepalive_interval = m->nat_keepalive_interval;
        }
 
        if (orig->security)
@@ -2029,8 +2043,9 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
        if (xfrm_replay_clone(x, orig))
                goto error;
 
-       memcpy(&x->mark, &orig->mark, sizeof(x->mark));
-       memcpy(&x->props.smark, &orig->props.smark, sizeof(x->props.smark));
+       x->mark = m->new_mark ? *m->new_mark : m->old_mark;
+
+       x->props.smark = m->smark;
 
        x->props.flags = orig->props.flags;
        x->props.extra_flags = orig->props.extra_flags;
@@ -2053,7 +2068,7 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
                        goto error;
        }
 
-
+       x->props.reqid = m->new_reqid;
        x->props.family = m->new_family;
        memcpy(&x->id.daddr, &m->new_daddr, sizeof(x->id.daddr));
        memcpy(&x->props.saddr, &m->new_saddr, sizeof(x->props.saddr));
index de857ef65b2f4141668382c131b15108ddf08454..b9fbb8d13c1ab0ee0301647ffbb9091511b907d5 100644 (file)
@@ -1201,6 +1201,16 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb)
        return 0;
 }
 
+static void xso_to_xuo(const struct xfrm_dev_offload *xso,
+                      struct xfrm_user_offload *xuo)
+{
+       xuo->ifindex = xso->dev->ifindex;
+       if (xso->dir == XFRM_DEV_OFFLOAD_IN)
+               xuo->flags = XFRM_OFFLOAD_INBOUND;
+       if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
+               xuo->flags |= XFRM_OFFLOAD_PACKET;
+}
+
 static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
 {
        struct xfrm_user_offload *xuo;
@@ -1212,11 +1222,7 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
 
        xuo = nla_data(attr);
        memset(xuo, 0, sizeof(*xuo));
-       xuo->ifindex = xso->dev->ifindex;
-       if (xso->dir == XFRM_DEV_OFFLOAD_IN)
-               xuo->flags = XFRM_OFFLOAD_INBOUND;
-       if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
-               xuo->flags |= XFRM_OFFLOAD_PACKET;
+       xso_to_xuo(xso, xuo);
 
        return 0;
 }
@@ -1341,7 +1347,7 @@ static int copy_to_user_encap(struct xfrm_encap_tmpl *ep, struct sk_buff *skb)
        return 0;
 }
 
-static int xfrm_smark_put(struct sk_buff *skb, struct xfrm_mark *m)
+static int xfrm_smark_put(struct sk_buff *skb, const struct xfrm_mark *m)
 {
        int ret = 0;
 
@@ -3090,6 +3096,25 @@ nomem:
 }
 
 #ifdef CONFIG_XFRM_MIGRATE
+static void copy_from_user_migrate_state(struct xfrm_migrate *ma,
+                                        const struct xfrm_user_migrate_state *um)
+{
+       memcpy(&ma->old_daddr, &um->id.daddr, sizeof(ma->old_daddr));
+       memcpy(&ma->new_daddr, &um->new_daddr, sizeof(ma->new_daddr));
+       memcpy(&ma->new_saddr, &um->new_saddr, sizeof(ma->new_saddr));
+
+       ma->proto = um->id.proto;
+       ma->new_reqid = um->new_reqid;
+
+       ma->old_family = um->id.family;
+       ma->new_family = um->new_family;
+
+       ma->old_mark = um->old_mark;
+       ma->flags    = um->flags;
+       ma->new_sel  = &um->new_sel;
+       ma->msg_type = XFRM_MSG_MIGRATE_STATE;
+}
+
 static int copy_from_user_migrate(struct xfrm_migrate *ma,
                                  struct xfrm_kmaddress *k,
                                  struct nlattr **attrs, int *num,
@@ -3129,6 +3154,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
 
                ma->old_family = um->old_family;
                ma->new_family = um->new_family;
+               ma->msg_type   = XFRM_MSG_MIGRATE;
        }
 
        *num = i;
@@ -3139,7 +3165,7 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
                           struct nlattr **attrs, struct netlink_ext_ack *extack)
 {
        struct xfrm_userpolicy_id *pi = nlmsg_data(nlh);
-       struct xfrm_migrate m[XFRM_MAX_DEPTH];
+       struct xfrm_migrate m[XFRM_MAX_DEPTH] = {};
        struct xfrm_kmaddress km, *kmp;
        u8 type;
        int err;
@@ -3192,7 +3218,298 @@ error:
        kfree(xuo);
        return err;
 }
+
+static int build_migrate_state(struct sk_buff *skb,
+                              const struct xfrm_user_migrate_state *um,
+                              const struct xfrm_migrate *m,
+                              u8 dir, u32 portid, u32 seq)
+{
+       int err;
+       struct nlmsghdr *nlh;
+       struct xfrm_user_migrate_state *hdr;
+
+       nlh = nlmsg_put(skb, portid, seq, XFRM_MSG_MIGRATE_STATE,
+                       sizeof(struct xfrm_user_migrate_state), 0);
+       if (!nlh)
+               return -EMSGSIZE;
+
+       hdr = nlmsg_data(nlh);
+       *hdr = *um;
+       hdr->new_sel = *m->new_sel;
+
+       if (m->encap) {
+               err = nla_put(skb, XFRMA_ENCAP, sizeof(*m->encap), m->encap);
+               if (err)
+                       goto out_cancel;
+       }
+
+       if (m->xuo) {
+               err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(*m->xuo), m->xuo);
+               if (err)
+                       goto out_cancel;
+       }
+
+       if (m->new_mark) {
+               err = nla_put(skb, XFRMA_MARK, sizeof(*m->new_mark),
+                             m->new_mark);
+               if (err)
+                       goto out_cancel;
+       }
+
+       err = xfrm_smark_put(skb, &m->smark);
+       if (err)
+               goto out_cancel;
+
+       if (m->mapping_maxage) {
+               err = nla_put_u32(skb, XFRMA_MTIMER_THRESH, m->mapping_maxage);
+               if (err)
+                       goto out_cancel;
+       }
+
+       if (m->nat_keepalive_interval) {
+               err = nla_put_u32(skb, XFRMA_NAT_KEEPALIVE_INTERVAL,
+                                 m->nat_keepalive_interval);
+               if (err)
+                       goto out_cancel;
+       }
+
+       if (dir) {
+               err = nla_put_u8(skb, XFRMA_SA_DIR, dir);
+               if (err)
+                       goto out_cancel;
+       }
+
+       nlmsg_end(skb, nlh);
+       return 0;
+
+out_cancel:
+       nlmsg_cancel(skb, nlh);
+       return err;
+}
+
+static unsigned int xfrm_migrate_state_msgsize(const struct xfrm_migrate *m,
+                                              u8 dir)
+{
+       return NLMSG_ALIGN(sizeof(struct xfrm_user_migrate_state)) +
+               (m->encap ? nla_total_size(sizeof(struct xfrm_encap_tmpl)) : 0) +
+               (m->xuo ? nla_total_size(sizeof(struct xfrm_user_offload)) : 0) +
+               (m->new_mark ? nla_total_size(sizeof(struct xfrm_mark)) : 0) +
+               ((m->smark.v | m->smark.m) ? nla_total_size(sizeof(u32)) * 2 : 0) +
+               (m->mapping_maxage ? nla_total_size(sizeof(u32)) : 0) +
+               (m->nat_keepalive_interval ? nla_total_size(sizeof(u32)) : 0) +
+               (dir ? nla_total_size(sizeof(u8)) : 0); /* XFRMA_SA_DIR */
+}
+
+static int xfrm_send_migrate_state(struct net *net,
+                                  const struct xfrm_user_migrate_state *um,
+                                  const struct xfrm_migrate *m,
+                                  u8 dir, u32 portid, u32 seq)
+{
+       int err;
+       struct sk_buff *skb;
+
+       skb = nlmsg_new(xfrm_migrate_state_msgsize(m, dir), GFP_ATOMIC);
+       if (!skb)
+               return -ENOMEM;
+
+       err = build_migrate_state(skb, um, m, dir, portid, seq);
+       if (err < 0) {
+               kfree_skb(skb);
+               return err;
+       }
+
+       return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE);
+}
+
+static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
+                                struct nlattr **attrs, struct netlink_ext_ack *extack)
+{
+       struct xfrm_user_migrate_state *um = nlmsg_data(nlh);
+       struct net *net = sock_net(skb->sk);
+       struct xfrm_user_offload xuo = {};
+       struct xfrm_migrate m = {};
+       struct xfrm_state *xc;
+       struct xfrm_state *x;
+       int err;
+
+       if (!um->id.spi) {
+               NL_SET_ERR_MSG(extack, "Invalid SPI 0x0");
+               return -EINVAL;
+       }
+
+       if (um->reserved) {
+               NL_SET_ERR_MSG(extack, "Reserved field must be zero");
+               return -EINVAL;
+       }
+
+       if (um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS) {
+               NL_SET_ERR_MSG_FMT(extack, "Unknown flags: 0x%x",
+                                  um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS);
+               return -EINVAL;
+       }
+
+       err = verify_xfrm_family(um->new_family, extack);
+       if (err)
+               return err;
+
+       if (!(um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL)) {
+               err = verify_selector_prefixlen(um->new_sel.family,
+                                               &um->new_sel, extack);
+               if (err)
+                       return err;
+       }
+
+       copy_from_user_migrate_state(&m, um);
+
+       x = xfrm_state_lookup(net, m.old_mark.v & m.old_mark.m,
+                             &um->id.daddr, um->id.spi,
+                             um->id.proto, um->id.family);
+       if (!x) {
+               NL_SET_ERR_MSG(extack, "Can not find state");
+               return -ESRCH;
+       }
+
+       if (um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) {
+               u8 prefixlen = (x->props.family == AF_INET6) ? 128 : 32;
+
+               if (x->sel.prefixlen_s != x->sel.prefixlen_d ||
+                   x->sel.prefixlen_d != prefixlen ||
+                   !xfrm_addr_equal(&x->sel.daddr, &x->id.daddr, x->props.family) ||
+                   !xfrm_addr_equal(&x->sel.saddr, &x->props.saddr, x->props.family)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "SA selector is not a single-host match for SA addresses");
+                       err = -EINVAL;
+                       goto out;
+               }
+       }
+
+       if (attrs[XFRMA_ENCAP]) {
+               m.encap = nla_data(attrs[XFRMA_ENCAP]);
+               if (m.encap->encap_type == 0) {
+                       m.encap = NULL; /* sentinel: remove encap */
+               } else if (m.encap->encap_type != UDP_ENCAP_ESPINUDP) {
+                       NL_SET_ERR_MSG(extack, "Unsupported encapsulation type");
+                       err = -EINVAL;
+                       goto out;
+               }
+       } else {
+               m.encap = x->encap; /* omit-to-inherit */
+       }
+
+       if (attrs[XFRMA_MTIMER_THRESH]) {
+               err = verify_mtimer_thresh(!!m.encap, x->dir, extack);
+               if (err)
+                       goto out;
+       }
+
+       if (nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL], 0) && !m.encap) {
+               NL_SET_ERR_MSG(extack,
+                              "NAT_KEEPALIVE_INTERVAL requires encapsulation");
+               err = -EINVAL;
+               goto out;
+       }
+
+       if (attrs[XFRMA_OFFLOAD_DEV]) {
+               m.xuo = nla_data(attrs[XFRMA_OFFLOAD_DEV]);
+       } else {
+               bool inherit_offload = !(um->flags & XFRM_MIGRATE_STATE_CLEAR_OFFLOAD);
+
+               if (inherit_offload && x->xso.dev) {
+                       xso_to_xuo(&x->xso, &xuo);
+                       m.xuo = &xuo;
+               }
+       }
+
+       if (attrs[XFRMA_MARK])
+               m.new_mark = nla_data(attrs[XFRMA_MARK]);
+
+       if (attrs[XFRMA_SET_MARK])
+               xfrm_smark_init(attrs, &m.smark);
+       else
+               m.smark = x->props.smark;
+
+       m.mapping_maxage = nla_get_u32_default(attrs[XFRMA_MTIMER_THRESH],
+                                              x->mapping_maxage);
+       m.nat_keepalive_interval = nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL],
+                                                      x->nat_keepalive_interval);
+
+       if (m.new_family != um->id.family ||
+           !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family)) {
+               u32 new_mark_key = m.new_mark ? m.new_mark->v & m.new_mark->m :
+                                               m.old_mark.v & m.old_mark.m;
+               struct xfrm_state *x_new;
+
+               x_new = xfrm_state_lookup(net, new_mark_key, &m.new_daddr,
+                                         um->id.spi, um->id.proto, m.new_family);
+               if (x_new) {
+                       xfrm_state_put(x_new);
+                       NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
+                       err = -EEXIST;
+                       goto out;
+               }
+       }
+
+       xc = xfrm_state_migrate_create(x, &m, net, extack);
+       if (!xc) {
+               NL_SET_ERR_MSG_WEAK(extack, "State migration clone failed");
+               err = -EINVAL;
+               goto out;
+       }
+
+       spin_lock_bh(&x->lock);
+       if (x->km.state != XFRM_STATE_VALID) {
+               spin_unlock_bh(&x->lock);
+               NL_SET_ERR_MSG(extack, "State already deleted");
+               err = -ESRCH;
+               goto out_xc;
+       }
+       xfrm_migrate_sync(xc, x); /* to prevent SN/IV reuse */
+       __xfrm_state_delete(x);
+       spin_unlock_bh(&x->lock);
+
+       err = xfrm_state_migrate_install(x, xc, &m, extack);
+       if (err < 0) {
+               /*
+                * Should not occur: pre-check above ensures the new tuple is
+                * free under xfrm_cfg_mutex. Both SAs are gone if it does;
+                * restoring x would risk SN/IV reuse.
+                */
+               goto out;
+       }
+
+       /* Restore encap cleared by sentinel (type=0) during migration. */
+       if (attrs[XFRMA_ENCAP])
+               m.encap = nla_data(attrs[XFRMA_ENCAP]);
+
+       m.new_sel = &xc->sel;
+       m.mapping_maxage = xc->mapping_maxage;
+       m.nat_keepalive_interval = xc->nat_keepalive_interval;
+
+       err = xfrm_send_migrate_state(net, um, &m, xc->dir,
+                                     nlh->nlmsg_pid, nlh->nlmsg_seq);
+       if (err < 0) {
+               NL_SET_ERR_MSG(extack, "Failed to send migration notification");
+               err = 0;
+       }
+
+out:
+       xfrm_state_put(x);
+       return err;
+out_xc:
+       xc->km.state = XFRM_STATE_DEAD;
+       xfrm_state_put(xc);
+       xfrm_state_put(x);
+       return err;
+}
+
 #else
+static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
+                                struct nlattr **attrs, struct netlink_ext_ack *extack)
+{
+       NL_SET_ERR_MSG(extack, "XFRM_MSG_MIGRATE_STATE is not supported");
+       return -ENOPROTOOPT;
+}
+
 static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
                           struct nlattr **attrs, struct netlink_ext_ack *extack)
 {
@@ -3345,6 +3662,7 @@ const int xfrm_msg_min[XFRM_NR_MSGTYPES] = {
        [XFRM_MSG_GETSPDINFO  - XFRM_MSG_BASE] = sizeof(u32),
        [XFRM_MSG_SETDEFAULT  - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default),
        [XFRM_MSG_GETDEFAULT  - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default),
+       [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state),
 };
 EXPORT_SYMBOL_GPL(xfrm_msg_min);
 
@@ -3438,6 +3756,7 @@ static const struct xfrm_link {
        [XFRM_MSG_GETSPDINFO  - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo   },
        [XFRM_MSG_SETDEFAULT  - XFRM_MSG_BASE] = { .doit = xfrm_set_default   },
        [XFRM_MSG_GETDEFAULT  - XFRM_MSG_BASE] = { .doit = xfrm_get_default   },
+       [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate_state },
 };
 
 static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
index 2c0b07f9fbbd0b38edfd063020f09933c0f1ecb3..655d2616c9d2ab8af51b427499434d96f0b4bfd2 100644 (file)
@@ -128,6 +128,7 @@ static const struct nlmsg_perm nlmsg_xfrm_perms[] = {
        { XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ },
        { XFRM_MSG_SETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
        { XFRM_MSG_GETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_READ },
+       { XFRM_MSG_MIGRATE_STATE, NETLINK_XFRM_SOCKET__NLMSG_WRITE },
 };
 
 static const struct nlmsg_perm nlmsg_audit_perms[] = {
@@ -203,7 +204,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm)
                 * structures at the top of this file with the new mappings
                 * before updating the BUILD_BUG_ON() macro!
                 */
-               BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_GETDEFAULT);
+               BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_MIGRATE_STATE);
 
                if (selinux_policycap_netlink_xperm()) {
                        *perm = NETLINK_XFRM_SOCKET__NLMSG;