netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC ipsec-next 0/5] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message
@ 2025-11-25  9:27 Antony Antony
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 1/5] xfrm: migrate encap should be set in migrate call Antony Antony
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Antony Antony @ 2025-11-25  9:27 UTC (permalink / raw)
  To: Steffen Klassert, Herbert Xu, netdev
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, devel

The current XFRM_MSG_MIGRATE interface is tightly coupled to policy and
SA migration, and it lacks the information required to reliably migrate
individual SAs. This makes it unsuitable for IKEv2 deployments,
dual-stack setups (IPv4/IPv6), and scenarios where policies are managed
externally (e.g., by other daemons than IKE daemon).

Mandatory SA selector list
The current API requires a non-empty SA selector list, which does not reflect
IKEv2 use case. A single Child SA may correspond to multiple policies,
and SA discovery already occurs via address and reqid matching. With
dual-stack Child SAs this leads to excessive churn: the current method
would have to be called up to six times (in/out/fwd × v4/v6) on SA,
while the new method only requires two calls. While polices are
migrated, first installing a block policy

Selectors lack SPI (and marks)
XFRM_MSG_MIGRATE cannot uniquely identify an SA when multiple SAs share
the same policies (per-CPU SAs, SELinux label-based SAs, etc.). Without
the SPI, the kernel may update the wrong SA instance.

Reqid cannot be changed
Some implementations allocate reqids based on traffic selectors. In
host-to-host or selector-changing scenarios, the reqid must change,
which the current API cannot express.

Because strongSwan and other implementations manage policies
independently of the kernel, an interface that updates only a specific
SA — with complete and unambiguous identification — is required.

XFRM_MSG_MIGRATE_STATE provides that interface. It supports migration
of a single SA via xfrm_usersa_id (including SPI) and we fix
encap removal in this patch set, reqid updates, address changes,
and other SA-specific parameters. It avoids the structural limitations of
XFRM_MSG_MIGRATE and provides a simpler, extensible mechanism for
precise per-SA migration without involving policies.

Antony Antony (5):
  xfrm: migrate encap should be set in migrate call
  xfrm: rename reqid in xfrm_migrate
  xfrm: new method XFRM_MSG_MIGRATE_STATE
  xfrm: reqid is invarient in old migration
  xfrm: check that SA is in VALID state before use

 include/net/xfrm.h          |   7 +-
 include/uapi/linux/xfrm.h   |  10 +++
 net/key/af_key.c            |  10 +--
 net/xfrm/xfrm_policy.c      |   4 +-
 net/xfrm/xfrm_replay.c      |  16 ++++
 net/xfrm/xfrm_state.c       |  37 ++++----
 net/xfrm/xfrm_user.c        | 166 +++++++++++++++++++++++++++++++++++-
 security/selinux/nlmsgtab.c |   3 +-
 8 files changed, 221 insertions(+), 32 deletions(-)


Antony

--
2.39.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH RFC ipsec-next 1/5] xfrm: migrate encap should be set in migrate call
  2025-11-25  9:27 [PATCH RFC ipsec-next 0/5] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
@ 2025-11-25  9:29 ` Antony Antony
  2025-12-01  9:21   ` Steffen Klassert
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 2/5] xfrm: rename reqid in xfrm_migrate Antony Antony
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 7+ messages in thread
From: Antony Antony @ 2025-11-25  9:29 UTC (permalink / raw)
  To: Steffen Klassert, Herbert Xu, netdev
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, devel

The existing code does not allow migration from UDP encapsulation to
non-encapsulation (ESP). This is useful when migrating from behind a
NAT to no NAT, or from IPv4 with NAT to IPv6 without NAT.

With this fix, while migrating state, the existing encap will be copied
only if the migrate call includes the encap attribute.

Which fixes tag should I add?
Fixes: 80c9abaabf42 ("[XFRM]: Extension for dynamic update of endpoint address(es)") ?
or
Fixes: 4ab47d47af20 ("xfrm: extend MIGRATE with UDP encapsulation port") ?

Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
 net/xfrm/xfrm_state.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 7f70ea7f4d46..1770b56c8587 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2008,14 +2008,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 	}
 	x->props.calgo = orig->props.calgo;
 
-	if (encap || orig->encap) {
-		if (encap)
-			x->encap = kmemdup(encap, sizeof(*x->encap),
-					GFP_KERNEL);
-		else
-			x->encap = kmemdup(orig->encap, sizeof(*x->encap),
-					GFP_KERNEL);
-
+	if (encap) {
+		x->encap = kmemdup(encap, sizeof(*x->encap), GFP_KERNEL);
 		if (!x->encap)
 			goto error;
 	}
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH RFC ipsec-next 2/5] xfrm: rename reqid in xfrm_migrate
  2025-11-25  9:27 [PATCH RFC ipsec-next 0/5] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 1/5] xfrm: migrate encap should be set in migrate call Antony Antony
@ 2025-11-25  9:29 ` Antony Antony
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 3/5] xfrm: new method XFRM_MSG_MIGRATE_STATE Antony Antony
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Antony Antony @ 2025-11-25  9:29 UTC (permalink / raw)
  To: Steffen Klassert, Herbert Xu, netdev
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, devel

In prepreation for the following patch rename s/reqid/old_reqid/.

Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
 include/net/xfrm.h     |  2 +-
 net/key/af_key.c       | 10 +++++-----
 net/xfrm/xfrm_policy.c |  4 ++--
 net/xfrm/xfrm_state.c  |  6 +++---
 net/xfrm/xfrm_user.c   |  4 ++--
 5 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index f3014e4f54fc..ae35a0499168 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -684,7 +684,7 @@ struct xfrm_migrate {
 	u8			proto;
 	u8			mode;
 	u16			reserved;
-	u32			reqid;
+	u32			old_reqid;
 	u16			old_family;
 	u16			new_family;
 };
diff --git a/net/key/af_key.c b/net/key/af_key.c
index 571200433aa9..7d5cf386654c 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -2538,7 +2538,7 @@ static int ipsecrequests_to_migrate(struct sadb_x_ipsecrequest *rq1, int len,
 	if ((mode = pfkey_mode_to_xfrm(rq1->sadb_x_ipsecrequest_mode)) < 0)
 		return -EINVAL;
 	m->mode = mode;
-	m->reqid = rq1->sadb_x_ipsecrequest_reqid;
+	m->old_reqid = rq1->sadb_x_ipsecrequest_reqid;
 
 	return ((int)(rq1->sadb_x_ipsecrequest_len +
 		      rq2->sadb_x_ipsecrequest_len));
@@ -3634,15 +3634,15 @@ static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
 		if (mode < 0)
 			goto err;
 		if (set_ipsecrequest(skb, mp->proto, mode,
-				     (mp->reqid ?  IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
-				     mp->reqid, mp->old_family,
+				     (mp->old_reqid ?  IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
+				     mp->old_reqid, mp->old_family,
 				     &mp->old_saddr, &mp->old_daddr) < 0)
 			goto err;
 
 		/* new ipsecrequest */
 		if (set_ipsecrequest(skb, mp->proto, mode,
-				     (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
-				     mp->reqid, mp->new_family,
+				     (mp->old_reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
+				     mp->old_reqid, mp->new_family,
 				     &mp->new_saddr, &mp->new_daddr) < 0)
 			goto err;
 	}
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 62486f866975..854dfc16ed55 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4530,7 +4530,7 @@ static int migrate_tmpl_match(const struct xfrm_migrate *m, const struct xfrm_tm
 	int match = 0;
 
 	if (t->mode == m->mode && t->id.proto == m->proto &&
-	    (m->reqid == 0 || t->reqid == m->reqid)) {
+	    (m->old_reqid == 0 || t->reqid == m->old_reqid)) {
 		switch (t->mode) {
 		case XFRM_MODE_TUNNEL:
 		case XFRM_MODE_BEET:
@@ -4624,7 +4624,7 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate,
 				    sizeof(m[i].old_saddr)) &&
 			    m[i].proto == m[j].proto &&
 			    m[i].mode == m[j].mode &&
-			    m[i].reqid == m[j].reqid &&
+			    m[i].old_reqid == m[j].old_reqid &&
 			    m[i].old_family == m[j].old_family) {
 				NL_SET_ERR_MSG(extack, "Entries in the MIGRATE attribute's list must be unique");
 				return -EINVAL;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 1770b56c8587..62ccdf35cd0e 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2079,14 +2079,14 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
 
 	spin_lock_bh(&net->xfrm.xfrm_state_lock);
 
-	if (m->reqid) {
+	if (m->old_reqid) {
 		h = xfrm_dst_hash(net, &m->old_daddr, &m->old_saddr,
-				  m->reqid, m->old_family);
+				  m->old_reqid, m->old_family);
 		hlist_for_each_entry(x, net->xfrm.state_bydst+h, bydst) {
 			if (x->props.mode != m->mode ||
 			    x->id.proto != m->proto)
 				continue;
-			if (m->reqid && x->props.reqid != m->reqid)
+			if (m->old_reqid && x->props.reqid != m->old_reqid)
 				continue;
 			if (if_id != 0 && x->if_id != if_id)
 				continue;
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 010c9e6638c0..027e9ad10b45 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3081,7 +3081,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
 
 		ma->proto = um->proto;
 		ma->mode = um->mode;
-		ma->reqid = um->reqid;
+		ma->old_reqid = um->reqid;
 
 		ma->old_family = um->old_family;
 		ma->new_family = um->new_family;
@@ -3164,7 +3164,7 @@ static int copy_to_user_migrate(const struct xfrm_migrate *m, struct sk_buff *sk
 	memset(&um, 0, sizeof(um));
 	um.proto = m->proto;
 	um.mode = m->mode;
-	um.reqid = m->reqid;
+	um.reqid = m->old_reqid;
 	um.old_family = m->old_family;
 	memcpy(&um.old_daddr, &m->old_daddr, sizeof(um.old_daddr));
 	memcpy(&um.old_saddr, &m->old_saddr, sizeof(um.old_saddr));
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH RFC ipsec-next 3/5] xfrm: new method XFRM_MSG_MIGRATE_STATE
  2025-11-25  9:27 [PATCH RFC ipsec-next 0/5] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 1/5] xfrm: migrate encap should be set in migrate call Antony Antony
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 2/5] xfrm: rename reqid in xfrm_migrate Antony Antony
@ 2025-11-25  9:29 ` Antony Antony
  2025-11-25  9:30 ` [PATCH RFC ipsec-next 4/5] xfrm: reqid is invarient in old migration Antony Antony
  2025-11-25  9:30 ` [PATCH RFC ipsec-next 5/5] xfrm: check that SA is in VALID state before use Antony Antony
  4 siblings, 0 replies; 7+ messages in thread
From: Antony Antony @ 2025-11-25  9:29 UTC (permalink / raw)
  To: Steffen Klassert, Herbert Xu, netdev
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, devel

new method to support single xfrm_state migration.
Besides exiting migration (SA + policy), this also
support changing reqid.

Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
 include/net/xfrm.h          |   5 +-
 include/uapi/linux/xfrm.h   |  10 +++
 net/xfrm/xfrm_replay.c      |  16 ++++
 net/xfrm/xfrm_state.c       |  18 ++--
 net/xfrm/xfrm_user.c        | 161 +++++++++++++++++++++++++++++++++++-
 security/selinux/nlmsgtab.c |   3 +-
 6 files changed, 202 insertions(+), 11 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index ae35a0499168..6a6babcdcd09 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -685,6 +685,7 @@ struct xfrm_migrate {
 	u8			mode;
 	u16			reserved;
 	u32			old_reqid;
+	u32			new_reqid;
 	u16			old_family;
 	u16			new_family;
 };
@@ -1906,6 +1907,7 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
 		 struct xfrm_encap_tmpl *encap, u32 if_id,
 		 struct netlink_ext_ack *extack,
 		 struct xfrm_user_offload *xuo);
+void xfrm_sync_oseq(struct xfrm_state *x, struct xfrm_state *orig);
 #endif
 
 int km_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr, __be16 sport);
@@ -2011,8 +2013,7 @@ static inline unsigned int xfrm_replay_state_esn_len(struct xfrm_replay_state_es
 }
 
 #ifdef CONFIG_XFRM_MIGRATE
-static inline int xfrm_replay_clone(struct xfrm_state *x,
-				     struct xfrm_state *orig)
+static inline int xfrm_replay_clone(struct xfrm_state *x, struct xfrm_state *orig)
 {
 
 	x->replay_esn = kmemdup(orig->replay_esn,
diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h
index a23495c0e0a1..df75f7c2b7f7 100644
--- a/include/uapi/linux/xfrm.h
+++ b/include/uapi/linux/xfrm.h
@@ -227,6 +227,8 @@ 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 +509,14 @@ struct xfrm_user_migrate {
 	__u16				new_family;
 };
 
+struct xfrm_user_migrate_state {
+	struct xfrm_usersa_id id;
+	xfrm_address_t new_saddr;
+	xfrm_address_t new_daddr;
+	__u16 new_family;
+	__u32 new_reqid;
+};
+
 struct xfrm_user_mapping {
 	struct xfrm_usersa_id		id;
 	__u32				reqid;
diff --git a/net/xfrm/xfrm_replay.c b/net/xfrm/xfrm_replay.c
index dbdf8a39dffe..3404c03a8590 100644
--- a/net/xfrm/xfrm_replay.c
+++ b/net/xfrm/xfrm_replay.c
@@ -797,3 +797,19 @@ int xfrm_init_replay(struct xfrm_state *x, struct netlink_ext_ack *extack)
 	return 0;
 }
 EXPORT_SYMBOL(xfrm_init_replay);
+
+void xfrm_sync_oseq(struct xfrm_state *x, struct xfrm_state *orig)
+{
+	switch (x->repl_mode) {
+	case XFRM_REPLAY_MODE_LEGACY:
+		x->replay.oseq = orig->replay.oseq;
+		break;
+
+	case XFRM_REPLAY_MODE_BMP:
+	case XFRM_REPLAY_MODE_ESN:
+		x->replay_esn->oseq = orig->replay_esn->oseq;
+		x->replay_esn->oseq_hi = orig->replay_esn->oseq_hi;
+		break;
+	}
+}
+EXPORT_SYMBOL(xfrm_sync_oseq);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 62ccdf35cd0e..17c3de65fb00 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1966,7 +1966,8 @@ static inline int clone_security(struct xfrm_state *x, struct xfrm_sec_ctx *secu
 
 static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 					   struct xfrm_encap_tmpl *encap,
-					   struct xfrm_migrate *m)
+					   struct xfrm_migrate *m,
+					   struct netlink_ext_ack *extack)
 {
 	struct net *net = xs_net(orig);
 	struct xfrm_state *x = xfrm_state_alloc(net);
@@ -1978,7 +1979,6 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 	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;
 	x->props.saddr = orig->props.saddr;
 
 	if (orig->aalg) {
@@ -2058,7 +2058,10 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
 			goto error;
 	}
 
-
+	if (orig->props.reqid != m->new_reqid)
+		x->props.reqid = m->new_reqid;
+	else
+		x->props.reqid = orig->props.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));
@@ -2132,7 +2135,7 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 {
 	struct xfrm_state *xc;
 
-	xc = xfrm_state_clone_and_setup(x, encap, m);
+	xc = xfrm_state_clone_and_setup(x, encap, m, extack);
 	if (!xc)
 		return NULL;
 
@@ -2144,9 +2147,10 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 		goto error;
 
 	/* add state */
-	if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
-		/* a care is needed when the destination address of the
-		   state is to be updated as it is a part of triplet */
+	if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family) ||
+			x->props.reqid != xc->props.reqid) {
+		/* a care is needed when the destination address or the reqid
+		 * of the state is to be updated as it is a part of triplet */
 		xfrm_state_insert(xc);
 	} else {
 		if (xfrm_state_add(xc) < 0)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 027e9ad10b45..cc5c816f01ed 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3046,6 +3046,22 @@ static int xfrm_add_acquire(struct sk_buff *skb, struct nlmsghdr *nlh,
 }
 
 #ifdef CONFIG_XFRM_MIGRATE
+static int copy_from_user_migrate_state(struct xfrm_migrate *ma,
+					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;
+
+	return 0;
+}
+
 static int copy_from_user_migrate(struct xfrm_migrate *ma,
 				  struct xfrm_kmaddress *k,
 				  struct nlattr **attrs, int *num,
@@ -3148,7 +3164,149 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
 	kfree(xuo);
 	return err;
 }
+
+static int build_migrate_state(struct sk_buff *skb,
+			       const struct xfrm_user_migrate_state *m,
+			       const struct xfrm_encap_tmpl *encap,
+			       const struct xfrm_user_offload *xuo)
+{
+	int err;
+	struct nlmsghdr *nlh;
+	struct xfrm_user_migrate_state *um;
+
+	nlh = nlmsg_put(skb, 0, 0, XFRM_MSG_MIGRATE_STATE,
+			sizeof(struct xfrm_user_migrate_state), 0);
+	if (!nlh)
+		return -EMSGSIZE;
+
+	um = nlmsg_data(nlh);
+	memset(um, 0, sizeof(*um));
+	memcpy(um, m, sizeof(*um));
+
+	if (encap) {
+		err = nla_put(skb, XFRMA_ENCAP, sizeof(*encap), encap);
+		if (err)
+			goto out_cancel;
+	}
+
+	if (xuo) {
+		err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(*xuo), xuo);
+		if (err)
+			goto out_cancel;
+	}
+
+	nlmsg_end(skb, nlh);
+	return 0;
+
+out_cancel:
+	nlmsg_cancel(skb, nlh);
+	return err;
+}
+
+static inline unsigned int xfrm_migrate_state_msgsize(bool with_encp)
+{
+	return NLMSG_ALIGN(sizeof(struct xfrm_user_migrate_state))
+		+ (with_encp ? nla_total_size(sizeof(struct xfrm_encap_tmpl)) : 0);
+}
+
+static int xfrm_send_migrate_state(const struct xfrm_user_migrate_state *um,
+				   const struct xfrm_encap_tmpl *encap,
+				   const struct xfrm_user_offload *xuo)
+{
+	int err;
+	struct sk_buff *skb;
+	struct net *net = &init_net;
+
+	skb = nlmsg_new(xfrm_migrate_state_msgsize(!!encap), GFP_ATOMIC);
+	if (!skb)
+		return -ENOMEM;
+
+	err = build_migrate_state(skb, um, encap, xuo);
+	if (err < 0) {
+		WARN_ON(1);
+		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)
+{
+	int err = -ESRCH;
+	struct xfrm_state *x;
+	struct xfrm_encap_tmpl *encap = NULL;
+	struct xfrm_user_offload *xuo = NULL;
+	struct xfrm_migrate m  = { .old_saddr.a4 = 0,};
+	struct net *net = sock_net(skb->sk);
+	struct xfrm_user_migrate_state *um = nlmsg_data(nlh);
+
+	if (!um->id.spi)  {
+		NL_SET_ERR_MSG(extack, "Invalid SPI 0x0");
+		return -EINVAL;
+	}
+
+	err = copy_from_user_migrate_state(&m, um);
+	if (err)
+		return err;
+
+	x = xfrm_user_state_lookup(net, &um->id, attrs, &err);
+
+	if (x) {
+		struct xfrm_state *xc;
+
+		if (!x->dir) {
+			NL_SET_ERR_MSG(extack, "State direction is invalid");
+			err = -EINVAL;
+			goto error;
+		}
+
+		if (attrs[XFRMA_ENCAP]) {
+			encap = kmemdup(nla_data(attrs[XFRMA_ENCAP]),
+					sizeof(*encap), GFP_KERNEL);
+			if (!encap) {
+				err = -ENOMEM;
+				goto error;
+			}
+		}
+		if (attrs[XFRMA_OFFLOAD_DEV]) {
+			xuo = kmemdup(nla_data(attrs[XFRMA_OFFLOAD_DEV]),
+				      sizeof(*xuo), GFP_KERNEL);
+			if (!xuo) {
+				err = -ENOMEM;
+				goto error;
+			}
+		}
+		xc = xfrm_state_migrate(x, &m, encap, net, xuo, extack);
+		if (xc) {
+			xfrm_state_delete(x);
+			if (x->dir == XFRM_SA_DIR_OUT)
+				xfrm_sync_oseq(xc, x);
+			xfrm_send_migrate_state(um, encap, xuo);
+		} else {
+			if (extack && !extack->_msg)
+				    NL_SET_ERR_MSG(extack, "State migration clone failed");
+			err = -EINVAL;
+		}
+	} else {
+		NL_SET_ERR_MSG(extack, "Can not find state");
+		return err;
+	}
+error:
+	xfrm_state_put(x);
+	kfree(encap);
+	kfree(xuo);
+	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)
 {
@@ -3213,7 +3371,6 @@ static int build_migrate(struct sk_buff *skb, const struct xfrm_migrate *m,
 		return -EMSGSIZE;
 
 	pol_id = nlmsg_data(nlh);
-	/* copy data from selector, dir, and type to the pol_id */
 	memset(pol_id, 0, sizeof(*pol_id));
 	memcpy(&pol_id->sel, sel, sizeof(pol_id->sel));
 	pol_id->dir = dir;
@@ -3301,6 +3458,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);
 
@@ -3394,6 +3552,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,
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index 2c0b07f9fbbd..9cec74c317f0 100644
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -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;
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH RFC ipsec-next 4/5] xfrm: reqid is invarient in old migration
  2025-11-25  9:27 [PATCH RFC ipsec-next 0/5] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
                   ` (2 preceding siblings ...)
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 3/5] xfrm: new method XFRM_MSG_MIGRATE_STATE Antony Antony
@ 2025-11-25  9:30 ` Antony Antony
  2025-11-25  9:30 ` [PATCH RFC ipsec-next 5/5] xfrm: check that SA is in VALID state before use Antony Antony
  4 siblings, 0 replies; 7+ messages in thread
From: Antony Antony @ 2025-11-25  9:30 UTC (permalink / raw)
  To: Steffen Klassert, Herbert Xu, netdev
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, devel

During the XFRM_MSG_MIGRATE the reqid remains invariant.

Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
 net/xfrm/xfrm_state.c | 2 +-
 net/xfrm/xfrm_user.c  | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 17c3de65fb00..91e0898c458f 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2148,7 +2148,7 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
 
 	/* add state */
 	if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family) ||
-			x->props.reqid != xc->props.reqid) {
+		x->props.reqid != xc->props.reqid) {
 		/* a care is needed when the destination address or the reqid
 		 * of the state is to be updated as it is a part of triplet */
 		xfrm_state_insert(xc);
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index cc5c816f01ed..b14a11b74788 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3098,6 +3098,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
 		ma->proto = um->proto;
 		ma->mode = um->mode;
 		ma->old_reqid = um->reqid;
+		ma->new_reqid = um->reqid; /* reqid is invariant in XFRM_MSG_MIGRATE */
 
 		ma->old_family = um->old_family;
 		ma->new_family = um->new_family;
@@ -3285,7 +3286,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
 			xfrm_send_migrate_state(um, encap, xuo);
 		} else {
 			if (extack && !extack->_msg)
-				    NL_SET_ERR_MSG(extack, "State migration clone failed");
+				NL_SET_ERR_MSG(extack, "State migration clone failed");
 			err = -EINVAL;
 		}
 	} else {
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH RFC ipsec-next 5/5] xfrm: check that SA is in VALID state before use
  2025-11-25  9:27 [PATCH RFC ipsec-next 0/5] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
                   ` (3 preceding siblings ...)
  2025-11-25  9:30 ` [PATCH RFC ipsec-next 4/5] xfrm: reqid is invarient in old migration Antony Antony
@ 2025-11-25  9:30 ` Antony Antony
  4 siblings, 0 replies; 7+ messages in thread
From: Antony Antony @ 2025-11-25  9:30 UTC (permalink / raw)
  To: Steffen Klassert, Herbert Xu, netdev
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, devel

During migration, a state/SA may have been deleted or become invalid.
Since skb(s) may still hold a reference to this deleted state, it could
be reused inadvertently. Using a migrated SA could result in reusing
the same IV for AES-GCM, which must be avoided for an output SA.

Add a check to ensure the state is in XFRM_STATE_VALID before use.
This check is useful for both output and input data paths.

Call chain:
  xfrm_output_one()
    xfrm_state_check_expire()
      if (x->km.state != XFRM_STATE_VALID) ---> new check
         return -EINVAL;
      encapsulate the packet

Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
 net/xfrm/xfrm_state.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 91e0898c458f..c9c5a2daa86f 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2265,6 +2265,9 @@ EXPORT_SYMBOL(xfrm_state_update);
 
 int xfrm_state_check_expire(struct xfrm_state *x)
 {
+	if (x->km.state != XFRM_STATE_VALID)
+		return -EINVAL;
+
 	/* All counters which are needed to decide if state is expired
 	 * are handled by SW for non-packet offload modes. Simply skip
 	 * the following update and save extra boilerplate in drivers.
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH RFC ipsec-next 1/5] xfrm: migrate encap should be set in migrate call
  2025-11-25  9:29 ` [PATCH RFC ipsec-next 1/5] xfrm: migrate encap should be set in migrate call Antony Antony
@ 2025-12-01  9:21   ` Steffen Klassert
  0 siblings, 0 replies; 7+ messages in thread
From: Steffen Klassert @ 2025-12-01  9:21 UTC (permalink / raw)
  To: Antony Antony
  Cc: Herbert Xu, netdev, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, devel

On Tue, Nov 25, 2025 at 10:29:08AM +0100, Antony Antony wrote:
> The existing code does not allow migration from UDP encapsulation to
> non-encapsulation (ESP). This is useful when migrating from behind a
> NAT to no NAT, or from IPv4 with NAT to IPv6 without NAT.
> 
> With this fix, while migrating state, the existing encap will be copied
> only if the migrate call includes the encap attribute.
> 
> Which fixes tag should I add?
> Fixes: 80c9abaabf42 ("[XFRM]: Extension for dynamic update of endpoint address(es)") ?
> or
> Fixes: 4ab47d47af20 ("xfrm: extend MIGRATE with UDP encapsulation port") ?

If this is a fix, it should go to the ipsec tree, not to
ipsec-next. But is this really a fix? Do we want to have
that backported? It changes the behaviour when the original
state used encapsulation.


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2025-12-01  9:21 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-25  9:27 [PATCH RFC ipsec-next 0/5] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
2025-11-25  9:29 ` [PATCH RFC ipsec-next 1/5] xfrm: migrate encap should be set in migrate call Antony Antony
2025-12-01  9:21   ` Steffen Klassert
2025-11-25  9:29 ` [PATCH RFC ipsec-next 2/5] xfrm: rename reqid in xfrm_migrate Antony Antony
2025-11-25  9:29 ` [PATCH RFC ipsec-next 3/5] xfrm: new method XFRM_MSG_MIGRATE_STATE Antony Antony
2025-11-25  9:30 ` [PATCH RFC ipsec-next 4/5] xfrm: reqid is invarient in old migration Antony Antony
2025-11-25  9:30 ` [PATCH RFC ipsec-next 5/5] xfrm: check that SA is in VALID state before use Antony Antony

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).