[PATCH ipsec-next v7 08/14] xfrm: add state synchronization after migration

Antony Antony
2026-04-12T11:15:41Z

Add xfrm_migrate_sync() to copy curlft and replay state from the old SA
to the new one before installation. The function allocates no memory, so
it can be called under a spinlock. In preparation for a subsequent patch
in this series.

A subsequent patch calls this under x->lock, atomically capturing the
latest lifetime counters and replay state from the original SA and
deleting it in the same critical section to prevent SN/IV reuse
for XFRM_MSG_MIGRATE_STATE method.

No functional change.

Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
v6->v7: - rephrase commit message
v5->v6: - move the sync before install to avoid overwriting
v4->v5: - added this patch
---
include/net/xfrm.h | 46 +++++++++++++++++++++++++++++++++++++---------
net/xfrm/xfrm_state.c | 11 ++++-------
2 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4137986f15e2..be22c26e4661 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 f7bcf1422358..8494c46118d9 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,6 +2188,8 @@ 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;

--
2.47.3