* [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message
@ 2026-03-09 18:43 Antony Antony
2026-03-09 18:44 ` [PATCH ipsec-next v6 01/14] xfrm: remove redundant assignments Antony Antony
` (13 more replies)
0 siblings, 14 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:43 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
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 daemons other than the IKE daemon).
Mandatory SA selector list
The current API requires a non-empty SA selector list, which does not
reflect the 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.
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.
New migration steps: first install block policy, remove the old policy,
call XFRM_MSG_MIGRATE_STATE for each state, then re-install the
policies and remove the block policy.
If the target SA tuple (daddr, SPI, proto, family) is already
occupied, the operation r/eturns -EEXIST. In this case the original
SA is not preserved. Userspace must handle -EEXIST by
re-establishing the SA at the IKE level and manage policies.
---
v5->v6: - add mark to look up SA.
- restrict netlink attributes in new method
- address review feedback from Sabrina
- add new patch to fix existing inter-family address comparison
- add extack xfrm_state_init()
- Feedback from Yan : omit-to-inherit add migrating marks
- Drop missing __rcu annotation on nlsk, Sabrina has a better patch
Link v5: https://lore.kernel.org/all/cover.1769509130.git.antony.antony@secunet.com/
v4->v5: add synchronize after migrate and delete it inside a lock
- split xfrm_state_migrate into create and install functions
Link v4: https://lore.kernel.org/all/cover.1768811736.git.antony.antony@secunet.com/
v3->v4: add patch to fix pre-existing missing __rcu annotation on nlsk
v2->v3: - fix commit message formatting
v1->v2: dropped 6/6. That check is already there where the func is called
- merged patch 4/6 and 5/6, to fix use uninitialized value
- fix commit messages
---
Antony Antony (14):
xfrm: remove redundant assignments
xfrm: add extack to xfrm_init_state
xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
xfrm: fix NAT-related field inheritance in SA migration
xfrm: rename reqid in xfrm_migrate
xfrm: split xfrm_state_migrate into create and install functions
xfrm: check family before comparing addresses in migrate
xfrm: add state synchronization after migration
xfrm: add error messages to state migration
xfrm: move encap and xuo into struct xfrm_migrate
xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper
xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE
xfrm: docs: add documentation for XFRM_MSG_MIGRATE_STATE
Documentation/networking/xfrm/index.rst | 1 +
.../networking/xfrm/xfrm_migrate_state.rst | 129 +++++++++
include/net/xfrm.h | 74 ++++-
include/uapi/linux/xfrm.h | 13 +
net/ipv4/ipcomp.c | 2 +-
net/ipv6/ipcomp6.c | 2 +-
net/key/af_key.c | 12 +-
net/xfrm/xfrm_device.c | 2 +-
net/xfrm/xfrm_policy.c | 24 +-
net/xfrm/xfrm_state.c | 128 +++++----
net/xfrm/xfrm_user.c | 308 ++++++++++++++++++++-
security/selinux/nlmsgtab.c | 3 +-
12 files changed, 604 insertions(+), 94 deletions(-)
---
base-commit: be14d13625c9b070c33c423026b598ed65695225
change-id: migrate-state-063ee0342680
Best regards,
--
Antony Antony <<antony.antony@secunet.com>>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 01/14] xfrm: remove redundant assignments
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
@ 2026-03-09 18:44 ` Antony Antony
2026-03-09 18:45 ` [PATCH ipsec-next v6 02/14] xfrm: add extack to xfrm_init_state Antony Antony
` (12 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:44 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
These assignments are overwritten within the same function further down
commit e8961c50ee9cc ("xfrm: Refactor migration setup
during the cloning process")
x->props.family = m->new_family;
Which actually moved it in the
commit e03c3bba351f9 ("xfrm: Fix xfrm migrate issues
when address family changes")
And the initial
commit 80c9abaabf428 ("[XFRM]: Extension for dynamic
update of endpoint address(es)")
added x->props.saddr = orig->props.saddr; and
memcpy(&xc->props.saddr, &m->new_saddr, sizeof(xc->props.saddr));
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v1->v2: remove extra saddr copy, previous line
---
net/xfrm/xfrm_state.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 98b362d518363bbc100b5446d6a327b3209fed99..3ee92f93dbd2f3e3a8807b9cbd4d0a9880e5c9a4 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1980,8 +1980,6 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->props.mode = orig->props.mode;
x->props.replay_window = orig->props.replay_window;
x->props.reqid = orig->props.reqid;
- x->props.family = orig->props.family;
- x->props.saddr = orig->props.saddr;
if (orig->aalg) {
x->aalg = xfrm_algo_auth_clone(orig->aalg);
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 02/14] xfrm: add extack to xfrm_init_state
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
2026-03-09 18:44 ` [PATCH ipsec-next v6 01/14] xfrm: remove redundant assignments Antony Antony
@ 2026-03-09 18:45 ` Antony Antony
2026-03-09 18:45 ` [PATCH ipsec-next v6 03/14] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP Antony Antony
` (11 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:45 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
Add a struct extack parameter to xfrm_init_state() and pass it
through to __xfrm_init_state(). This allows validation errors detected
during state initialization to propagate meaningful error messages back
to userspace.
xfrm_state_migrate_create() now passes extack so that errors from the
XFRM_MSG_MIGRATE_STATE path are properly reported. Callers without an
extack context (af_key, ipcomp4, ipcomp6) pass NULL, preserving their
existing behaviour.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6: added this patch
---
include/net/xfrm.h | 2 +-
net/ipv4/ipcomp.c | 2 +-
net/ipv6/ipcomp6.c | 2 +-
net/key/af_key.c | 2 +-
net/xfrm/xfrm_state.c | 6 +++---
5 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 10d3edde6b2fa76af15eba562d2f583c4d689069..0c035955d87da289846fd3a9ad59ec4c8599eec9 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1774,7 +1774,7 @@ u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq);
int xfrm_init_replay(struct xfrm_state *x, struct netlink_ext_ack *extack);
u32 xfrm_state_mtu(struct xfrm_state *x, int mtu);
int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack);
-int xfrm_init_state(struct xfrm_state *x);
+int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack);
int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type);
int xfrm_input_resume(struct sk_buff *skb, int nexthdr);
int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb,
diff --git a/net/ipv4/ipcomp.c b/net/ipv4/ipcomp.c
index 9a45aed508d193de4f443a58a4d41cc8f6f93c85..b1ea2d37e8c5df0616765ef50dee7536216809ac 100644
--- a/net/ipv4/ipcomp.c
+++ b/net/ipv4/ipcomp.c
@@ -77,7 +77,7 @@ static struct xfrm_state *ipcomp_tunnel_create(struct xfrm_state *x)
memcpy(&t->mark, &x->mark, sizeof(t->mark));
t->if_id = x->if_id;
- if (xfrm_init_state(t))
+ if (xfrm_init_state(t, NULL))
goto error;
atomic_set(&t->tunnel_users, 1);
diff --git a/net/ipv6/ipcomp6.c b/net/ipv6/ipcomp6.c
index 8607569de34f3af5aa6354bec28746388b5d0c0f..b340d67eb1d907905a29f23ed2ddf63d915a343e 100644
--- a/net/ipv6/ipcomp6.c
+++ b/net/ipv6/ipcomp6.c
@@ -95,7 +95,7 @@ static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
memcpy(&t->mark, &x->mark, sizeof(t->mark));
t->if_id = x->if_id;
- if (xfrm_init_state(t))
+ if (xfrm_init_state(t, NULL))
goto error;
atomic_set(&t->tunnel_users, 1);
diff --git a/net/key/af_key.c b/net/key/af_key.c
index 571200433aa90c6fcab5779e3b0491e2ffe759bc..41afb9e82a586bfe796a9181273fd92a055166be 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -1283,7 +1283,7 @@ static struct xfrm_state * pfkey_msg2xfrm_state(struct net *net,
}
}
- err = xfrm_init_state(x);
+ err = xfrm_init_state(x, NULL);
if (err)
goto out;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 3ee92f93dbd2f3e3a8807b9cbd4d0a9880e5c9a4..86f21a19a0eed04325b2ea26b5fe4fbb3527b2e4 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2143,7 +2143,7 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
if (!xc)
return NULL;
- if (xfrm_init_state(xc) < 0)
+ if (xfrm_init_state(xc, extack) < 0)
goto error;
/* configure the hardware if offload is requested */
@@ -3236,11 +3236,11 @@ int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
EXPORT_SYMBOL(__xfrm_init_state);
-int xfrm_init_state(struct xfrm_state *x)
+int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
{
int err;
- err = __xfrm_init_state(x, NULL);
+ err = __xfrm_init_state(x, extack);
if (err)
return err;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 03/14] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
2026-03-09 18:44 ` [PATCH ipsec-next v6 01/14] xfrm: remove redundant assignments Antony Antony
2026-03-09 18:45 ` [PATCH ipsec-next v6 02/14] xfrm: add extack to xfrm_init_state Antony Antony
@ 2026-03-09 18:45 ` Antony Antony
2026-03-09 18:45 ` [PATCH ipsec-next v6 04/14] xfrm: fix NAT-related field inheritance in SA migration Antony Antony
` (10 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:45 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
The current code prevents migrating an SA from UDP encapsulation to
plain ESP. This is needed when moving from a NATed path to a non-NATed
one, for example when switching from IPv4+NAT to IPv6.
Only copy the existing encapsulation during migration if the encap
attribute is explicitly provided.
Note: PF_KEY's SADB_X_MIGRATE always passes encap=NULL and never
supported encapsulation in migration. PF_KEY is deprecated and was
in feature freeze when UDP encapsulation was added to xfrm.
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 86f21a19a0eed04325b2ea26b5fe4fbb3527b2e4..20ebd10dbee5cf0ee29c81c9cc3839fb031597c2 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.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 04/14] xfrm: fix NAT-related field inheritance in SA migration
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (2 preceding siblings ...)
2026-03-09 18:45 ` [PATCH ipsec-next v6 03/14] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP Antony Antony
@ 2026-03-09 18:45 ` Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 05/14] xfrm: rename reqid in xfrm_migrate Antony Antony
` (9 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:45 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
During SA migration via xfrm_state_clone_and_setup(),
nat_keepalive_interval was silently dropped and never copied to the new
SA. mapping_maxage was unconditionally copied even when migrating to a
non-encapsulated SA.
Both fields are only meaningful when UDP encapsulation (NAT-T) is in
use. Move mapping_maxage and add nat_keepalive_interval inside the
existing if (encap) block, so both are inherited when migrating with
encapsulation and correctly absent when migrating without it.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6: added this patch
---
net/xfrm/xfrm_state.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 20ebd10dbee5cf0ee29c81c9cc3839fb031597c2..defa753b26ae09f4ed66b8e240e8081c28b16e63 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2012,6 +2012,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->encap = kmemdup(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;
}
if (orig->security)
@@ -2046,7 +2048,6 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->km.seq = orig->km.seq;
x->replay = orig->replay;
x->preplay = orig->preplay;
- x->mapping_maxage = orig->mapping_maxage;
x->lastused = orig->lastused;
x->new_mapping = 0;
x->new_mapping_sport = 0;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 05/14] xfrm: rename reqid in xfrm_migrate
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (3 preceding siblings ...)
2026-03-09 18:45 ` [PATCH ipsec-next v6 04/14] xfrm: fix NAT-related field inheritance in SA migration Antony Antony
@ 2026-03-09 18:46 ` Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 06/14] xfrm: split xfrm_state_migrate into create and install functions Antony Antony
` (8 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:46 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
In preparation for a later patch in this series s/reqid/old_reqid/.
No functional change.
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 0c035955d87da289846fd3a9ad59ec4c8599eec9..368b1dc22e5cc376cbe96a7f6fb8cddc2e1cec87 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -685,7 +685,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 41afb9e82a586bfe796a9181273fd92a055166be..ccd2e2d65688ffa1aad8d4b4fb1292894350093c 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 7bcb6583e84c0f0f6f8c37bb2a4a7192cf05dc86..62218b52fd35ee5d630efac5803a151a41c194a0 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 defa753b26ae09f4ed66b8e240e8081c28b16e63..a94f82f1354e4522673c9ed8d89cd9b9879c42ab 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2081,14 +2081,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 403b5ecac2c544111b0c2b3268d288c76d7aea81..26b82d94acc1519fca1e4ef85b0f2a814e6f5d8c 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3087,7 +3087,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;
@@ -3170,7 +3170,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.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 06/14] xfrm: split xfrm_state_migrate into create and install functions
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (4 preceding siblings ...)
2026-03-09 18:46 ` [PATCH ipsec-next v6 05/14] xfrm: rename reqid in xfrm_migrate Antony Antony
@ 2026-03-09 18:46 ` Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 07/14] xfrm: check family before comparing addresses in migrate Antony Antony
` (7 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:46 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
To prepare for subsequent patches, split
xfrm_state_migrate() into two functions:
- xfrm_state_migrate_create(): creates the migrated state
- xfrm_state_migrate_install(): installs it into the state table
splitting will help to avoid SN/IV reuse when migrating AEAD SA.
And add const whenever possible.
No functional change.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v4->v5: - added this patch
---
include/net/xfrm.h | 11 ++++++++
net/xfrm/xfrm_state.c | 73 +++++++++++++++++++++++++++++++++++++--------------
2 files changed, 64 insertions(+), 20 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 368b1dc22e5cc376cbe96a7f6fb8cddc2e1cec87..4137986f15e241bf0bda2003be1da8deb5a58f0c 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1895,6 +1895,17 @@ int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
const struct xfrm_encap_tmpl *encap);
struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net,
u32 if_id);
+struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
+ const struct xfrm_migrate *m,
+ const struct xfrm_encap_tmpl *encap,
+ struct net *net,
+ struct xfrm_user_offload *xuo,
+ struct netlink_ext_ack *extack);
+int xfrm_state_migrate_install(const struct xfrm_state *x,
+ struct xfrm_state *xc,
+ const struct xfrm_migrate *m,
+ struct xfrm_user_offload *xuo,
+ struct netlink_ext_ack *extack);
struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
struct xfrm_migrate *m,
struct xfrm_encap_tmpl *encap,
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index a94f82f1354e4522673c9ed8d89cd9b9879c42ab..9060a6c399fd2befc09751f106e0f138990c9a2a 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1966,8 +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)
+ const struct xfrm_encap_tmpl *encap,
+ const struct xfrm_migrate *m)
{
struct net *net = xs_net(orig);
struct xfrm_state *x = xfrm_state_alloc(net);
@@ -2125,12 +2125,12 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
}
EXPORT_SYMBOL(xfrm_migrate_state_find);
-struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
- struct xfrm_migrate *m,
- struct xfrm_encap_tmpl *encap,
- struct net *net,
- struct xfrm_user_offload *xuo,
- struct netlink_ext_ack *extack)
+struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
+ const struct xfrm_migrate *m,
+ const struct xfrm_encap_tmpl *encap,
+ struct net *net,
+ struct xfrm_user_offload *xuo,
+ struct netlink_ext_ack *extack)
{
struct xfrm_state *xc;
@@ -2145,24 +2145,57 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
if (xuo && xfrm_dev_state_add(net, xc, xuo, extack))
goto error;
- /* add state */
+ return xc;
+error:
+ xc->km.state = XFRM_STATE_DEAD;
+ xfrm_state_put(xc);
+ return NULL;
+}
+EXPORT_SYMBOL(xfrm_state_migrate_create);
+
+int xfrm_state_migrate_install(const struct xfrm_state *x,
+ struct xfrm_state *xc,
+ const struct xfrm_migrate *m,
+ struct xfrm_user_offload *xuo,
+ struct netlink_ext_ack *extack)
+{
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 */
+ /*
+ * Care is needed when the destination address
+ * 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)
- goto error_add;
+ if (xfrm_state_add(xc) < 0) {
+ if (xuo)
+ xfrm_dev_state_delete(xc);
+ xc->km.state = XFRM_STATE_DEAD;
+ xfrm_state_put(xc);
+ return -EEXIST;
+ }
}
+ return 0;
+}
+EXPORT_SYMBOL(xfrm_state_migrate_install);
+
+struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
+ struct xfrm_migrate *m,
+ struct xfrm_encap_tmpl *encap,
+ struct net *net,
+ struct xfrm_user_offload *xuo,
+ struct netlink_ext_ack *extack)
+{
+ struct xfrm_state *xc;
+
+ xc = xfrm_state_migrate_create(x, m, encap, net, xuo, extack);
+ if (!xc)
+ return NULL;
+
+ if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0)
+ return NULL;
+
return xc;
-error_add:
- if (xuo)
- xfrm_dev_state_delete(xc);
-error:
- xc->km.state = XFRM_STATE_DEAD;
- xfrm_state_put(xc);
- return NULL;
}
EXPORT_SYMBOL(xfrm_state_migrate);
#endif
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 07/14] xfrm: check family before comparing addresses in migrate
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (5 preceding siblings ...)
2026-03-09 18:46 ` [PATCH ipsec-next v6 06/14] xfrm: split xfrm_state_migrate into create and install functions Antony Antony
@ 2026-03-09 18:46 ` Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 08/14] xfrm: add state synchronization after migration Antony Antony
` (6 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:46 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
When migrating between different address families, xfrm_addr_equal()
cannot meaningfully compare addresses, different lengths.
Only call xfrm_addr_equal() when families match, and take
the xfrm_state_insert() path when addresses are equal.
Fixes: 80c9abaabf42 ("[XFRM]: Extension for dynamic update of endpoint address(es)")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6 added this patch
---
net/xfrm/xfrm_state.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 9060a6c399fd2befc09751f106e0f138990c9a2a..f7bcf14223584bd7d779a2521a9d5b0bf7946640 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2159,10 +2159,11 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack)
{
- if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
+ if (m->new_family == m->old_family &&
+ xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
/*
- * Care is needed when the destination address
- * of the state is to be updated as it is a part of triplet.
+ * Care is needed when the destination address of the state is
+ * to be updated as it is a part of triplet.
*/
xfrm_state_insert(xc);
} else {
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 08/14] xfrm: add state synchronization after migration
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (6 preceding siblings ...)
2026-03-09 18:46 ` [PATCH ipsec-next v6 07/14] xfrm: check family before comparing addresses in migrate Antony Antony
@ 2026-03-09 18:46 ` Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 09/14] xfrm: add error messages to state migration Antony Antony
` (5 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:46 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
Add xfrm_migrate_sync() to synchronize curlft and replay state after
state installation, this can be called under lock without memory
allocation. In preparation for a subsequent patch in this series.
This ensures the migrated state captures the latest lifetime counters
and replay state from the original after installation completes.
Within the same lock, the original xfrm state is deleted.
No functional change.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6: - move the sync before install to sync overwriting
Link:
https://lore.kernel.org/all/58f43c2cf105ed9ab4ac6807c8bcdbe2764f13c3.1769509131.git.antony.antony@secunet.com/
[v5]
v4->v5: - added this patch
---
include/net/xfrm.h | 46 +++++++++++++++++++++++++++++++++++++---------
net/xfrm/xfrm_state.c | 12 +++++-------
2 files changed, 42 insertions(+), 16 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4137986f15e241bf0bda2003be1da8deb5a58f0c..be22c26e4661b9cd5613878b7cc6fac20712ffc2 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -2024,23 +2024,51 @@ 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)
+ const struct xfrm_state *orig)
{
+ /* Counters synced later in xfrm_replay_sync() */
- x->replay_esn = kmemdup(orig->replay_esn,
+ x->replay = orig->replay;
+ x->preplay = orig->preplay;
+
+ if (orig->replay_esn) {
+ x->replay_esn = kmemdup(orig->replay_esn,
xfrm_replay_state_esn_len(orig->replay_esn),
GFP_KERNEL);
- if (!x->replay_esn)
- return -ENOMEM;
- x->preplay_esn = kmemdup(orig->preplay_esn,
- xfrm_replay_state_esn_len(orig->preplay_esn),
- GFP_KERNEL);
- if (!x->preplay_esn)
- return -ENOMEM;
+ if (!x->replay_esn)
+ return -ENOMEM;
+ x->preplay_esn = kmemdup(orig->preplay_esn,
+ xfrm_replay_state_esn_len(orig->preplay_esn),
+ GFP_KERNEL);
+ if (!x->preplay_esn)
+ return -ENOMEM;
+ }
return 0;
}
+static inline void xfrm_replay_sync(struct xfrm_state *x, const struct xfrm_state *orig)
+{
+ x->replay = orig->replay;
+ x->preplay = orig->preplay;
+
+ if (orig->replay_esn) {
+ memcpy(x->replay_esn, orig->replay_esn,
+ xfrm_replay_state_esn_len(orig->replay_esn));
+
+ memcpy(x->preplay_esn, orig->preplay_esn,
+ xfrm_replay_state_esn_len(orig->preplay_esn));
+ }
+}
+
+static inline void xfrm_migrate_sync(struct xfrm_state *x,
+ const struct xfrm_state *orig)
+{
+ /* called under lock so no race conditions or mallocs allowed */
+ memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft));
+ xfrm_replay_sync(x, orig);
+}
+
static inline struct xfrm_algo_aead *xfrm_algo_aead_clone(struct xfrm_algo_aead *orig)
{
return kmemdup(orig, aead_len(orig), GFP_KERNEL);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index f7bcf14223584bd7d779a2521a9d5b0bf7946640..85a5567af5b33fc70c48a7205b334b2ab3c0ab81 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2027,10 +2027,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
goto error;
}
- if (orig->replay_esn) {
- if (xfrm_replay_clone(x, orig))
- goto error;
- }
+ 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));
@@ -2043,11 +2041,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->tfcpad = orig->tfcpad;
x->replay_maxdiff = orig->replay_maxdiff;
x->replay_maxage = orig->replay_maxage;
- memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft));
x->km.state = orig->km.state;
x->km.seq = orig->km.seq;
- x->replay = orig->replay;
- x->preplay = orig->preplay;
x->lastused = orig->lastused;
x->new_mapping = 0;
x->new_mapping_sport = 0;
@@ -2193,9 +2188,12 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
if (!xc)
return NULL;
+ xfrm_migrate_sync(xc, x);
+
if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0)
return NULL;
+
return xc;
}
EXPORT_SYMBOL(xfrm_state_migrate);
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 09/14] xfrm: add error messages to state migration
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (7 preceding siblings ...)
2026-03-09 18:46 ` [PATCH ipsec-next v6 08/14] xfrm: add state synchronization after migration Antony Antony
@ 2026-03-09 18:46 ` Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 10/14] xfrm: move encap and xuo into struct xfrm_migrate Antony Antony
` (4 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:46 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
Add descriptive(extack) error messages for all error paths
in state migration. This improves diagnostics by
providing clear feedback when migration fails.
After xfrm_init_state() use NL_SET_ERR_MSG_WEAK() as fallback for
error paths not yet propagating extack e.g. mode_cbs->init_state()
No functional change.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
-
v5->v6: - in case dev_state_add() extack already set
- after xfrm_init_state() use NL_SET_ERR_MSG_WEAK() as fallback
Link: https://lore.kernel.org/all/dd1d5b341323d6e0f93057c214adf4fc51c3cbd1.1769509131.git.antony.antony@secunet.com [v5]
v4->v5: - added this patch
---
net/xfrm/xfrm_state.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 85a5567af5b33fc70c48a7205b334b2ab3c0ab81..96f700dedb7ede063ea6a7951be5b05cd14451b4 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2130,11 +2130,15 @@ struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
struct xfrm_state *xc;
xc = xfrm_state_clone_and_setup(x, encap, m);
- if (!xc)
+ if (!xc) {
+ NL_SET_ERR_MSG(extack, "Failed to clone and setup state");
return NULL;
+ }
- if (xfrm_init_state(xc, extack) < 0)
+ if (xfrm_init_state(xc, extack) < 0) {
+ NL_SET_ERR_MSG_WEAK(extack, "Failed to initialize migrated state");
goto error;
+ }
/* configure the hardware if offload is requested */
if (xuo && xfrm_dev_state_add(net, xc, xuo, extack))
@@ -2163,6 +2167,7 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
xfrm_state_insert(xc);
} else {
if (xfrm_state_add(xc) < 0) {
+ NL_SET_ERR_MSG(extack, "Failed to add migrated state");
if (xuo)
xfrm_dev_state_delete(xc);
xc->km.state = XFRM_STATE_DEAD;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 10/14] xfrm: move encap and xuo into struct xfrm_migrate
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (8 preceding siblings ...)
2026-03-09 18:46 ` [PATCH ipsec-next v6 09/14] xfrm: add error messages to state migration Antony Antony
@ 2026-03-09 18:46 ` Antony Antony
2026-03-09 18:47 ` [PATCH ipsec-next v6 11/14] xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper Antony Antony
` (3 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:46 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
In preparation for an upcoming patch, move the xfrm_encap_tmpl and
xfrm_user_offload pointers from separate parameters into struct
xfrm_migrate, reducing the parameter count of
xfrm_state_migrate_create(), xfrm_state_migrate_install(), and
xfrm_state_migrate().
The fields are placed after the four xfrm_address_t members where
the struct is naturally 8-byte aligned, avoiding padding.
No functional change.
Tested-by: Yan Yan <evitayan@google.com>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6 : added this patch.
---
include/net/xfrm.h | 7 ++-----
net/xfrm/xfrm_policy.c | 4 +++-
net/xfrm/xfrm_state.c | 20 +++++++-------------
3 files changed, 12 insertions(+), 19 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index be22c26e4661b9cd5613878b7cc6fac20712ffc2..4b29ab92c2a73533795825034cd5e4e12538cb83 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -682,6 +682,8 @@ struct xfrm_migrate {
xfrm_address_t old_saddr;
xfrm_address_t new_daddr;
xfrm_address_t new_saddr;
+ struct xfrm_encap_tmpl *encap;
+ struct xfrm_user_offload *xuo;
u8 proto;
u8 mode;
u16 reserved;
@@ -1897,20 +1899,15 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n
u32 if_id);
struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
const struct xfrm_migrate *m,
- const struct xfrm_encap_tmpl *encap,
struct net *net,
- struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack);
int xfrm_state_migrate_install(const struct xfrm_state *x,
struct xfrm_state *xc,
const struct xfrm_migrate *m,
- struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack);
struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
struct xfrm_migrate *m,
- struct xfrm_encap_tmpl *encap,
struct net *net,
- struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack);
int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
struct xfrm_migrate *m, int num_bundles,
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 62218b52fd35ee5d630efac5803a151a41c194a0..0b5c7b51183a9ddf17270da6367b478380f2fbf0 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4672,7 +4672,9 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
if ((x = xfrm_migrate_state_find(mp, net, if_id))) {
x_cur[nx_cur] = x;
nx_cur++;
- xc = xfrm_state_migrate(x, mp, encap, net, xuo, extack);
+ mp->encap = encap;
+ mp->xuo = xuo;
+ xc = xfrm_state_migrate(x, mp, net, extack);
if (xc) {
x_new[nx_new] = xc;
nx_new++;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 96f700dedb7ede063ea6a7951be5b05cd14451b4..8f07e6294c472cd92baa72c9e15358917faed008 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1966,7 +1966,6 @@ 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,
- const struct xfrm_encap_tmpl *encap,
const struct xfrm_migrate *m)
{
struct net *net = xs_net(orig);
@@ -2008,8 +2007,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
}
x->props.calgo = orig->props.calgo;
- if (encap) {
- x->encap = kmemdup(encap, sizeof(*x->encap), GFP_KERNEL);
+ if (m->encap) {
+ x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL);
if (!x->encap)
goto error;
x->mapping_maxage = orig->mapping_maxage;
@@ -2122,14 +2121,12 @@ EXPORT_SYMBOL(xfrm_migrate_state_find);
struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
const struct xfrm_migrate *m,
- const struct xfrm_encap_tmpl *encap,
struct net *net,
- struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack)
{
struct xfrm_state *xc;
- xc = xfrm_state_clone_and_setup(x, encap, m);
+ xc = xfrm_state_clone_and_setup(x, m);
if (!xc) {
NL_SET_ERR_MSG(extack, "Failed to clone and setup state");
return NULL;
@@ -2141,7 +2138,7 @@ struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
}
/* configure the hardware if offload is requested */
- if (xuo && xfrm_dev_state_add(net, xc, xuo, extack))
+ if (m->xuo && xfrm_dev_state_add(net, xc, m->xuo, extack))
goto error;
return xc;
@@ -2155,7 +2152,6 @@ EXPORT_SYMBOL(xfrm_state_migrate_create);
int xfrm_state_migrate_install(const struct xfrm_state *x,
struct xfrm_state *xc,
const struct xfrm_migrate *m,
- struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack)
{
if (m->new_family == m->old_family &&
@@ -2168,7 +2164,7 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
} else {
if (xfrm_state_add(xc) < 0) {
NL_SET_ERR_MSG(extack, "Failed to add migrated state");
- if (xuo)
+ if (m->xuo)
xfrm_dev_state_delete(xc);
xc->km.state = XFRM_STATE_DEAD;
xfrm_state_put(xc);
@@ -2182,20 +2178,18 @@ EXPORT_SYMBOL(xfrm_state_migrate_install);
struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x,
struct xfrm_migrate *m,
- struct xfrm_encap_tmpl *encap,
struct net *net,
- struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack)
{
struct xfrm_state *xc;
- xc = xfrm_state_migrate_create(x, m, encap, net, xuo, extack);
+ xc = xfrm_state_migrate_create(x, m, net, extack);
if (!xc)
return NULL;
xfrm_migrate_sync(xc, x);
- if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0)
+ if (xfrm_state_migrate_install(x, xc, m, extack) < 0)
return NULL;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 11/14] xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (9 preceding siblings ...)
2026-03-09 18:46 ` [PATCH ipsec-next v6 10/14] xfrm: move encap and xuo into struct xfrm_migrate Antony Antony
@ 2026-03-09 18:47 ` Antony Antony
2026-03-09 18:47 ` [PATCH ipsec-next v6 12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Antony Antony
` (2 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:47 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
Extract verify_mtimer_thresh() to consolidate the XFRMA_MTIMER_THRESH
validation logic shared between the add_sa and upcoming patch.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
-
v5->v6: added this patch
---
net/xfrm/xfrm_user.c | 29 ++++++++++++++++++-----------
1 file changed, 18 insertions(+), 11 deletions(-)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 26b82d94acc1519fca1e4ef85b0f2a814e6f5d8c..fe0cf824f072b09e3267dc5b8aab9b5b0d68c968 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -239,6 +239,22 @@ static inline int verify_replay(struct xfrm_usersa_info *p,
return 0;
}
+static int verify_mtimer_thresh(bool has_encap, u8 dir,
+ struct netlink_ext_ack *extack)
+{
+ if (!has_encap) {
+ NL_SET_ERR_MSG(extack,
+ "MTIMER_THRESH requires encapsulation");
+ return -EINVAL;
+ }
+ if (dir == XFRM_SA_DIR_OUT) {
+ NL_SET_ERR_MSG(extack,
+ "MTIMER_THRESH should not be set on output SA");
+ return -EINVAL;
+ }
+ return 0;
+}
+
static int verify_newsa_info(struct xfrm_usersa_info *p,
struct nlattr **attrs,
struct netlink_ext_ack *extack)
@@ -446,18 +462,9 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
err = 0;
if (attrs[XFRMA_MTIMER_THRESH]) {
- if (!attrs[XFRMA_ENCAP]) {
- NL_SET_ERR_MSG(extack, "MTIMER_THRESH attribute can only be set on ENCAP states");
- err = -EINVAL;
- goto out;
- }
-
- if (sa_dir == XFRM_SA_DIR_OUT) {
- NL_SET_ERR_MSG(extack,
- "MTIMER_THRESH attribute should not be set on output SA");
- err = -EINVAL;
+ err = verify_mtimer_thresh(!!attrs[XFRMA_ENCAP], sa_dir, extack);
+ if (err)
goto out;
- }
}
if (sa_dir == XFRM_SA_DIR_OUT) {
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (10 preceding siblings ...)
2026-03-09 18:47 ` [PATCH ipsec-next v6 11/14] xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper Antony Antony
@ 2026-03-09 18:47 ` Antony Antony
2026-03-11 19:57 ` [ipsec-next,v6,12/14] " Simon Horman
2026-03-09 18:47 ` [PATCH ipsec-next v6 13/14] xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE Antony Antony
2026-03-09 18:47 ` [PATCH ipsec-next v6 14/14] xfrm: docs: add documentation " Antony Antony
13 siblings, 1 reply; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:47 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
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>
---
v5->v6: - (Feedback from Sabrina's review)
- reqid change: use xfrm_state_add, not xfrm_state_insert
- encap and xuo: use nla_data() directly, no kmemdup needed
- notification failure is non-fatal: set extack warning, return 0
- drop state direction, x->dir, check, not required
- reverse xmas tree local variable ordering
- use NL_SET_ERR_MSG_WEAK for clone failure message
- fix implicit padding in xfrm_user_migrate_state uapi struct
- support XFRMA_SET_MARK/XFRMA_SET_MARK_MASK in XFRM_MSG_MIGRATE_STATE
Link: https://lore.kernel.org/all/b7b1bee9456ac4ada8941c93c2cc17f07d0b1912.1769509131.git.antony.antony@secunet.com/ [v5]
v4->v5: - set portid, seq in XFRM_MSG_MIGRATE_STATE netlink notification
- rename error label to out for clarity
- add locking and synchronize after cloning
- change some if(x) to if(!x) for clarity
- call __xfrm_state_delete() inside the lock
- return error from xfrm_send_migrate_state() instead of always returning 0
Link: https://lore.kernel.org/all/b134abaf18390d1bcdd59d2192687bac0e2c2710.1768811736.git.antony.antony@secunet.com/ [v4]
v3->v4: preserve reqid invariant for each state migrated
v2->v3: free the skb on the error path
v1->v2: merged next patch here to fix use uninitialized value
- removed unnecessary inline
- added const when possible
---
include/net/xfrm.h | 14 ++-
include/uapi/linux/xfrm.h | 13 +++
net/xfrm/xfrm_device.c | 2 +-
net/xfrm/xfrm_policy.c | 18 +++-
net/xfrm/xfrm_state.c | 16 +--
net/xfrm/xfrm_user.c | 251 +++++++++++++++++++++++++++++++++++++++++++-
security/selinux/nlmsgtab.c | 3 +-
7 files changed, 302 insertions(+), 15 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4b29ab92c2a73533795825034cd5e4e12538cb83..ae2a1cf2311af55d3c436ed069272a1d19f01f97 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -683,11 +683,17 @@ struct xfrm_migrate {
xfrm_address_t new_daddr;
xfrm_address_t new_saddr;
struct xfrm_encap_tmpl *encap;
- struct xfrm_user_offload *xuo;
+ struct xfrm_user_offload xuo;
+ struct xfrm_mark old_mark;
+ struct xfrm_mark new_mark;
+ struct xfrm_mark smark;
u8 proto;
u8 mode;
u16 reserved;
u32 old_reqid;
+ u32 new_reqid;
+ u32 nat_keepalive_interval;
+ u32 mapping_maxage;
u16 old_family;
u16 new_family;
};
@@ -2104,7 +2110,7 @@ void xfrm_dev_resume(struct sk_buff *skb);
void xfrm_dev_backlog(struct softnet_data *sd);
struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t features, bool *again);
int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
- struct xfrm_user_offload *xuo,
+ const struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack);
int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp,
struct xfrm_user_offload *xuo, u8 dir,
@@ -2175,7 +2181,9 @@ static inline struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_fea
return skb;
}
-static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack)
+static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
+ const struct xfrm_user_offload *xuo,
+ struct netlink_ext_ack *extack)
{
return 0;
}
diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h
index a23495c0e0a109c428080d207f59f7581336537f..eed74e1c4061f167e0898b06e111f32a56ba9c2b 100644
--- a/include/uapi/linux/xfrm.h
+++ b/include/uapi/linux/xfrm.h
@@ -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,16 @@ 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;
+ __u16 new_family;
+ __u16 reserved;
+ __u32 new_reqid;
+ struct xfrm_mark old_mark;
+};
+
struct xfrm_user_mapping {
struct xfrm_usersa_id id;
__u32 reqid;
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 52ae0e034d29e265af5eeaf03df8fd973d8dbf9f..9d4c1addb98f61dd4a19177aedb673749262060c 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -229,7 +229,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
- struct xfrm_user_offload *xuo,
+ const struct xfrm_user_offload *xuo,
struct netlink_ext_ack *extack)
{
int err;
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 0b5c7b51183a9ddf17270da6367b478380f2fbf0..b24cb5b7509f5533db3329e680c85c6413e9e987 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4635,6 +4635,19 @@ 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.
+ */
+static void xfrm_migrate_copy_old(struct xfrm_migrate *mp,
+ const struct xfrm_state *x)
+{
+ 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,
@@ -4673,7 +4686,10 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
x_cur[nx_cur] = x;
nx_cur++;
mp->encap = encap;
- mp->xuo = xuo;
+ if (xuo)
+ mp->xuo = *xuo;
+ xfrm_migrate_copy_old(mp, x);
+
xc = xfrm_state_migrate(x, mp, net, extack);
if (xc) {
x_new[nx_new] = xc;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 8f07e6294c472cd92baa72c9e15358917faed008..ddafbae085f1388cc9eda53d5e8cadf658be5cdd 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1978,7 +1978,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;
if (orig->aalg) {
x->aalg = xfrm_algo_auth_clone(orig->aalg);
@@ -2011,8 +2010,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 +2028,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;
+
+ x->props.smark = m->smark;
x->props.flags = orig->props.flags;
x->props.extra_flags = orig->props.extra_flags;
@@ -2053,7 +2053,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));
@@ -2138,7 +2138,7 @@ struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x,
}
/* configure the hardware if offload is requested */
- if (m->xuo && xfrm_dev_state_add(net, xc, m->xuo, extack))
+ if (m->xuo.ifindex && xfrm_dev_state_add(net, xc, &m->xuo, extack))
goto error;
return xc;
@@ -2164,7 +2164,7 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
} else {
if (xfrm_state_add(xc) < 0) {
NL_SET_ERR_MSG(extack, "Failed to add migrated state");
- if (m->xuo)
+ if (m->xuo.ifindex)
xfrm_dev_state_delete(xc);
xc->km.state = XFRM_STATE_DEAD;
xfrm_state_put(xc);
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index fe0cf824f072b09e3267dc5b8aab9b5b0d68c968..f3b485c800a3483b1515218805b1c2edcdb0c512 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1318,7 +1318,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;
@@ -3059,6 +3059,22 @@ static int xfrm_add_acquire(struct sk_buff *skb, struct nlmsghdr *nlh,
}
#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;
+}
+
static int copy_from_user_migrate(struct xfrm_migrate *ma,
struct xfrm_kmaddress *k,
struct nlattr **attrs, int *num,
@@ -3161,7 +3177,238 @@ 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 *um,
+ const struct xfrm_migrate *m,
+ bool new_mark_set, bool offload_set,
+ 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;
+
+ if (m->encap) {
+ err = nla_put(skb, XFRMA_ENCAP, sizeof(*m->encap), m->encap);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (offload_set || m->xuo.ifindex) {
+ err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(m->xuo), &m->xuo);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (new_mark_set) {
+ 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) +
+ nla_total_size(sizeof(struct xfrm_user_offload)) +
+ nla_total_size(sizeof(struct xfrm_mark)) +
+ nla_total_size(sizeof(u32)) * 2 + /* SET_MARK + SET_MARK_MASK */
+ (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(const struct xfrm_user_migrate_state *um,
+ const struct xfrm_migrate *m,
+ bool new_mark_set, bool offload_set,
+ u8 dir, u32 portid, u32 seq)
+{
+ int err;
+ struct sk_buff *skb;
+ struct net *net = &init_net;
+
+ skb = nlmsg_new(xfrm_migrate_state_msgsize(m, dir), GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ err = build_migrate_state(skb, um, m, new_mark_set, offload_set,
+ 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_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;
+ }
+
+ 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 (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 (attrs[XFRMA_NAT_KEEPALIVE_INTERVAL] &&
+ nla_get_u32(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL]) && !m.encap) {
+ NL_SET_ERR_MSG(extack,
+ "NAT_KEEPALIVE_INTERVAL requires encapsulation");
+ err = -EINVAL;
+ goto out;
+ }
+
+ /* absent copies offload; ifindex=0 removes it */
+ if (attrs[XFRMA_OFFLOAD_DEV]) {
+ nla_memcpy(&m.xuo, attrs[XFRMA_OFFLOAD_DEV], sizeof(m.xuo));
+ } else if (x->xso.dev) {
+ m.xuo.ifindex = x->xso.dev->ifindex;
+ if (x->xso.dir == XFRM_DEV_OFFLOAD_IN)
+ m.xuo.flags = XFRM_OFFLOAD_INBOUND;
+ if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
+ m.xuo.flags |= XFRM_OFFLOAD_PACKET;
+ }
+
+ if (attrs[XFRMA_MARK])
+ m.new_mark = *(struct xfrm_mark *)nla_data(attrs[XFRMA_MARK]);
+ else
+ m.new_mark = m.old_mark;
+
+ if (attrs[XFRMA_SET_MARK])
+ xfrm_smark_init(attrs, &m.smark);
+ else
+ m.smark = x->props.smark;
+
+ m.mapping_maxage = attrs[XFRMA_MTIMER_THRESH] ?
+ nla_get_u32(attrs[XFRMA_MTIMER_THRESH]) : x->mapping_maxage;
+ m.nat_keepalive_interval = attrs[XFRMA_NAT_KEEPALIVE_INTERVAL] ?
+ nla_get_u32(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL]) :
+ x->nat_keepalive_interval;
+
+ 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);
+ 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) {
+ /*
+ * In this rare case both the old SA and the new SA
+ * will disappear.
+ * Alternatives risk duplicate SN/IV usage which must not occur.
+ * Userspace must handle this error, -EEXIST.
+ */
+ goto out;
+ }
+
+ /* Restore encap cleared by sentinel (type=0) during migration. */
+ if (attrs[XFRMA_ENCAP])
+ m.encap = nla_data(attrs[XFRMA_ENCAP]);
+
+ err = xfrm_send_migrate_state(um, &m, !!attrs[XFRMA_MARK],
+ !!attrs[XFRMA_OFFLOAD_DEV],
+ 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;
+}
+
#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)
{
@@ -3314,6 +3561,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);
@@ -3407,6 +3655,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 2c0b07f9fbbd0b38edfd063020f09933c0f1ecb3..655d2616c9d2ab8af51b427499434d96f0b4bfd2 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.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 13/14] xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (11 preceding siblings ...)
2026-03-09 18:47 ` [PATCH ipsec-next v6 12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Antony Antony
@ 2026-03-09 18:47 ` Antony Antony
2026-03-09 18:47 ` [PATCH ipsec-next v6 14/14] xfrm: docs: add documentation " Antony Antony
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:47 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
Only accept XFRMA used in this method, reject the rest.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6 added this patch
---
net/xfrm/xfrm_user.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index f3b485c800a3483b1515218805b1c2edcdb0c512..386e2d181c999dd5b558428c3f11b51a6a5d886f 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3687,6 +3687,30 @@ static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
}
}
+ if (type == XFRM_MSG_MIGRATE_STATE) {
+ int i;
+
+ for (i = 0; i <= XFRMA_MAX; i++) {
+ if (!attrs[i])
+ continue;
+
+ switch (i) {
+ case XFRMA_MARK:
+ case XFRMA_ENCAP:
+ case XFRMA_OFFLOAD_DEV:
+ case XFRMA_SET_MARK:
+ case XFRMA_SET_MARK_MASK:
+ case XFRMA_MTIMER_THRESH:
+ case XFRMA_NAT_KEEPALIVE_INTERVAL:
+ break;
+ default:
+ NL_SET_ERR_MSG_ATTR(extack, attrs[i],
+ "Unsupported attribute in XFRM_MSG_MIGRATE_STATE");
+ return -EINVAL;
+ }
+ }
+ }
+
return 0;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH ipsec-next v6 14/14] xfrm: docs: add documentation for XFRM_MSG_MIGRATE_STATE
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
` (12 preceding siblings ...)
2026-03-09 18:47 ` [PATCH ipsec-next v6 13/14] xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE Antony Antony
@ 2026-03-09 18:47 ` Antony Antony
13 siblings, 0 replies; 18+ messages in thread
From: Antony Antony @ 2026-03-09 18:47 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Masahide NAKAMURA, Paul Moore, Stephen Smalley,
Ondrej Mosnacek, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, selinux, linux-doc, Chiachang Wang, Yan Yan,
devel
Add documentation for the new XFRM_MSG_MIGRATE_STATE netlink message,
which migrates a single SA identified by SPI and mark without involving
policies.
The document covers the motivation and design differences from the
existing XFRM_MSG_MIGRATE, the SA lookup mechanism, supported attributes
with their omit-to-inherit semantics, and usage examples.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v5->v6: added this patch
---
Documentation/networking/xfrm/index.rst | 1 +
.../networking/xfrm/xfrm_migrate_state.rst | 129 +++++++++++++++++++++
2 files changed, 130 insertions(+)
diff --git a/Documentation/networking/xfrm/index.rst b/Documentation/networking/xfrm/index.rst
index 7d866da836fe76642d36d8bf9a9c11757427453f..90191848f8db907148d610e14572f4ba43390114 100644
--- a/Documentation/networking/xfrm/index.rst
+++ b/Documentation/networking/xfrm/index.rst
@@ -9,5 +9,6 @@ XFRM Framework
xfrm_device
xfrm_proc
+ xfrm_migrate_state
xfrm_sync
xfrm_sysctl
diff --git a/Documentation/networking/xfrm/xfrm_migrate_state.rst b/Documentation/networking/xfrm/xfrm_migrate_state.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a218dd6510ca17df3f5a88adb55b9a7de26e6c35
--- /dev/null
+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst
@@ -0,0 +1,129 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=====================
+XFRM SA Migrate State
+=====================
+
+Overview
+========
+
+``XFRM_MSG_MIGRATE_STATE`` migrates a single SA, looked up using SPI and
+mark, without involving policies. Unlike ``XFRM_MSG_MIGRATE``, which couples
+SA and policy migration and allows migrating multiple SAs in one call, this
+interface identifies the SA unambiguously via SPI and supports changing
+the reqid, addresses, encapsulation, and other SA-specific parameters.
+
+Because IKE daemons such as strongSwan manage policies independently of
+the kernel, this interface allows precise per-SA migration without
+requiring policy involvement. Optional XFRM attributes follow an
+omit-to-inherit model.
+
+SA Identification
+=================
+
+The struct is defined in ``include/uapi/linux/xfrm.h``. The SA is looked
+up using ``xfrm_state_lookup()`` with ``id.spi``,
+``id.daddr``, ``id.proto``, ``id.family``, and ``old_mark``::
+
+ struct xfrm_user_migrate_state {
+ struct xfrm_usersa_id id; /* spi, daddr, proto, family */
+ xfrm_address_t new_daddr;
+ xfrm_address_t new_saddr;
+ __u16 new_family;
+ __u16 reserved;
+ __u32 new_reqid;
+ struct xfrm_mark old_mark; /* SA lookup */
+ };
+
+Supported Attributes
+====================
+
+The following fields in ``xfrm_user_migrate_state`` are always explicit
+and are not inherited from the existing SA. Passing zero is not equivalent
+to "keep unchanged" — zero is used as-is:
+
+- ``new_daddr`` - new destination address
+- ``new_saddr`` - new source address
+- ``new_family`` - new address family
+- ``new_reqid`` - new reqid (0 = no reqid)
+
+The following netlink attributes are also accepted. Omitting an attribute
+inherits the value from the existing SA (omit-to-inherit).
+
+.. list-table::
+ :widths: 30 70
+ :header-rows: 1
+
+ * - Attribute
+ - Description
+ * - ``XFRMA_MARK``
+ - Mark on the migrated SA (``struct xfrm_mark``). Absent inherits
+ ``old_mark``. To use no mark on the new SA, send ``XFRMA_MARK``
+ with ``{0, 0}``.
+ * - ``XFRMA_ENCAP``
+ - UDP encapsulation template; only ``UDP_ENCAP_ESPINUDP`` is supported.
+ Set ``encap_type=0`` to remove encap.
+ * - ``XFRMA_OFFLOAD_DEV``
+ - Hardware offload configuration. Set ``ifindex=0`` to remove offload.
+ * - ``XFRMA_SET_MARK``
+ - Output mark on the migrated SA; pair with ``XFRMA_SET_MARK_MASK``.
+ Send 0 to clear.
+ * - ``XFRMA_NAT_KEEPALIVE_INTERVAL``
+ - NAT keepalive interval in seconds. Requires encap. Send 0 to clear.
+ Automatically cleared when encap is removed; setting a non-zero
+ value without encap returns ``-EINVAL``.
+ * - ``XFRMA_MTIMER_THRESH``
+ - Mapping maxage threshold. Requires encap. Send 0 to clear.
+ Automatically cleared when encap is removed; setting a non-zero
+ value without encap returns ``-EINVAL``.
+
+The following SA properties are immutable and cannot be changed via
+``XFRM_MSG_MIGRATE_STATE``: algorithms (``XFRMA_ALG_*``), replay state,
+direction (``XFRMA_SA_DIR``), and security context (``XFRMA_SEC_CTX``).
+
+Migration Steps
+===============
+
+#. Install a block policy to drop traffic on the affected selector.
+#. Remove the old policy.
+#. Call ``XFRM_MSG_MIGRATE_STATE`` for each SA.
+#. Reinstall the policies.
+#. Remove the block policy.
+
+Block Policy and IV Safety
+--------------------------
+
+Installing a block policy before migration is required to prevent
+traffic leaks and IV reuse.
+
+AES-GCM IV uniqueness is critical: reusing a (key, IV) pair allows
+an attacker to recover the authentication subkey and forge
+authentication tags, breaking both confidentiality and integrity.
+
+``XFRM_MSG_MIGRATE_STATE`` atomically deletes the old SA and installs
+the new one with the sequence counter and replay window copied. The
+block policy ensures no outgoing packets are sent in the migration
+window, preventing IV reuse under the same key.
+
+Feature Detection
+=================
+
+Userspace can probe for kernel support by sending a minimal
+``XFRM_MSG_MIGRATE_STATE`` message with a non-existent SPI:
+
+- ``-ENOPROTOOPT``: not supported (``CONFIG_XFRM_MIGRATE`` not enabled)
+- any other error: supported
+
+Error Handling
+==============
+
+If the target SA tuple (daddr, SPI, proto, family) is occupied by an existing
+unrelated SA, the operation returns ``-EEXIST``. In this case both the old and
+the new SA are gone. The old SA cannot be restored as doing so would risk
+duplicate sequence number and IV reuse, which must not occur. Userspace should
+handle ``-EEXIST``, for example by re-establishing the SA at the IKE level.
+
+If the multicast notification (``XFRMNLGRP_MIGRATE``) fails to send,
+the migration itself has already completed successfully and the new SA
+is installed. The operation returns success, 0, with an extack warning,
+but listeners will not receive the migration event.
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [ipsec-next,v6,12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
2026-03-09 18:47 ` [PATCH ipsec-next v6 12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Antony Antony
@ 2026-03-11 19:57 ` Simon Horman
2026-03-11 20:43 ` [devel-ipsec] " Antony Antony
0 siblings, 1 reply; 18+ messages in thread
From: Simon Horman @ 2026-03-11 19:57 UTC (permalink / raw)
To: antony.antony
Cc: Simon Horman, evitayan, linux-kernel, kuba, pabeni, herbert, paul,
devel, davem, nakam, netdev, edumazet, stephen.smalley.work,
steffen.klassert, selinux, linux-doc, chiachangwang, omosnace,
corbet, dsahern, skhan
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
This patch adds 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.
> diff --git a/include/net/xfrm.h b/include/net/xfrm.h
> index 4b29ab92c2a7..ae2a1cf2311a 100644
> --- a/include/net/xfrm.h
> +++ b/include/net/xfrm.h
> @@ -683,11 +683,17 @@ struct xfrm_migrate {
> xfrm_address_t new_daddr;
> xfrm_address_t new_saddr;
> struct xfrm_encap_tmpl *encap;
> - struct xfrm_user_offload *xuo;
> + struct xfrm_user_offload xuo;
[ ... ]
> diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
> index 2f23ca5b62a9..de8b038fc08c 100644
> --- a/net/xfrm/xfrm_policy.c
> +++ b/net/xfrm/xfrm_policy.c
> @@ -4642,6 +4642,19 @@ 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.
> + */
> +static void xfrm_migrate_copy_old(struct xfrm_migrate *mp,
> + const struct xfrm_state *x)
> +{
> + 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,
> @@ -4680,7 +4693,10 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
> x_cur[nx_cur] = x;
> nx_cur++;
> mp->encap = encap;
> - mp->xuo = xuo;
> + if (xuo)
> + mp->xuo = *xuo;
^^^^
> xfrm_migrate_copy_old(mp, x);
>
What happens to mp->xuo when xuo is NULL?
The xfrm_migrate structure changed its xuo field from a pointer
(struct xfrm_user_offload *xuo) to an embedded value
(struct xfrm_user_offload xuo).
In xfrm_migrate(), when the xuo parameter is NULL (PF_KEY path via
pfkey_migrate, or XFRM_MSG_MIGRATE without XFRMA_OFFLOAD_DEV), the
'if (xuo) mp->xuo = *xuo' branch is skipped, leaving mp->xuo with
uninitialized stack data.
Both callers declare 'struct xfrm_migrate m[XFRM_MAX_DEPTH]' on the
stack without zeroing:
net/key/af_key.c:pfkey_migrate() {
struct xfrm_migrate m[XFRM_MAX_DEPTH];
...
err = xfrm_migrate(&sel, dir, XFRM_POLICY_TYPE_MAIN, m, i,
k.local && k.remote ? &k : NULL, net,
NULL, 0, NULL);
}
net/xfrm/xfrm_user.c:xfrm_do_migrate() {
struct xfrm_migrate m[XFRM_MAX_DEPTH];
...
err = xfrm_migrate(&sel, dir, type, m, n, k, net, encap, xuo, extack);
}
The uninitialized mp->xuo.ifindex is later read in
xfrm_state_migrate_create() and xfrm_state_migrate_install():
net/xfrm/xfrm_state.c:xfrm_state_migrate_create() {
...
/* configure the hardware if offload is requested */
if (m->xuo.ifindex && xfrm_dev_state_add(net, xc, &m->xuo, extack))
goto error;
}
net/xfrm/xfrm_state.c:xfrm_state_migrate_install() {
...
if (xfrm_state_add(xc) < 0) {
NL_SET_ERR_MSG(extack, "Failed to add migrated state");
if (m->xuo.ifindex)
xfrm_dev_state_delete(xc);
...
}
}
If the stack garbage contains a non-zero ifindex value,
xfrm_dev_state_add() will be called with random ifindex and flags,
potentially causing spurious hardware offload attempts or migration
failures.
Previously, 'mp->xuo = xuo' unconditionally set the pointer to NULL in
these paths, which was then properly checked as 'if (m->xuo)'.
Should mp->xuo be zero-initialized when xuo is NULL, either by adding
'else memset(&mp->xuo, 0, sizeof(mp->xuo))' after the if-block, or by
zero-initializing the m[] arrays in the callers?
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [devel-ipsec] Re: [ipsec-next,v6,12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
2026-03-11 19:57 ` [ipsec-next,v6,12/14] " Simon Horman
@ 2026-03-11 20:43 ` Antony Antony
2026-03-12 16:41 ` Simon Horman
0 siblings, 1 reply; 18+ messages in thread
From: Antony Antony @ 2026-03-11 20:43 UTC (permalink / raw)
To: Simon Horman
Cc: antony.antony, evitayan, linux-kernel, kuba, pabeni, herbert,
paul, devel, davem, nakam, netdev, edumazet, stephen.smalley.work,
steffen.klassert, selinux, linux-doc, chiachangwang, omosnace,
corbet, dsahern, skhan
Hi Simon,
On Wed, Mar 11, 2026 at 07:57:31PM +0000, Simon Horman via Devel wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
>
> For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
> ---
> xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
>
> This patch adds 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.
>
> > diff --git a/include/net/xfrm.h b/include/net/xfrm.h
> > index 4b29ab92c2a7..ae2a1cf2311a 100644
> > --- a/include/net/xfrm.h
> > +++ b/include/net/xfrm.h
> > @@ -683,11 +683,17 @@ struct xfrm_migrate {
> > xfrm_address_t new_daddr;
> > xfrm_address_t new_saddr;
> > struct xfrm_encap_tmpl *encap;
> > - struct xfrm_user_offload *xuo;
> > + struct xfrm_user_offload xuo;
>
> [ ... ]
>
> > diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
> > index 2f23ca5b62a9..de8b038fc08c 100644
> > --- a/net/xfrm/xfrm_policy.c
> > +++ b/net/xfrm/xfrm_policy.c
> > @@ -4642,6 +4642,19 @@ 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.
> > + */
> > +static void xfrm_migrate_copy_old(struct xfrm_migrate *mp,
> > + const struct xfrm_state *x)
> > +{
> > + 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,
> > @@ -4680,7 +4693,10 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
> > x_cur[nx_cur] = x;
> > nx_cur++;
> > mp->encap = encap;
> > - mp->xuo = xuo;
> > + if (xuo)
> > + mp->xuo = *xuo;
> ^^^^
> > xfrm_migrate_copy_old(mp, x);
> >
>
> What happens to mp->xuo when xuo is NULL?
thanks. I also red this on NIPA AI reviews. I will fix it in the next
version.
>
> The xfrm_migrate structure changed its xuo field from a pointer
> (struct xfrm_user_offload *xuo) to an embedded value
> (struct xfrm_user_offload xuo).
>
> In xfrm_migrate(), when the xuo parameter is NULL (PF_KEY path via
> pfkey_migrate, or XFRM_MSG_MIGRATE without XFRMA_OFFLOAD_DEV), the
> 'if (xuo) mp->xuo = *xuo' branch is skipped, leaving mp->xuo with
> uninitialized stack data.
>
> Both callers declare 'struct xfrm_migrate m[XFRM_MAX_DEPTH]' on the
> stack without zeroing:
>
> net/key/af_key.c:pfkey_migrate() {
> struct xfrm_migrate m[XFRM_MAX_DEPTH];
> ...
> err = xfrm_migrate(&sel, dir, XFRM_POLICY_TYPE_MAIN, m, i,
> k.local && k.remote ? &k : NULL, net,
> NULL, 0, NULL);
> }
>
> net/xfrm/xfrm_user.c:xfrm_do_migrate() {
> struct xfrm_migrate m[XFRM_MAX_DEPTH];
- struct xfrm_migrate m[XFRM_MAX_DEPTH];
+ struct xfrm_migrate m[XFRM_MAX_DEPTH] = {};
this should fix it.
> ...
> err = xfrm_migrate(&sel, dir, type, m, n, k, net, encap, xuo, extack);
> }
>
> The uninitialized mp->xuo.ifindex is later read in
> xfrm_state_migrate_create() and xfrm_state_migrate_install():
>
> net/xfrm/xfrm_state.c:xfrm_state_migrate_create() {
> ...
> /* configure the hardware if offload is requested */
> if (m->xuo.ifindex && xfrm_dev_state_add(net, xc, &m->xuo, extack))
> goto error;
> }
>
> net/xfrm/xfrm_state.c:xfrm_state_migrate_install() {
> ...
> if (xfrm_state_add(xc) < 0) {
> NL_SET_ERR_MSG(extack, "Failed to add migrated state");
> if (m->xuo.ifindex)
> xfrm_dev_state_delete(xc);
> ...
> }
> }
>
> If the stack garbage contains a non-zero ifindex value,
> xfrm_dev_state_add() will be called with random ifindex and flags,
> potentially causing spurious hardware offload attempts or migration
> failures.
>
> Previously, 'mp->xuo = xuo' unconditionally set the pointer to NULL in
> these paths, which was then properly checked as 'if (m->xuo)'.
>
> Should mp->xuo be zero-initialized when xuo is NULL, either by adding
> 'else memset(&mp->xuo, 0, sizeof(mp->xuo))' after the if-block, or by
> zero-initializing the m[] arrays in the callers?
thanks,
-antony
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [devel-ipsec] Re: [ipsec-next,v6,12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
2026-03-11 20:43 ` [devel-ipsec] " Antony Antony
@ 2026-03-12 16:41 ` Simon Horman
0 siblings, 0 replies; 18+ messages in thread
From: Simon Horman @ 2026-03-12 16:41 UTC (permalink / raw)
To: Antony Antony
Cc: antony.antony, evitayan, linux-kernel, kuba, pabeni, herbert,
paul, devel, davem, nakam, netdev, edumazet, stephen.smalley.work,
steffen.klassert, selinux, linux-doc, chiachangwang, omosnace,
corbet, dsahern, skhan
On Wed, Mar 11, 2026 at 09:43:04PM +0100, Antony Antony wrote:
> Hi Simon,
>
> On Wed, Mar 11, 2026 at 07:57:31PM +0000, Simon Horman via Devel wrote:
> > This is an AI-generated review of your patch. The human sending this
> > email has considered the AI review valid, or at least plausible.
> >
> > For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
> > ---
> > xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
> >
> > This patch adds 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.
> >
> > > diff --git a/include/net/xfrm.h b/include/net/xfrm.h
> > > index 4b29ab92c2a7..ae2a1cf2311a 100644
> > > --- a/include/net/xfrm.h
> > > +++ b/include/net/xfrm.h
> > > @@ -683,11 +683,17 @@ struct xfrm_migrate {
> > > xfrm_address_t new_daddr;
> > > xfrm_address_t new_saddr;
> > > struct xfrm_encap_tmpl *encap;
> > > - struct xfrm_user_offload *xuo;
> > > + struct xfrm_user_offload xuo;
> >
> > [ ... ]
> >
> > > diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
> > > index 2f23ca5b62a9..de8b038fc08c 100644
> > > --- a/net/xfrm/xfrm_policy.c
> > > +++ b/net/xfrm/xfrm_policy.c
> > > @@ -4642,6 +4642,19 @@ 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.
> > > + */
> > > +static void xfrm_migrate_copy_old(struct xfrm_migrate *mp,
> > > + const struct xfrm_state *x)
> > > +{
> > > + 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,
> > > @@ -4680,7 +4693,10 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
> > > x_cur[nx_cur] = x;
> > > nx_cur++;
> > > mp->encap = encap;
> > > - mp->xuo = xuo;
> > > + if (xuo)
> > > + mp->xuo = *xuo;
> > ^^^^
> > > xfrm_migrate_copy_old(mp, x);
> > >
> >
> > What happens to mp->xuo when xuo is NULL?
>
> thanks. I also red this on NIPA AI reviews. I will fix it in the next
> version.
Thanks, good to know.
>
> >
> > The xfrm_migrate structure changed its xuo field from a pointer
> > (struct xfrm_user_offload *xuo) to an embedded value
> > (struct xfrm_user_offload xuo).
> >
> > In xfrm_migrate(), when the xuo parameter is NULL (PF_KEY path via
> > pfkey_migrate, or XFRM_MSG_MIGRATE without XFRMA_OFFLOAD_DEV), the
> > 'if (xuo) mp->xuo = *xuo' branch is skipped, leaving mp->xuo with
> > uninitialized stack data.
> >
> > Both callers declare 'struct xfrm_migrate m[XFRM_MAX_DEPTH]' on the
> > stack without zeroing:
> >
> > net/key/af_key.c:pfkey_migrate() {
> > struct xfrm_migrate m[XFRM_MAX_DEPTH];
> > ...
> > err = xfrm_migrate(&sel, dir, XFRM_POLICY_TYPE_MAIN, m, i,
> > k.local && k.remote ? &k : NULL, net,
> > NULL, 0, NULL);
> > }
> >
> > net/xfrm/xfrm_user.c:xfrm_do_migrate() {
> > struct xfrm_migrate m[XFRM_MAX_DEPTH];
>
> - struct xfrm_migrate m[XFRM_MAX_DEPTH];
> + struct xfrm_migrate m[XFRM_MAX_DEPTH] = {};
>
> this should fix it.
Yes, I agree that should fix the problem.
>
> > ...
> > err = xfrm_migrate(&sel, dir, type, m, n, k, net, encap, xuo, extack);
> > }
...
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-03-12 16:42 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09 18:43 [PATCH ipsec-next v6 00/14] xfrm: XFRM_MSG_MIGRATE_STATE new netlink message Antony Antony
2026-03-09 18:44 ` [PATCH ipsec-next v6 01/14] xfrm: remove redundant assignments Antony Antony
2026-03-09 18:45 ` [PATCH ipsec-next v6 02/14] xfrm: add extack to xfrm_init_state Antony Antony
2026-03-09 18:45 ` [PATCH ipsec-next v6 03/14] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP Antony Antony
2026-03-09 18:45 ` [PATCH ipsec-next v6 04/14] xfrm: fix NAT-related field inheritance in SA migration Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 05/14] xfrm: rename reqid in xfrm_migrate Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 06/14] xfrm: split xfrm_state_migrate into create and install functions Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 07/14] xfrm: check family before comparing addresses in migrate Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 08/14] xfrm: add state synchronization after migration Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 09/14] xfrm: add error messages to state migration Antony Antony
2026-03-09 18:46 ` [PATCH ipsec-next v6 10/14] xfrm: move encap and xuo into struct xfrm_migrate Antony Antony
2026-03-09 18:47 ` [PATCH ipsec-next v6 11/14] xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper Antony Antony
2026-03-09 18:47 ` [PATCH ipsec-next v6 12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Antony Antony
2026-03-11 19:57 ` [ipsec-next,v6,12/14] " Simon Horman
2026-03-11 20:43 ` [devel-ipsec] " Antony Antony
2026-03-12 16:41 ` Simon Horman
2026-03-09 18:47 ` [PATCH ipsec-next v6 13/14] xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE Antony Antony
2026-03-09 18:47 ` [PATCH ipsec-next v6 14/14] xfrm: docs: add documentation " Antony Antony
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox