* [PATCH ipsec-next v8 02/14] xfrm: add extack to xfrm_init_state
From: Antony Antony @ 2026-05-05 4:32 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 10d3edde6b2f..0c035955d87d 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 9a45aed508d1..b1ea2d37e8c5 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 8607569de34f..b340d67eb1d9 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 a166a88d8788..842bf5786e3f 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -1299,7 +1299,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 9417a025270c..53d88b87bdbd 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 */
@@ -3238,11 +3238,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
* [PATCH ipsec-next v8 03/14] xfrm: allow migration from UDP encapsulated to non-encapsulated ESP
From: Antony Antony @ 2026-05-05 4:32 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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>
Tested-by: Yan Yan <evitayan@google.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 53d88b87bdbd..933541bc9093 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
* [PATCH ipsec-next v8 04/14] xfrm: fix NAT-related field inheritance in SA migration
From: Antony Antony @ 2026-05-05 4:32 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 933541bc9093..b9de931d84c1 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
* [PATCH ipsec-next v8 05/14] xfrm: rename reqid in xfrm_migrate
From: Antony Antony @ 2026-05-05 4:32 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 0c035955d87d..368b1dc22e5c 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 842bf5786e3f..1f0201d97b4f 100644
--- a/net/key/af_key.c
+++ b/net/key/af_key.c
@@ -2554,7 +2554,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));
@@ -3655,15 +3655,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 c944327ce66c..fd505adf080e 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4538,7 +4538,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:
@@ -4632,7 +4632,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 b9de931d84c1..5424f2becbaf 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, xfrm_state_deref_prot(net->xfrm.state_bydst, net) + 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 ae144d1e4a65..273ea6fdb8ad 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3104,7 +3104,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;
@@ -3187,7 +3187,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
* [PATCH ipsec-next v8 06/14] xfrm: split xfrm_state_migrate into create and install functions
From: Antony Antony @ 2026-05-05 4:33 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 368b1dc22e5c..4137986f15e2 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 5424f2becbaf..85fd80520184 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
* [PATCH ipsec-next v8 07/14] xfrm: check family before comparing addresses in migrate
From: Antony Antony @ 2026-05-05 4:33 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 85fd80520184..327a855253e6 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
* [PATCH ipsec-next v8 08/14] xfrm: add state synchronization after migration
From: Antony Antony @ 2026-05-05 4:33 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 327a855253e6..fcf6f0c6400d 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
^ permalink raw reply related
* [PATCH ipsec-next v8 09/14] xfrm: add error messages to state migration
From: Antony Antony @ 2026-05-05 4:33 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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
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 fcf6f0c6400d..1db48ecda80d 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
* [PATCH ipsec-next v8 10/14] xfrm: move encap and xuo into struct xfrm_migrate
From: Antony Antony @ 2026-05-05 4:34 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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.
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 be22c26e4661..4b29ab92c2a7 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 fd505adf080e..cf05d778e2dd 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4680,7 +4680,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 1db48ecda80d..043e573c4f32 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;
return xc;
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec-next v8 11/14] xfrm: refactor XFRMA_MTIMER_THRESH validation into a helper
From: Antony Antony @ 2026-05-05 4:34 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 273ea6fdb8ad..03fa4cabf601 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -248,6 +248,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)
@@ -455,18 +471,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
* [PATCH ipsec-next v8 12/14] xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration
From: Antony Antony @ 2026-05-05 4:34 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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>
---
v7->v8: - removed the unknown-flags validation block
v6->v7: - add flags field to xfrm_user_migrate_state (based on Sabrina's feedback)
- add XFRM_MIGRATE_STATE_NO_OFFLOAD (bit 0): suppresses offload
- omit-to-inherit; mutually exclusive with XFRMA_OFFLOAD_DEV
- zero-initialize struct xfrm_migrate m[XFRM_MAX_DEPTH]
- add struct xfrm_selector new_sel to xfrm_user_migrate_state
- add XFRM_MIGRATE_STATE_UPDATE_SEL: derive new selector
from SA addresses when old selector is a single-host match
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
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
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 | 16 ++-
include/uapi/linux/xfrm.h | 21 ++++
net/xfrm/xfrm_device.c | 2 +-
net/xfrm/xfrm_policy.c | 19 +++
net/xfrm/xfrm_state.c | 29 +++--
net/xfrm/xfrm_user.c | 281 +++++++++++++++++++++++++++++++++++++++++++-
security/selinux/nlmsgtab.c | 3 +-
7 files changed, 357 insertions(+), 14 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4b29ab92c2a7..e33e524cd909 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -684,12 +684,20 @@ struct xfrm_migrate {
xfrm_address_t new_saddr;
struct xfrm_encap_tmpl *encap;
struct xfrm_user_offload *xuo;
+ struct xfrm_mark old_mark;
+ struct xfrm_mark *new_mark;
+ struct xfrm_mark smark;
u8 proto;
u8 mode;
- u16 reserved;
+ u16 msg_type; /* XFRM_MSG_MIGRATE or XFRM_MSG_MIGRATE_STATE */
+ u32 flags;
u32 old_reqid;
+ u32 new_reqid;
+ u32 nat_keepalive_interval;
+ u32 mapping_maxage;
u16 old_family;
u16 new_family;
+ const struct xfrm_selector *new_sel;
};
#define XFRM_KM_TIMEOUT 30
@@ -2104,7 +2112,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 +2183,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 a23495c0e0a1..34d8ad5c4818 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,24 @@ struct xfrm_user_migrate {
__u16 new_family;
};
+struct xfrm_user_migrate_state {
+ struct xfrm_usersa_id id;
+ xfrm_address_t new_daddr;
+ xfrm_address_t new_saddr;
+ struct xfrm_mark old_mark;
+ struct xfrm_selector new_sel;
+ __u32 new_reqid;
+ __u32 flags;
+ __u16 new_family;
+ __u16 reserved;
+};
+
+/* Flags for xfrm_user_migrate_state.flags */
+enum xfrm_migrate_state_flags {
+ XFRM_MIGRATE_STATE_NO_OFFLOAD = 1, /* do not inherit offload from existing SA */
+ XFRM_MIGRATE_STATE_UPDATE_SEL = 2, /* update host-to-host selector from saddr and daddr */
+};
+
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 550457e4c4f0..630f3dd31cc5 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 cf05d778e2dd..9ecc4c8ba693 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -4643,6 +4643,22 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate,
return 0;
}
+/*
+ * Fill migrate fields that are invariant in XFRM_MSG_MIGRATE: inherited
+ * from the existing SA unchanged. XFRM_MSG_MIGRATE_STATE can update these.
+ */
+static void xfrm_migrate_copy_old(struct xfrm_migrate *mp,
+ const struct xfrm_state *x,
+ struct xfrm_mark *new_mark_buf)
+{
+ 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;
+ *new_mark_buf = x->mark;
+ mp->new_mark = new_mark_buf;
+}
+
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,
@@ -4650,6 +4666,7 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
struct netlink_ext_ack *extack, struct xfrm_user_offload *xuo)
{
int i, err, nx_cur = 0, nx_new = 0;
+ struct xfrm_mark new_marks[XFRM_MAX_DEPTH] = {};
struct xfrm_policy *pol = NULL;
struct xfrm_state *x, *xc;
struct xfrm_state *x_cur[XFRM_MAX_DEPTH];
@@ -4682,6 +4699,8 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
nx_cur++;
mp->encap = encap;
mp->xuo = xuo;
+ xfrm_migrate_copy_old(mp, x, &new_marks[i]);
+
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 043e573c4f32..44244bd323ea 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1974,11 +1974,25 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
goto out;
memcpy(&x->id, &orig->id, sizeof(x->id));
- memcpy(&x->sel, &orig->sel, sizeof(x->sel));
+ if (m->msg_type == XFRM_MSG_MIGRATE_STATE) {
+ if (m->flags & XFRM_MIGRATE_STATE_UPDATE_SEL) {
+ u8 prefixlen = (m->new_family == AF_INET6) ? 128 : 32;
+
+ memcpy(&x->sel, &orig->sel, sizeof(x->sel));
+ x->sel.family = m->new_family;
+ x->sel.prefixlen_d = prefixlen;
+ x->sel.prefixlen_s = prefixlen;
+ memcpy(&x->sel.daddr, &m->new_daddr, sizeof(x->sel.daddr));
+ memcpy(&x->sel.saddr, &m->new_saddr, sizeof(x->sel.saddr));
+ } else {
+ x->sel = *m->new_sel;
+ }
+ } else {
+ memcpy(&x->sel, &orig->sel, sizeof(x->sel));
+ }
memcpy(&x->lft, &orig->lft, sizeof(x->lft));
x->props.mode = orig->props.mode;
x->props.replay_window = orig->props.replay_window;
- x->props.reqid = orig->props.reqid;
if (orig->aalg) {
x->aalg = xfrm_algo_auth_clone(orig->aalg);
@@ -2011,8 +2025,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL);
if (!x->encap)
goto error;
- x->mapping_maxage = orig->mapping_maxage;
- x->nat_keepalive_interval = orig->nat_keepalive_interval;
+ x->mapping_maxage = m->mapping_maxage;
+ x->nat_keepalive_interval = m->nat_keepalive_interval;
}
if (orig->security)
@@ -2029,8 +2043,9 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
if (xfrm_replay_clone(x, orig))
goto error;
- memcpy(&x->mark, &orig->mark, sizeof(x->mark));
- memcpy(&x->props.smark, &orig->props.smark, sizeof(x->props.smark));
+ x->mark = m->new_mark ? *m->new_mark : m->old_mark;
+
+ x->props.smark = m->smark;
x->props.flags = orig->props.flags;
x->props.extra_flags = orig->props.extra_flags;
@@ -2053,7 +2068,7 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
goto error;
}
-
+ x->props.reqid = m->new_reqid;
x->props.family = m->new_family;
memcpy(&x->id.daddr, &m->new_daddr, sizeof(x->id.daddr));
memcpy(&x->props.saddr, &m->new_saddr, sizeof(x->props.saddr));
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 03fa4cabf601..a49edf7d6f78 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1327,7 +1327,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;
@@ -3076,6 +3076,25 @@ 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;
+ ma->flags = um->flags;
+ ma->new_sel = &um->new_sel;
+ ma->msg_type = XFRM_MSG_MIGRATE_STATE;
+}
+
static int copy_from_user_migrate(struct xfrm_migrate *ma,
struct xfrm_kmaddress *k,
struct nlattr **attrs, int *num,
@@ -3115,6 +3134,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma,
ma->old_family = um->old_family;
ma->new_family = um->new_family;
+ ma->msg_type = XFRM_MSG_MIGRATE;
}
*num = i;
@@ -3125,7 +3145,7 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh,
struct nlattr **attrs, struct netlink_ext_ack *extack)
{
struct xfrm_userpolicy_id *pi = nlmsg_data(nlh);
- struct xfrm_migrate m[XFRM_MAX_DEPTH];
+ struct xfrm_migrate m[XFRM_MAX_DEPTH] = {};
struct xfrm_kmaddress km, *kmp;
u8 type;
int err;
@@ -3178,7 +3198,262 @@ 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,
+ u8 dir, u32 portid, u32 seq)
+{
+ int err;
+ struct nlmsghdr *nlh;
+ struct xfrm_user_migrate_state *hdr;
+
+ nlh = nlmsg_put(skb, portid, seq, XFRM_MSG_MIGRATE_STATE,
+ sizeof(struct xfrm_user_migrate_state), 0);
+ if (!nlh)
+ return -EMSGSIZE;
+
+ hdr = nlmsg_data(nlh);
+ *hdr = *um;
+ hdr->new_sel = *m->new_sel;
+
+ if (m->encap) {
+ err = nla_put(skb, XFRMA_ENCAP, sizeof(*m->encap), m->encap);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (m->xuo) {
+ err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(*m->xuo), m->xuo);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (m->new_mark) {
+ err = nla_put(skb, XFRMA_MARK, sizeof(*m->new_mark),
+ m->new_mark);
+ if (err)
+ goto out_cancel;
+ }
+
+ err = xfrm_smark_put(skb, &m->smark);
+ if (err)
+ goto out_cancel;
+
+ if (m->mapping_maxage) {
+ err = nla_put_u32(skb, XFRMA_MTIMER_THRESH, m->mapping_maxage);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (m->nat_keepalive_interval) {
+ err = nla_put_u32(skb, XFRMA_NAT_KEEPALIVE_INTERVAL,
+ m->nat_keepalive_interval);
+ if (err)
+ goto out_cancel;
+ }
+
+ if (dir) {
+ err = nla_put_u8(skb, XFRMA_SA_DIR, dir);
+ if (err)
+ goto out_cancel;
+ }
+
+ nlmsg_end(skb, nlh);
+ return 0;
+
+out_cancel:
+ nlmsg_cancel(skb, nlh);
+ return err;
+}
+
+static unsigned int xfrm_migrate_state_msgsize(const struct xfrm_migrate *m,
+ u8 dir)
+{
+ return NLMSG_ALIGN(sizeof(struct xfrm_user_migrate_state)) +
+ (m->encap ? nla_total_size(sizeof(struct xfrm_encap_tmpl)) : 0) +
+ (m->xuo ? nla_total_size(sizeof(struct xfrm_user_offload)) : 0) +
+ (m->new_mark ? nla_total_size(sizeof(struct xfrm_mark)) : 0) +
+ (m->smark.v ? nla_total_size(sizeof(u32)) * 2 : 0) + /* 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,
+ 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, dir, portid, seq);
+ if (err < 0) {
+ kfree_skb(skb);
+ return err;
+ }
+
+ return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE);
+}
+
+static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
+ struct nlattr **attrs, struct netlink_ext_ack *extack)
+{
+ struct xfrm_user_migrate_state *um = nlmsg_data(nlh);
+ struct net *net = sock_net(skb->sk);
+ struct xfrm_user_offload xuo = {};
+ struct xfrm_migrate m = {};
+ struct xfrm_state *xc;
+ struct xfrm_state *x;
+ int err;
+
+ if (!um->id.spi) {
+ NL_SET_ERR_MSG(extack, "Invalid SPI 0x0");
+ return -EINVAL;
+ }
+
+ if (um->reserved) {
+ NL_SET_ERR_MSG(extack, "Reserved field must be zero");
+ return -EINVAL;
+ }
+
+ if ((um->flags & XFRM_MIGRATE_STATE_NO_OFFLOAD) &&
+ attrs[XFRMA_OFFLOAD_DEV]) {
+ NL_SET_ERR_MSG(extack,
+ "XFRM_MIGRATE_STATE_NO_OFFLOAD and XFRMA_OFFLOAD_DEV are mutually exclusive");
+ 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 (um->flags & XFRM_MIGRATE_STATE_UPDATE_SEL) {
+ u8 prefixlen = (x->sel.family == AF_INET6) ? 128 : 32;
+
+ if (x->sel.prefixlen_s != x->sel.prefixlen_d ||
+ x->sel.prefixlen_d != prefixlen ||
+ !xfrm_addr_equal(&x->sel.daddr, &x->id.daddr, x->sel.family) ||
+ !xfrm_addr_equal(&x->sel.saddr, &x->props.saddr, x->sel.family)) {
+ NL_SET_ERR_MSG(extack,
+ "SA selector is not a single-host match for SA addresses");
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (attrs[XFRMA_ENCAP]) {
+ m.encap = nla_data(attrs[XFRMA_ENCAP]);
+ if (m.encap->encap_type == 0) {
+ m.encap = NULL; /* sentinel: remove encap */
+ } else if (m.encap->encap_type != UDP_ENCAP_ESPINUDP) {
+ NL_SET_ERR_MSG(extack, "Unsupported encapsulation type");
+ err = -EINVAL;
+ goto out;
+ }
+ } else {
+ m.encap = x->encap; /* omit-to-inherit */
+ }
+
+ if (attrs[XFRMA_MTIMER_THRESH]) {
+ err = verify_mtimer_thresh(!!m.encap, x->dir, extack);
+ if (err)
+ goto out;
+ }
+
+ if (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;
+ }
+
+ if (attrs[XFRMA_OFFLOAD_DEV]) {
+ m.xuo = nla_data(attrs[XFRMA_OFFLOAD_DEV]);
+ } else if (!(um->flags & XFRM_MIGRATE_STATE_NO_OFFLOAD) && x->xso.dev) {
+ xuo.ifindex = x->xso.dev->ifindex;
+ if (x->xso.dir == XFRM_DEV_OFFLOAD_IN)
+ xuo.flags = XFRM_OFFLOAD_INBOUND;
+ if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
+ xuo.flags |= XFRM_OFFLOAD_PACKET;
+ m.xuo = &xuo;
+ }
+
+ if (attrs[XFRMA_MARK])
+ m.new_mark = nla_data(attrs[XFRMA_MARK]);
+
+ if (attrs[XFRMA_SET_MARK])
+ xfrm_smark_init(attrs, &m.smark);
+ else
+ m.smark = x->props.smark;
+
+ m.mapping_maxage = 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]);
+
+ m.new_sel = &xc->sel;
+
+ err = xfrm_send_migrate_state(um, &m, xc->dir,
+ nlh->nlmsg_pid, nlh->nlmsg_seq);
+ if (err < 0) {
+ NL_SET_ERR_MSG(extack, "Failed to send migration notification");
+ err = 0;
+ }
+
+out:
+ xfrm_state_put(x);
+ return err;
+}
+
#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)
{
@@ -3331,6 +3606,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);
@@ -3424,6 +3700,7 @@ static const struct xfrm_link {
[XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo },
[XFRM_MSG_SETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_set_default },
[XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_get_default },
+ [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate_state },
};
static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index 2c0b07f9fbbd..655d2616c9d2 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
* [PATCH ipsec-next v8 13/14] xfrm: restrict netlink attributes for XFRM_MSG_MIGRATE_STATE
From: Antony Antony @ 2026-05-05 4:34 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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 a49edf7d6f78..c435b38f25bd 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3732,6 +3732,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
* [PATCH ipsec-next v8 14/14] xfrm: add documentation for XFRM_MSG_MIGRATE_STATE
From: Antony Antony @ 2026-05-05 4:34 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: Sabrina Dubroca, netdev, linux-kernel, selinux, linux-doc,
Chiachang Wang, Yan Yan, devel
In-Reply-To: <migrate-state-v8-0-4578fb016965@secunet.com>
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>
---
v7->v8: unknown flags ignored
v6->v7: update docs to reflect the flags
v5->v6: added this patch
---
Documentation/networking/xfrm/index.rst | 1 +
.../networking/xfrm/xfrm_migrate_state.rst | 231 +++++++++++++++++++++
2 files changed, 232 insertions(+)
diff --git a/Documentation/networking/xfrm/index.rst b/Documentation/networking/xfrm/index.rst
index 7d866da836fe..90191848f8db 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 000000000000..66e7f2e729d8
--- /dev/null
+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst
@@ -0,0 +1,231 @@
+.. 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, selector, and offload.
+
+Because IKE daemons such as *wan manage policies independently of
+the kernel, this interface allows precise per-SA migration without
+requiring policy involvement. Optional netlink attributes follow an
+omit-to-inherit model: omitting an attribute preserves the value from
+the old SA. The ``flags`` field controls two exceptions: hardware offload
+is inherited by default and can be suppressed with
+``XFRM_MIGRATE_STATE_NO_OFFLOAD`` or overridden with ``XFRMA_OFFLOAD_DEV``;
+the new selector is taken from ``new_sel`` by default and can instead be
+derived from the new addresses with ``XFRM_MIGRATE_STATE_UPDATE_SEL``.
+
+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.v & old_mark.m`` as the mark key::
+
+ struct xfrm_user_migrate_state {
+ struct xfrm_usersa_id id; /* spi, daddr, proto, family */
+ xfrm_address_t new_daddr;
+ xfrm_address_t new_saddr;
+ struct xfrm_mark old_mark; /* SA lookup: key = v & m */
+ struct xfrm_selector new_sel; /* new selector (see Flags) */
+ __u32 new_reqid;
+ __u32 flags; /* XFRM_MIGRATE_STATE_* */
+ __u16 new_family;
+ __u16 reserved;
+ };
+
+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)
+- ``new_sel`` - new selector; used when ``XFRM_MIGRATE_STATE_UPDATE_SEL`` is
+ not set (see `Flags`_ below)
+- ``flags`` - bitmask of ``XFRM_MIGRATE_STATE_*`` flags (see `Flags`_ below)
+
+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 (``struct xfrm_user_offload``). Absent
+ copies offload from the existing SA. When
+ ``XFRM_MIGRATE_STATE_NO_OFFLOAD`` is set in ``flags``, the new SA has
+ no offload; this flag is mutually exclusive with ``XFRMA_OFFLOAD_DEV``
+ and sending both returns ``-EINVAL``.
+ * - ``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``).
+
+Flags
+=====
+
+The ``flags`` field in ``xfrm_user_migrate_state`` controls optional
+migration behaviour. Unknown flag bits are ignored.
+
+.. list-table::
+ :widths: 40 60
+ :header-rows: 1
+
+ * - Flag
+ - Description
+ * - ``XFRM_MIGRATE_STATE_NO_OFFLOAD``
+ - When set, the new SA has no hardware offload even when
+ ``XFRMA_OFFLOAD_DEV`` is absent. Without this flag, omitting
+ ``XFRMA_OFFLOAD_DEV`` copies the existing offload to the new SA.
+ Mutually exclusive with ``XFRMA_OFFLOAD_DEV``; sending both
+ returns ``-EINVAL``.
+ * - ``XFRM_MIGRATE_STATE_UPDATE_SEL``
+ - When set, the kernel validates that the existing SA selector is a
+ single-host entry matching the SA addresses (``prefixlen_s ==
+ prefixlen_d`` equal to 32 for IPv4 or 128 for IPv6, and addresses
+ matching ``id.daddr`` and ``props.saddr``). If the check passes,
+ the new selector is derived from ``new_daddr`` and ``new_saddr``
+ with the single-host mask for ``new_family``. A mismatch returns
+ ``-EINVAL``. When this flag is not set, ``new_sel`` is used as-is
+ for the migrated SA.
+
+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 in counter mode.
+
+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 copies the sequence number and
+replay window from the old SA to the new SA and deletes the old SA.
+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
+
+Userspace Notification on Success
+=================================
+
+On successful migration the kernel multicasts an
+``XFRM_MSG_MIGRATE_STATE`` message to the ``XFRMNLGRP_MIGRATE`` group.
+The fixed header is ``struct xfrm_user_migrate_state`` copied from the
+request, followed by the same set of netlink attributes that are
+accepted as input, with the differences noted below.
+
+Differences from the request
+-----------------------------
+
+.. list-table::
+ :widths: 25 75
+ :header-rows: 1
+
+ * - Field / Attribute
+ - Difference
+ * - ``new_sel``
+ - Contains the actual selector of the newly installed SA, not the
+ ``new_sel`` from the request. When
+ ``XFRM_MIGRATE_STATE_UPDATE_SEL`` is set the kernel derives the
+ selector from ``new_daddr`` / ``new_saddr``; the caller's
+ ``new_sel`` field is ignored in that case. The notification
+ always carries the real selector of the new SA.
+ * - ``XFRMA_SA_DIR``
+ - Present in the notification (set from the direction of the new
+ SA) but **not accepted as input** — direction is immutable.
+ * - ``flags``
+ - Echoed back as-is. ``XFRM_MIGRATE_STATE_NO_OFFLOAD`` and
+ ``XFRM_MIGRATE_STATE_UPDATE_SEL`` describe the request that was
+ made, not a property of the resulting SA.
+
+Attributes in the notification
+-------------------------------
+
+.. list-table::
+ :widths: 30 70
+ :header-rows: 1
+
+ * - Attribute
+ - Description
+ * - ``XFRMA_ENCAP``
+ - UDP encapsulation template, if configured on the new SA.
+ * - ``XFRMA_OFFLOAD_DEV``
+ - Hardware offload configuration, if active on the new SA.
+ * - ``XFRMA_MARK``
+ - Mark on the new SA, if set.
+ * - ``XFRMA_SET_MARK``
+ - Output mark on the new SA, if set.
+ * - ``XFRMA_SET_MARK_MASK``
+ - Output mark mask, present together with ``XFRMA_SET_MARK``.
+ * - ``XFRMA_MTIMER_THRESH``
+ - Mapping maxage threshold, if non-zero.
+ * - ``XFRMA_NAT_KEEPALIVE_INTERVAL``
+ - NAT keepalive interval, if non-zero.
+ * - ``XFRMA_SA_DIR``
+ - Direction of the new SA.
+
+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
* Re: [PATCH] watchdog: it8712f_wdt: remove redundant driver
From: Guenter Roeck @ 2026-05-05 5:01 UTC (permalink / raw)
To: Ethan Nelson-Moore, linux-watchdog, linux-doc
Cc: Wim Van Sebroeck, Jonathan Corbet, Shuah Khan
In-Reply-To: <20260505032145.66065-1-enelsonmoore@gmail.com>
On 5/4/26 20:21, Ethan Nelson-Moore wrote:
> The it87_wdt driver also supports the IT8712 (regardless of revision),
> and therefore the it8712f_wdt driver is redundant. Remove it and
> add an alias to it87_wdt so it loads when it8712f_wdt is requested.
>
> A notable difference between the drivers is that it8712f_wdt kicks the
> watchdog using game port reads which it generates, or optionally,
> keyboard, mouse, or CIR activity. None of these methods are
> particularly useful or reliable; the documentation indicates:
> If the driver does not work, then make sure that the game port in the
> BIOS is enabled.
> and:
> You can also use KBD, MOUSE or CIR if you have some external way to
> generate those interrupts.
> it87_wdt kicks the watchdog by directly writing to the timer register,
> which should work in all circumstances.
>
> Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
Same as with the other drivers: Mark it deprecated in the documentation
and Kconfig file, add a kernel message saying that it is deprecated
and what to replace it with, and indicate clearly when it will be removed.
Guenter
> ---
> .../watchdog/watchdog-parameters.rst | 9 -
> drivers/watchdog/Kconfig | 13 -
> drivers/watchdog/Makefile | 1 -
> drivers/watchdog/it8712f_wdt.c | 449 ------------------
> drivers/watchdog/it87_wdt.c | 5 +
> 5 files changed, 5 insertions(+), 472 deletions(-)
> delete mode 100644 drivers/watchdog/it8712f_wdt.c
>
> diff --git a/Documentation/watchdog/watchdog-parameters.rst b/Documentation/watchdog/watchdog-parameters.rst
> index 773241ed9986..39b20250b416 100644
> --- a/Documentation/watchdog/watchdog-parameters.rst
> +++ b/Documentation/watchdog/watchdog-parameters.rst
> @@ -248,15 +248,6 @@ iop_wdt:
>
> -------------------------------------------------
>
> -it8712f_wdt:
> - margin:
> - Watchdog margin in seconds (default 60)
> - nowayout:
> - Disable watchdog shutdown on close
> - (default=kernel config parameter)
> -
> --------------------------------------------------
> -
> it87_wdt:
> nogameport:
> Forbid the activation of game port, default=0
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index dc78729ba2a5..a84fbf9d52b8 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -1429,19 +1429,6 @@ config ITCO_WDT
> To compile this driver as a module, choose M here: the
> module will be called iTCO_wdt.
>
> -config IT8712F_WDT
> - tristate "IT8712F (Smart Guardian) Watchdog Timer"
> - depends on (X86 || COMPILE_TEST) && HAS_IOPORT
> - help
> - This is the driver for the built-in watchdog timer on the IT8712F
> - Super I/0 chipset used on many motherboards.
> -
> - If the driver does not work, then make sure that the game port in
> - the BIOS is enabled.
> -
> - To compile this driver as a module, choose M here: the
> - module will be called it8712f_wdt.
> -
> config IT87_WDT
> tristate "IT87 Watchdog Timer"
> depends on (X86 || COMPILE_TEST) && HAS_IOPORT
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index d2fb16b9f9ce..30b35d9887cd 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -127,7 +127,6 @@ obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o
> obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
> obj-$(CONFIG_LENOVO_SE10_WDT) += lenovo_se10_wdt.o
> obj-$(CONFIG_LENOVO_SE30_WDT) += lenovo_se30_wdt.o
> -obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
> obj-$(CONFIG_IT87_WDT) += it87_wdt.o
> obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
> diff --git a/drivers/watchdog/it8712f_wdt.c b/drivers/watchdog/it8712f_wdt.c
> deleted file mode 100644
> index b776e6766c9d..000000000000
> --- a/drivers/watchdog/it8712f_wdt.c
> +++ /dev/null
> @@ -1,449 +0,0 @@
> -// SPDX-License-Identifier: GPL-2.0-or-later
> -/*
> - * IT8712F "Smart Guardian" Watchdog support
> - *
> - * Copyright (c) 2006-2007 Jorge Boncompte - DTI2 <jorge@dti2.net>
> - *
> - * Based on info and code taken from:
> - *
> - * drivers/char/watchdog/scx200_wdt.c
> - * drivers/hwmon/it87.c
> - * IT8712F EC-LPC I/O Preliminary Specification 0.8.2
> - * IT8712F EC-LPC I/O Preliminary Specification 0.9.3
> - *
> - * The author(s) of this software shall not be held liable for damages
> - * of any nature resulting due to the use of this software. This
> - * software is provided AS-IS with no warranties.
> - */
> -
> -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> -
> -#include <linux/module.h>
> -#include <linux/moduleparam.h>
> -#include <linux/init.h>
> -#include <linux/miscdevice.h>
> -#include <linux/watchdog.h>
> -#include <linux/notifier.h>
> -#include <linux/reboot.h>
> -#include <linux/fs.h>
> -#include <linux/spinlock.h>
> -#include <linux/uaccess.h>
> -#include <linux/io.h>
> -#include <linux/ioport.h>
> -
> -#define NAME "it8712f_wdt"
> -
> -MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>");
> -MODULE_DESCRIPTION("IT8712F Watchdog Driver");
> -MODULE_LICENSE("GPL");
> -
> -static int max_units = 255;
> -static int margin = 60; /* in seconds */
> -module_param(margin, int, 0);
> -MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
> -
> -static bool nowayout = WATCHDOG_NOWAYOUT;
> -module_param(nowayout, bool, 0);
> -MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
> -
> -static unsigned long wdt_open;
> -static unsigned expect_close;
> -static unsigned char revision;
> -
> -/* Dog Food address - We use the game port address */
> -static unsigned short address;
> -
> -#define REG 0x2e /* The register to read/write */
> -#define VAL 0x2f /* The value to read/write */
> -
> -#define LDN 0x07 /* Register: Logical device select */
> -#define DEVID 0x20 /* Register: Device ID */
> -#define DEVREV 0x22 /* Register: Device Revision */
> -#define ACT_REG 0x30 /* LDN Register: Activation */
> -#define BASE_REG 0x60 /* LDN Register: Base address */
> -
> -#define IT8712F_DEVID 0x8712
> -
> -#define LDN_GPIO 0x07 /* GPIO and Watch Dog Timer */
> -#define LDN_GAME 0x09 /* Game Port */
> -
> -#define WDT_CONTROL 0x71 /* WDT Register: Control */
> -#define WDT_CONFIG 0x72 /* WDT Register: Configuration */
> -#define WDT_TIMEOUT 0x73 /* WDT Register: Timeout Value */
> -
> -#define WDT_RESET_GAME 0x10 /* Reset timer on read or write to game port */
> -#define WDT_RESET_KBD 0x20 /* Reset timer on keyboard interrupt */
> -#define WDT_RESET_MOUSE 0x40 /* Reset timer on mouse interrupt */
> -#define WDT_RESET_CIR 0x80 /* Reset timer on consumer IR interrupt */
> -
> -#define WDT_UNIT_SEC 0x80 /* If 0 in MINUTES */
> -
> -#define WDT_OUT_PWROK 0x10 /* Pulse PWROK on timeout */
> -#define WDT_OUT_KRST 0x40 /* Pulse reset on timeout */
> -
> -static int wdt_control_reg = WDT_RESET_GAME;
> -module_param(wdt_control_reg, int, 0);
> -MODULE_PARM_DESC(wdt_control_reg, "Value to write to watchdog control "
> - "register. The default WDT_RESET_GAME resets the timer on "
> - "game port reads that this driver generates. You can also "
> - "use KBD, MOUSE or CIR if you have some external way to "
> - "generate those interrupts.");
> -
> -static int superio_inb(int reg)
> -{
> - outb(reg, REG);
> - return inb(VAL);
> -}
> -
> -static void superio_outb(int val, int reg)
> -{
> - outb(reg, REG);
> - outb(val, VAL);
> -}
> -
> -static int superio_inw(int reg)
> -{
> - int val;
> - outb(reg++, REG);
> - val = inb(VAL) << 8;
> - outb(reg, REG);
> - val |= inb(VAL);
> - return val;
> -}
> -
> -static inline void superio_select(int ldn)
> -{
> - outb(LDN, REG);
> - outb(ldn, VAL);
> -}
> -
> -static inline int superio_enter(void)
> -{
> - /*
> - * Try to reserve REG and REG + 1 for exclusive access.
> - */
> - if (!request_muxed_region(REG, 2, NAME))
> - return -EBUSY;
> -
> - outb(0x87, REG);
> - outb(0x01, REG);
> - outb(0x55, REG);
> - outb(0x55, REG);
> - return 0;
> -}
> -
> -static inline void superio_exit(void)
> -{
> - outb(0x02, REG);
> - outb(0x02, VAL);
> - release_region(REG, 2);
> -}
> -
> -static inline void it8712f_wdt_ping(void)
> -{
> - if (wdt_control_reg & WDT_RESET_GAME)
> - inb(address);
> -}
> -
> -static void it8712f_wdt_update_margin(void)
> -{
> - int config = WDT_OUT_KRST | WDT_OUT_PWROK;
> - int units = margin;
> -
> - /* Switch to minutes precision if the configured margin
> - * value does not fit within the register width.
> - */
> - if (units <= max_units) {
> - config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */
> - pr_info("timer margin %d seconds\n", units);
> - } else {
> - units /= 60;
> - pr_info("timer margin %d minutes\n", units);
> - }
> - superio_outb(config, WDT_CONFIG);
> -
> - if (revision >= 0x08)
> - superio_outb(units >> 8, WDT_TIMEOUT + 1);
> - superio_outb(units, WDT_TIMEOUT);
> -}
> -
> -static int it8712f_wdt_get_status(void)
> -{
> - if (superio_inb(WDT_CONTROL) & 0x01)
> - return WDIOF_CARDRESET;
> - else
> - return 0;
> -}
> -
> -static int it8712f_wdt_enable(void)
> -{
> - int ret = superio_enter();
> - if (ret)
> - return ret;
> -
> - pr_debug("enabling watchdog timer\n");
> - superio_select(LDN_GPIO);
> -
> - superio_outb(wdt_control_reg, WDT_CONTROL);
> -
> - it8712f_wdt_update_margin();
> -
> - superio_exit();
> -
> - it8712f_wdt_ping();
> -
> - return 0;
> -}
> -
> -static int it8712f_wdt_disable(void)
> -{
> - int ret = superio_enter();
> - if (ret)
> - return ret;
> -
> - pr_debug("disabling watchdog timer\n");
> - superio_select(LDN_GPIO);
> -
> - superio_outb(0, WDT_CONFIG);
> - superio_outb(0, WDT_CONTROL);
> - if (revision >= 0x08)
> - superio_outb(0, WDT_TIMEOUT + 1);
> - superio_outb(0, WDT_TIMEOUT);
> -
> - superio_exit();
> - return 0;
> -}
> -
> -static int it8712f_wdt_notify(struct notifier_block *this,
> - unsigned long code, void *unused)
> -{
> - if (code == SYS_HALT || code == SYS_POWER_OFF)
> - if (!nowayout)
> - it8712f_wdt_disable();
> -
> - return NOTIFY_DONE;
> -}
> -
> -static struct notifier_block it8712f_wdt_notifier = {
> - .notifier_call = it8712f_wdt_notify,
> -};
> -
> -static ssize_t it8712f_wdt_write(struct file *file, const char __user *data,
> - size_t len, loff_t *ppos)
> -{
> - /* check for a magic close character */
> - if (len) {
> - size_t i;
> -
> - it8712f_wdt_ping();
> -
> - expect_close = 0;
> - for (i = 0; i < len; ++i) {
> - char c;
> - if (get_user(c, data + i))
> - return -EFAULT;
> - if (c == 'V')
> - expect_close = 42;
> - }
> - }
> -
> - return len;
> -}
> -
> -static long it8712f_wdt_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> -{
> - void __user *argp = (void __user *)arg;
> - int __user *p = argp;
> - static const struct watchdog_info ident = {
> - .identity = "IT8712F Watchdog",
> - .firmware_version = 1,
> - .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
> - WDIOF_MAGICCLOSE,
> - };
> - int value;
> - int ret;
> -
> - switch (cmd) {
> - case WDIOC_GETSUPPORT:
> - if (copy_to_user(argp, &ident, sizeof(ident)))
> - return -EFAULT;
> - return 0;
> - case WDIOC_GETSTATUS:
> - ret = superio_enter();
> - if (ret)
> - return ret;
> - superio_select(LDN_GPIO);
> -
> - value = it8712f_wdt_get_status();
> -
> - superio_exit();
> -
> - return put_user(value, p);
> - case WDIOC_GETBOOTSTATUS:
> - return put_user(0, p);
> - case WDIOC_KEEPALIVE:
> - it8712f_wdt_ping();
> - return 0;
> - case WDIOC_SETTIMEOUT:
> - if (get_user(value, p))
> - return -EFAULT;
> - if (value < 1)
> - return -EINVAL;
> - if (value > (max_units * 60))
> - return -EINVAL;
> - margin = value;
> - ret = superio_enter();
> - if (ret)
> - return ret;
> - superio_select(LDN_GPIO);
> -
> - it8712f_wdt_update_margin();
> -
> - superio_exit();
> - it8712f_wdt_ping();
> - fallthrough;
> - case WDIOC_GETTIMEOUT:
> - if (put_user(margin, p))
> - return -EFAULT;
> - return 0;
> - default:
> - return -ENOTTY;
> - }
> -}
> -
> -static int it8712f_wdt_open(struct inode *inode, struct file *file)
> -{
> - int ret;
> - /* only allow one at a time */
> - if (test_and_set_bit(0, &wdt_open))
> - return -EBUSY;
> -
> - ret = it8712f_wdt_enable();
> - if (ret)
> - return ret;
> - return stream_open(inode, file);
> -}
> -
> -static int it8712f_wdt_release(struct inode *inode, struct file *file)
> -{
> - if (expect_close != 42) {
> - pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
> - } else if (!nowayout) {
> - if (it8712f_wdt_disable())
> - pr_warn("Watchdog disable failed\n");
> - }
> - expect_close = 0;
> - clear_bit(0, &wdt_open);
> -
> - return 0;
> -}
> -
> -static const struct file_operations it8712f_wdt_fops = {
> - .owner = THIS_MODULE,
> - .write = it8712f_wdt_write,
> - .unlocked_ioctl = it8712f_wdt_ioctl,
> - .compat_ioctl = compat_ptr_ioctl,
> - .open = it8712f_wdt_open,
> - .release = it8712f_wdt_release,
> -};
> -
> -static struct miscdevice it8712f_wdt_miscdev = {
> - .minor = WATCHDOG_MINOR,
> - .name = "watchdog",
> - .fops = &it8712f_wdt_fops,
> -};
> -
> -static int __init it8712f_wdt_find(unsigned short *address)
> -{
> - int err = -ENODEV;
> - int chip_type;
> - int ret = superio_enter();
> - if (ret)
> - return ret;
> -
> - chip_type = superio_inw(DEVID);
> - if (chip_type != IT8712F_DEVID)
> - goto exit;
> -
> - superio_select(LDN_GAME);
> - superio_outb(1, ACT_REG);
> - if (!(superio_inb(ACT_REG) & 0x01)) {
> - pr_err("Device not activated, skipping\n");
> - goto exit;
> - }
> -
> - *address = superio_inw(BASE_REG);
> - if (*address == 0) {
> - pr_err("Base address not set, skipping\n");
> - goto exit;
> - }
> -
> - err = 0;
> - revision = superio_inb(DEVREV) & 0x0f;
> -
> - /* Later revisions have 16-bit values per datasheet 0.9.1 */
> - if (revision >= 0x08)
> - max_units = 65535;
> -
> - if (margin > (max_units * 60))
> - margin = (max_units * 60);
> -
> - pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n",
> - chip_type, revision, *address);
> -
> -exit:
> - superio_exit();
> - return err;
> -}
> -
> -static int __init it8712f_wdt_init(void)
> -{
> - int err = 0;
> -
> - if (it8712f_wdt_find(&address))
> - return -ENODEV;
> -
> - if (!request_region(address, 1, "IT8712F Watchdog")) {
> - pr_warn("watchdog I/O region busy\n");
> - return -EBUSY;
> - }
> -
> - err = it8712f_wdt_disable();
> - if (err) {
> - pr_err("unable to disable watchdog timer\n");
> - goto out;
> - }
> -
> - err = register_reboot_notifier(&it8712f_wdt_notifier);
> - if (err) {
> - pr_err("unable to register reboot notifier\n");
> - goto out;
> - }
> -
> - err = misc_register(&it8712f_wdt_miscdev);
> - if (err) {
> - pr_err("cannot register miscdev on minor=%d (err=%d)\n",
> - WATCHDOG_MINOR, err);
> - goto reboot_out;
> - }
> -
> - return 0;
> -
> -
> -reboot_out:
> - unregister_reboot_notifier(&it8712f_wdt_notifier);
> -out:
> - release_region(address, 1);
> - return err;
> -}
> -
> -static void __exit it8712f_wdt_exit(void)
> -{
> - misc_deregister(&it8712f_wdt_miscdev);
> - unregister_reboot_notifier(&it8712f_wdt_notifier);
> - release_region(address, 1);
> -}
> -
> -module_init(it8712f_wdt_init);
> -module_exit(it8712f_wdt_exit);
> diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c
> index 1d9f8591f38d..9da5ec544c8a 100644
> --- a/drivers/watchdog/it87_wdt.c
> +++ b/drivers/watchdog/it87_wdt.c
> @@ -419,6 +419,11 @@ static void __exit it87_wdt_exit(void)
> module_init(it87_wdt_init);
> module_exit(it87_wdt_exit);
>
> +/*
> + * Load this driver when the separate IT8712F driver, which was removed, is
> + * requested
> + */
> +MODULE_ALIAS("it8712f_wdt");
> MODULE_AUTHOR("Oliver Schuster");
> MODULE_DESCRIPTION("Hardware Watchdog Device Driver for IT87xx EC-LPC I/O");
> MODULE_LICENSE("GPL");
^ permalink raw reply
* Re: [PATCH] fix broken links in bpf, driver_api & filesystem documentation
From: Bagas Sanjaya @ 2026-05-05 5:23 UTC (permalink / raw)
To: Jeremy Bobbin, corbet; +Cc: linux-doc
In-Reply-To: <20260505014839.2670290-1-jer@jer.cx>
[-- Attachment #1: Type: text/plain, Size: 434 bytes --]
On Mon, May 04, 2026 at 06:48:38PM -0700, Jeremy Bobbin wrote:
> @@ -712,7 +712,7 @@ the following:
> * - 0x80
> - This filesystem has a snapshot (RO_COMPAT_HAS_SNAPSHOT).
> * - 0x100
> - - `Quota <Quota>`__ (RO_COMPAT_QUOTA).
> + - `Quota </filesystems/quota.html>`__ (RO_COMPAT_QUOTA).
Maybe :doc:`Quota </filesystems/quota>`?
Thanks.
--
An old man doll... just what I always wanted! - Clara
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v13 3/4] gpio: rpmsg: add generic rpmsg GPIO driver
From: Beleswar Prasad Padhi @ 2026-05-05 5:25 UTC (permalink / raw)
To: Arnaud POULIQUEN, Mathieu Poirier
Cc: Shenwei Wang, Andrew Lunn, Linus Walleij, Bartosz Golaszewski,
Jonathan Corbet, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Frank Li, Sascha Hauer, Shuah Khan,
linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, Pengutronix Kernel Team,
Fabio Estevam, Peng Fan, devicetree@vger.kernel.org,
linux-remoteproc@vger.kernel.org, imx@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, dl-linux-imx,
Bartosz Golaszewski
In-Reply-To: <9e2492d3-8753-46c7-8db6-5f1a80b4f2e9@foss.st.com>
Hi Arnaud,
On 04/05/26 22:34, Arnaud POULIQUEN wrote:
> Hi Beleswar,
>
> On 5/4/26 10:17, Beleswar Prasad Padhi wrote:
>
[...]
>>
>>>
>>> I may have misunderstood your solution. Could you please help me
>>> understand your proposal by explaining how you would handle three
>>> GPIO ports defined in the DT, considering that the endpoint
>>> addresses on the Linux side can be random?
>>> If I assume there is a unique endpoint on the remote side,
>>> I do not understand how you can match, on the firmware side,
>>> the Linux endpoint address to the GPIO port.
>>
>>
>> Sure, let me take an example:
>> Assumptions: 3 GPIO ports in DT, 3 endpoints in Linux (one per port),
>> 1 endpoint in remote (0xd) and 1 rpmsg channel (rpmsg-io)
>>
>> rpmsg {
>> rpmsg-io {
>> #address-cells = <1>;
>> #size-cells = <0>;
>>
>> gpio@25 {
>> compatible = "rpmsg-gpio";
>> reg = <25>;
>> gpio-controller;
>> #gpio-cells = <2>;
>> #interrupt-cells = <2>;
>> interrupt-controller;
>> };
>>
>> gpio@32 {
>> compatible = "rpmsg-gpio";
>> reg = <32>;
>> gpio-controller;
>> #gpio-cells = <2>;
>> #interrupt-cells = <2>;
>> interrupt-controller;
>> };
>>
>> gpio@35 {
>> compatible = "rpmsg-gpio";
>> reg = <35>;
>> gpio-controller;
>> #gpio-cells = <2>;
>> #interrupt-cells = <2>;
>> interrupt-controller;
>> };
>> };
>> };
>>
>> Code Flow:
>> 1. "rpmsg-io" channel is announced from remote firmware with unique dst
>> ept = 0xd.
>>
>> 2. rpmsg_core.c creates the default dynamic local ept for the channel
>> ept = 0x405.
>>
>> 3. rpmsg_core.c assigns the allocated addr to rpdev device:
>> rpdev->src = 0x405 and rpdev->dst = 0xd.
>>
>> 4. rpmsg_gpio_channel_probe() is triggered. For *each* of the GPIO ports
>> in DT, it will trigger rpmsg_gpiochip_register() which will now:
>> a. Call port->ept = rpmsg_create_ept(rpdev,
>> rpmsg_gpio_channel_callback,
>> port,
>> {rpdev.id.name,
>> RPMSG_ADDR_ANY,
>> RPMSG_ADDR_ANY});
>> Ex- port->ept->addr = 0x408
>>
>> b. Prepare a 8-byte message having 2 fields:
>> port->ept->addr (0x408) and port->idx (25)
>>
>> c. Send this message to remote firmware on default channel ept
>> (0x405 -> 0xd) by:
>> rpmsg_send(rpdev->ept, &message, sizeof(message));
>>
>> d. Remote side receives this message and creates a map of the
>> linux_ept_addr to gpio_port. (0x408 <-> 25)
>>
>> 5. After this point, any gpio messages sent from Linux from gpio port
>> endpoints (Ex- 0x408) can be decoded at remote side by looking up
>> its map (Ex- map[0x408] = 25).
>>
>> 6. Any messages sent from remote to Linux for a particular gpio port can
>> also be decoded at Linux by simply fetching the priv pointer to get
>> the per-port device:
>> struct rpmsg_gpio_port *port = priv;
>>
>
> Thanks for the details!
>
> To sum up:
> - the default endpoint acts as the GPIO controller (0x405),
> - one extra Linux endpoint is created per port defined in DT.
>
> This should work, but my concerns remain the same:
>
> 1) This implementation forces the remote processor to handle a single
> endpoint instead of one endpoint per port. This may add complexity to
> the remote firmware if each port is managed in a separate thread.
A. Not really, I just chose 1 remote endpoint for this example as you
suggested to. We can scale it for two-way communication via the
get_config message like you suggested below.
B. Isn't it a bad design of the firmware if it is handling 10 gpio ports
in 10 threads? The logic to handle all the ports is the same, only
the parameters (e.g. line number, msg) is different.
>
> 2) Linux, as a consumer, should not expose its capabilities to the remote
> side (in your proposal it enumerates the ports defined in the DT). In my view, the remote processor should expose its capabilities as the
> provider.
Agreed on this.
>
> From my perspective, based on your proposal:
> 1) Linux should send a get_config message to the remote proc (0x405 -> 0xD). 2) The remote processor would respond with the list of ports, associated
> with an remote endpoint addresses.
Agreed, we can scale it for multiple remote endpoints like this.
> 3) Linux would parse the response, compare it with the DT, enable the GPIO
> ports accordingly, creating it local endpoint and associating it with
> the remote endpoint.
> Using name service to identify the ports should avoid step 1 & 2 ...
Yes, but won't that make a lot of hard-codings in the driver?
+static struct rpmsg_device_id rpmsg_gpio_channel_id_table[] = {
+ { .name = "rpmsg-io-25" },
+ { .name = "rpmsg-io-32" },
+ { .name = "rpmsg-io-35" },
+ { },
+};
What if tomorrow another vendor decides to add more remoteproc
controlled GPIO ports to Linux, they would have to update this struct in
the driver everytime. And the port indexes (25/32/35) could also differ
between vendors. We should make the driver dynamic i.e. vendor
agnostic.
I think querying the remote firmware at runtime (step 1 & 2 above) is a
common design pattern and makes the driver vendor agnostic. But feel
free to correct me.
>
> At the end, whatever solution is implemented, my main concern is that the
> Linux driver design should, if possible, avoid adding unnecessary complexity
> or limitations on the remote side (for instance in openAMP project).
Yes definitely, I want the same. Feel free to let me know if this does
not suit with the OpenAMP project.
Thanks,
Beleswar
>
> Thanks,
> Arnaud
>
>
>> So Linux does not need to send the port idx everytime while sending a
>> gpio message anymore.
>>
>> Thanks,
>> Beleswar
>>
>> [...]
>>
>
^ permalink raw reply
* Re: [PATCH v12 12/22] gpu: nova-core: mm: Add page table entry operation traits
From: Alexandre Courbot @ 2026-05-05 5:37 UTC (permalink / raw)
To: Joel Fernandes
Cc: linux-kernel, Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Dave Airlie, Daniel Almeida, dri-devel,
rust-for-linux, nova-gpu, Nikola Djukic, David Airlie, Boqun Feng,
John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer,
Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc
In-Reply-To: <bd210abc-590f-4011-8337-21b54780fd4c@nvidia.com>
On Tue May 5, 2026 at 4:28 AM JST, Joel Fernandes wrote:
>
>
> On 5/4/2026 10:31 AM, Alexandre Courbot wrote:
>> On Sun May 3, 2026 at 4:19 AM JST, Joel Fernandes wrote:
>>>> Please reorder things so they land, as much as possible, in their final
>>>> form. In this case this probably means defining the trait *before* the V2
>>>> and V3 page table definitions, so they can implement it from the get-go.
>>>
>>> That is a reasonable approach too, I can try to do that, but it is
>>> misleading to say '270 lines of diff that reviewers will have processed for
>>> nothing' which is nothing but fiction. Please look more carefully, the
>>> patch is iterative on the series.
>>
>> For context, here is where the 270 lines of diff come from:
>>
>> drivers/gpu/nova-core/mm/pagetable/ver2.rs | 150 ++++++++------
>> drivers/gpu/nova-core/mm/pagetable/ver3.rs | 120 +++++++----
>>
>> But the number is not important.
>
> It is important, numbers and accuracy are really important things
> especially on the Linux kernel mailing list. Sorry if you feel that is
> inconvenient. And even quoting the 270 is a falsehood, the 270 lines were
> not refactored, only 90 lines or so was.
I am not particularly attached to this 270 number. It represents the
diff that I believe shouldn't be in this commit. Counting the number of
changed lines is also perfectly valid.
But again, that's not the point. Let's set my metric aside: we still
have, by your own account, 90 lines of churn that could be avoided by
the following 3 steps, each of which takes one minute to perform:
- Move the trait definitions of `pagetable.rs` into their own commit.
- Move that new commit before the ones introducing `ver2.rs` and
`ver3.rs`.
- Squash the relevant parts of the remainder into the commit introducing
`ver2.rs` or `ver3.rs`.
By doing that, on top of removing 90 lines of immediate follow-up
changes, you have also moved the public interface of the page tables
before their implementation, making all 3 patches easier to process as
reviewers are now introduced to how that code will be used *before* the
implementation details.
That's my closing argument on this topic and I won't insist if you are
not convinced this is worth doing; please act on it, or not, as you see
fit.
^ permalink raw reply
* Re: [PATCH 8/9] docs: maintainers_include: don't ignore invalid profile entries
From: Mauro Carvalho Chehab @ 2026-05-05 5:45 UTC (permalink / raw)
To: Miguel Ojeda
Cc: Jonathan Corbet, Linux Doc Mailing List, Mauro Carvalho Chehab,
linux-kernel, rust-for-linux, Björn Roy Baron, Alice Ryhl,
Andreas Hindborg, Benno Lossin, Boqun Feng, Danilo Krummrich,
Gary Guo, Miguel Ojeda, Shuah Khan, Trevor Gross
In-Reply-To: <CANiq72=2cB_bDa0c1FA4aOMx8d=RyuNs_O+_72EiUurMAY+0mw@mail.gmail.com>
On Tue, 5 May 2026 02:20:45 +0200
Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> wrote:
> On Tue, May 5, 2026 at 2:08 AM Mauro Carvalho Chehab
> <mchehab+huawei@kernel.org> wrote:
> >
> > Also, with time, maintainers may change their employers while still
> > keeping their maintainership status.
> >
> > So, I'd say that whatever is there at the "P" entry, or where it is
> > located (either on a ReST file at the Kernel or on some external URL),
> > it should reflect the model that a maintainer or subsystem community
> > that actively participate at the Kernel development agrees with.
> > This should be vendor-agnostic.
>
> I am not sure what you mean. By "vendored" I don't mean
> companies/employers, I mean that the file comes from an upstream
> repository:
>
> https://github.com/Rust-for-Linux/pin-init/blob/main/CONTRIBUTING.md
>
> Nevertheless, it is true that this really is a special case, in that
> the upstream project decided to provide something that could then be
> fit into the `P:` field.
>
> One could say "let's ask them to do rst upstream", but to be honest,
> it is simpler to just put a hyperlink to GitHub's rendered file.
> Markdown is anyway a better fit for their file.
Ok. Then it P entry could be:
P: https://github.com/Rust-for-Linux/pin-init/blob/main/CONTRIBUTING.md
> > Generating on the fly is a bad idea, as when one uses:
> >
> > make O=SOME_DIR
> >
> > It is expected that the original source directory will remain
> > untouched.
>
> I am not sure why that would be a problem -- the output would be in
> `objtree`, not in `srctree`, as usual.
No, this won't work. See sphinx-build help:
$ sphinx-build --help
usage: sphinx-build [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]
...
positional arguments:
SOURCE_DIR path to documentation source files
OUTPUT_DIR path to output directory
filenames (optional) a list of specific files to rebuild. Ignored if --write-all is specified
The way Sphinx works is that it will consider a source file
only for stuff inside the SOURCE_DIR positional parameter passed to
sphinx-build. It handles SOURCE_DIR the same way chroot does:
assuming that you add at toctable inside Documentation/index.rst
(e.g on its root level) something like like this:
/DIR/file.rst
../DIR/file.rst
../../../../../../../../../DIR/file.rst
They all will be interpreted as:
{SOURCE_DIR}/DIR/file.rst
Making impossible to reference any file at the OUTPUT_DIR, except
if you place OUTPUT_DIR inside SOURCE_DIR.
On normal builds, where we have "output" dir inside "Documentation",
this works, but when O=DIR is used, the output directory is
typically elsewhere, which effectively breaks O=DIR support.
Besides it, Sphinx makes a 1:1 map between a source rst file
and a destination html file (for make htmldocs).
MAINTAINERS is actually a good example of such limitation: I would
love to produce per-subsystem output files from a single
maintainers.rst file, but Sphinx doesn't allow that.
>
> > I suggested pandoc as a one-time conversion if one wants to migrate
> > from MD to rst, as for simple documents like this one, it works
> > fine.
>
> They are the maintainers, so it is up to them, but it is simpler to
> use a hyperlink.
>
> (The file is trivial, i.e. the conversion can be done in a moment
> without `pandoc`).
Ok.
Thanks,
Mauro
^ permalink raw reply
* Re: [PATCH] fix broken links in bpf, driver_api & filesystem documentation
From: Randy Dunlap @ 2026-05-05 5:56 UTC (permalink / raw)
To: Bagas Sanjaya, Jeremy Bobbin, corbet; +Cc: linux-doc
In-Reply-To: <afl-79jS4RiGpx_c@archie.me>
On 5/4/26 10:23 PM, Bagas Sanjaya wrote:
> On Mon, May 04, 2026 at 06:48:38PM -0700, Jeremy Bobbin wrote:
>> @@ -712,7 +712,7 @@ the following:
>> * - 0x80
>> - This filesystem has a snapshot (RO_COMPAT_HAS_SNAPSHOT).
>> * - 0x100
>> - - `Quota <Quota>`__ (RO_COMPAT_QUOTA).
>> + - `Quota </filesystems/quota.html>`__ (RO_COMPAT_QUOTA).
>
> Maybe :doc:`Quota </filesystems/quota>`?
>
> Thanks.
I just tried that - unsuccessfully. (Maybe because this is in
a table?)
What works for me is this: (FWIW)
--- linux-next-20260504.orig/Documentation/filesystems/ext4/super.rst
+++ linux-next-20260504/Documentation/filesystems/ext4/super.rst
@@ -408,11 +408,11 @@ The ext4 superblock is laid out as follo
* - 0x240
- __le32
- s_usr_quota_inum
- - Inode number of user `quota <quota>`__ file.
+ - Inode number of user `quota <../quota.html>`__ file.
* - 0x244
- __le32
- s_grp_quota_inum
- - Inode number of group `quota <quota>`__ file.
+ - Inode number of group `quota <../quota.html>`__ file.
* - 0x248
- __le32
- s_overhead_blocks
@@ -712,7 +712,7 @@ the following:
* - 0x80
- This filesystem has a snapshot (RO_COMPAT_HAS_SNAPSHOT).
* - 0x100
- - `Quota <Quota>`__ (RO_COMPAT_QUOTA).
+ - `Quota <../quota.html>`__ (RO_COMPAT_QUOTA).
* - 0x200
- This filesystem supports “bigalloc”, which means that file extents are
tracked in units of clusters (of blocks) instead of blocks
--
~Randy
^ permalink raw reply
* Re: [PATCH 2/9] docs: escape ** glob pattern in MAINTAINERS descriptions
From: Mauro Carvalho Chehab @ 2026-05-05 5:57 UTC (permalink / raw)
To: Joe Perches
Cc: Randy Dunlap, Jonathan Corbet, Linux Doc Mailing List,
Mauro Carvalho Chehab, linux-kernel, rust-for-linux,
Andrew Morton, Matteo Croce, Shuah Khan, Matteo Croce
In-Reply-To: <0b127006e552de855ba7d33c15d2199436a001cb.camel@perches.com>
On Mon, 04 May 2026 20:19:50 -0700
Joe Perches <joe@perches.com> wrote:
> On Mon, 2026-05-04 at 14:20 -0700, Randy Dunlap wrote:
> > On 5/4/26 8:51 AM, Mauro Carvalho Chehab wrote:
> > From: Matteo Croce <[teknoraver@meta.com](mailto:teknoraver@meta.com)>
> > >
> > > Escape '**' in the MAINTAINERS descriptions section to prevent
> > > reStructuredText from interpreting it as bold/strong inline markup,
> > > which causes a warning when running 'make htmldocs'.
> []
> > It's nice to eliminate one warning from 'make htmldocs', so this is good
> > in that regard. However, there are still multiple problems (not Warnings)
> > with '*' characters in the MAINTAINERS file:
> >
> > 1) F: */net/* all files in "any top level directory"/net
> >
> > In the html output, it shows "/net/" italicized (that's what one * does).
> >
> > 2) F: fs/**/*foo*.c all *foo*.c files in any subdirectory of fs
> >
> > In the html output, it shows
> >
> > F: fs/**/foo.c all foo.c files in any subdirectory of fs
> >
> > with both occurrences of "foo.c" italicized (dropping the '*' characters).
> >
> > These 2 examples are actively wrong.
> >
> > [adding new:]
> > We would be better served by just putting file patterns inside ``fs/**/*foo*.c``
> > quotation marks IMO.
The logic actually does that already:
#
# Mark paths (and regexes) as literal text for improved
# readability and to escape any escapes.
#
if field in ['F', 'N', 'X', 'K']:
# But only if not already marked :)
if not ':doc:' in details:
details = '``%s``' % (details)
The only reason why this patch is needed is for the header part, which
describes that "**" is allowed. If you look at the final code, this
replace code will be only at the header parsing logic:
def parse_descriptions(self, line):
"""Handle contents of the descriptions section."""
...
# Escape the escapes in preformatted text.
line = self.linkify(line).replace("\\", "\\\\").replace("**", "\\**")
self.header += "| " + line
> >
> > Ah, similar to what you do in the table output.
> >
> > Oh, with one little glitch:
> > E.g., in the very first entry for 3C59X NETWORK DRIVER,
> > F: Documentation/networking/device_drivers/ethernet/3com/vortex.rst
> > F: drivers/net/ethernet/3com/3c59x.c
> > it looks like automarkup is applied to the Documentation file so these
> > 2 files are displayed as:
> >
> > networking/device_drivers/ethernet/3com/vortex, drivers/net/ethernet/3com/3c59x.c
> >
> > with the Doc file underlined and missing both Documentation and .rst.
> > Or maybe that's what you intended since the automarkup link does work.
> > It's just not what I expected. Oh well.
>
> Please stop trying to format MAINTAINERS into rst.
> It shouldn't be formatted.
> It's simple text.
MAINTAINERS is not a simple text. It is a machine-readable text
database.
Thanks,
Mauro
^ permalink raw reply
* Re: [PATCH 2/9] docs: escape ** glob pattern in MAINTAINERS descriptions
From: Mauro Carvalho Chehab @ 2026-05-05 6:46 UTC (permalink / raw)
To: Joe Perches
Cc: Randy Dunlap, Jonathan Corbet, Linux Doc Mailing List,
Mauro Carvalho Chehab, linux-kernel, rust-for-linux,
Andrew Morton, Matteo Croce, Shuah Khan, Matteo Croce
In-Reply-To: <20260505075725.0f3d2a6b@foz.lan>
On Tue, 5 May 2026 07:57:25 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> On Mon, 04 May 2026 20:19:50 -0700
> Joe Perches <joe@perches.com> wrote:
>
> > On Mon, 2026-05-04 at 14:20 -0700, Randy Dunlap wrote:
> > > On 5/4/26 8:51 AM, Mauro Carvalho Chehab wrote:
> > > From: Matteo Croce <[teknoraver@meta.com](mailto:teknoraver@meta.com)>
> > > >
> > > > Escape '**' in the MAINTAINERS descriptions section to prevent
> > > > reStructuredText from interpreting it as bold/strong inline markup,
> > > > which causes a warning when running 'make htmldocs'.
> > []
> > > It's nice to eliminate one warning from 'make htmldocs', so this is good
> > > in that regard. However, there are still multiple problems (not Warnings)
> > > with '*' characters in the MAINTAINERS file:
> > >
> > > 1) F: */net/* all files in "any top level directory"/net
> > >
> > > In the html output, it shows "/net/" italicized (that's what one * does).
> > >
> > > 2) F: fs/**/*foo*.c all *foo*.c files in any subdirectory of fs
> > >
> > > In the html output, it shows
> > >
> > > F: fs/**/foo.c all foo.c files in any subdirectory of fs
> > >
> > > with both occurrences of "foo.c" italicized (dropping the '*' characters).
> > >
> > > These 2 examples are actively wrong.
Heh, I read this too quickly: you're talking about the header descriptions
where we have:
F: *Files* and directories wildcard patterns.
A trailing slash includes all files and subdirectory files.
F: drivers/net/ all files in and below drivers/net
F: drivers/net/* all files in drivers/net, but not below
F: */net/* all files in "any top level directory"/net
F: fs/**/*foo*.c all *foo*.c files in any subdirectory of fs
Yeah, you're right: the header parsing logic won't handle this well.
This should do the trick:
def parse_descriptions(self, line):
"""Handle contents of the descriptions section."""
# Have we reached the end of the preformatted Descriptions text?
if line.startswith("Maintainers"):
self.descriptions = False
self.header += "\n" + line
return
# Look for and record field letter to field name mappings:
# R: Designated *reviewer*: FullName <address@domain>
m = re.match(r"\s+(\S):\s+(\S+)", line)
if m:
field = m.group(1)
details = m.group(2)
if field not in self.fields:
m = re.search(r"\*([^\*]+)\*", line)
if m:
self.fields[field] = m.group(1)
elif field in ['F', 'N', 'X', 'K']:
line = line.replace(details, f'``{details}``')
# Escape the escapes in preformatted text.
self.header += "| " + self.linkify(line).replace("\\", "\\\\")
I'll address it in v2.
Thanks,
Thanks,
Mauro
^ permalink raw reply
* Re: [PATCH v3 1/9] kernel/api: introduce kernel API specification framework
From: Sasha Levin @ 2026-05-05 7:45 UTC (permalink / raw)
To: Nathan Chancellor
Cc: Sasha Levin, Nicolas Schier, linux-api, linux-kernel, linux-doc,
linux-fsdevel, linux-kbuild, linux-kselftest, workflows, tools,
x86, Thomas Gleixner, Paul E . McKenney, Greg Kroah-Hartman,
Jonathan Corbet, Dmitry Vyukov, Randy Dunlap, Cyril Hrubis,
Kees Cook, Jake Edge, David Laight, Askar Safin, Gabriele Paoloni,
Mauro Carvalho Chehab, Christian Brauner, Alexander Viro,
Andrew Morton, Masahiro Yamada, Shuah Khan, Ingo Molnar,
Arnd Bergmann
In-Reply-To: <177726106581.2478607.12645653803520391071.b4-review@b4>
On Sun, Apr 26, 2026 at 11:37:45PM -0400, Nathan Chancellor wrote:
> On Fri, 24 Apr 2026 12:51:21 -0400, Sasha Levin <sashal@kernel.org> wrote:
> > diff --git a/kernel/Makefile b/kernel/Makefile
> > index 6785982013dc..564315153643 100644
> > --- a/kernel/Makefile
> > +++ b/kernel/Makefile
> > @@ -59,6 +59,9 @@ obj-y += dma/
> > obj-y += entry/
> > obj-y += unwind/
> > obj-$(CONFIG_MODULES) += module/
> > +obj-$(CONFIG_KAPI_SPEC) += api/
> > +# Ensure api/ is always cleaned even when CONFIG_KAPI_SPEC is not set
> > +obj- += api/
>
> If $(CONFIG_KAPI_SPEC) is not set, shouldn't
>
> obj-$(CONFIG_KAPI_SPEC) += api/
>
> evaluate to
>
> obj- += api/
>
> anyways? Why the duplication? This is the only place in the kernel where
> this would be needed?
You are right, the explicit "obj- += api/" is redundant. Nicolas pointed
to scripts/Makefile.clean which already evaluates obj- during
'make clean', so the conditional gate alone is sufficient. Dropped in
v4.
> > diff --git a/kernel/api/.gitignore b/kernel/api/.gitignore
> > new file mode 100644
> > index 000000000000..ca2f632621cf
> > --- /dev/null
> > +++ b/kernel/api/.gitignore
> > @@ -0,0 +1,2 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +/generated_api_specs.c
>
> This appears unused?
Correct, leftover from an earlier prototype that generated a single
combined .c file. Nothing in the current series produces
generated_api_specs.c, so the .gitignore is removed entirely in v4.
> > diff --git a/kernel/api/Kconfig b/kernel/api/Kconfig
> > new file mode 100644
> > index 000000000000..d1072728742a
> > --- /dev/null
> > +++ b/kernel/api/Kconfig
> > @@ -0,0 +1,77 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +#
> > +# Kernel API Specification Framework Configuration
> > +#
> > +
> > +config KAPI_SPEC
> > + bool "Kernel API Specification Framework"
> > + default n
>
> I think 'default n' is tautological since 'n' is the default for all
> bool symbols. Consider dropping it on all symbols throughtout this file.
Dropped from KAPI_SPEC, KAPI_RUNTIME_CHECKS, and KAPI_SPEC_DEBUGFS in
v4. KAPI_KUNIT_TEST already uses "default KUNIT_ALL_TESTS" and is left
unchanged.
Thanks for the review!
--
Thanks,
Sasha
^ permalink raw reply
* Re: [PATCH v3 1/9] kernel/api: introduce kernel API specification framework
From: Sasha Levin @ 2026-05-05 7:45 UTC (permalink / raw)
To: Nicolas Schier
Cc: Sasha Levin, Nathan Chancellor, linux-api, linux-kernel,
linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E . McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
In-Reply-To: <afIykLLPj7m0fcsX@levanger>
On Wed, Apr 29, 2026 at 06:32:16PM +0200, Nicolas Schier wrote:
> On Sun, Apr 26, 2026 at 11:37:45PM -0400, Nathan Chancellor wrote:
> > On Fri, 24 Apr 2026 12:51:21 -0400, Sasha Levin <sashal@kernel.org> wrote:
> > > diff --git a/kernel/Makefile b/kernel/Makefile
> > > [...]
> > > +obj-$(CONFIG_KAPI_SPEC) += api/
> > > +# Ensure api/ is always cleaned even when CONFIG_KAPI_SPEC is not set
> > > +obj- += api/
> >
> > If $(CONFIG_KAPI_SPEC) is not set, shouldn't
> >
> > obj-$(CONFIG_KAPI_SPEC) += api/
> >
> > evaluate to
> >
> > obj- += api/
> >
> > anyways? Why the duplication? This is the only place in the kernel where
> > this would be needed?
>
> yes, this is definitely not needed, as obj- is always evaluated during
> 'make clean', cp. scripts/Makefile.clean [1].
>
> Kind regards
> Nicolas
>
> [1]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/scripts/Makefile.clean?h=v7.1-rc1#n30
Thanks for the pointer!
The redundant "obj- += api/" and the accompanying comment are dropped in v4.
--
Thanks,
Sasha
^ permalink raw reply
* Re: [PATCH v3 1/9] kernel/api: introduce kernel API specification framework
From: Sasha Levin @ 2026-05-05 7:45 UTC (permalink / raw)
To: Nicolas Schier
Cc: Sasha Levin, Nathan Chancellor, linux-api, linux-kernel,
linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E . McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
In-Reply-To: <afI1LMB_vNMWYU7o@levanger>
On Wed, Apr 29, 2026 at 06:43:24PM +0200, Nicolas Schier wrote:
> On Fri, Apr 24, 2026 at 12:51:21PM -0400, Sasha Levin wrote:
> > diff --git a/kernel/api/Makefile b/kernel/api/Makefile
> > new file mode 100644
> > index 0000000000000..c0a13fc590e4a
> > --- /dev/null
> > +++ b/kernel/api/Makefile
> > @@ -0,0 +1,14 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# Makefile for the Kernel API Specification Framework
> > +#
> > +
> > +# Core API specification framework
> > +obj-$(CONFIG_KAPI_SPEC) += kernel_api_spec.o
>
> Bike-shedding: I'd use 'obj-y' here, to state clearly that
> kernel_api_spec.c is the core part in the kernel/api/ subdir. If
> CONFIG_KAPI_SPEC is unset, the subfir will not be entered at all.
Agreed, switched to "obj-y" in v4. The subdir gate moves up to
kernel/Makefile and the entry inside kernel/api/Makefile becomes
unconditional. The other two entries (KAPI_SPEC_DEBUGFS,
KAPI_KUNIT_TEST) keep their own conditional guards since they are
optional sub-features.
--
Thanks,
Sasha
^ permalink raw reply
* Re: [PATCH v3 2/9] kernel/api: enable kerneldoc-based API specifications
From: Sasha Levin @ 2026-05-05 7:45 UTC (permalink / raw)
To: Nicolas Schier
Cc: Sasha Levin, Nathan Chancellor, linux-api, linux-kernel,
linux-doc, linux-fsdevel, linux-kbuild, linux-kselftest,
workflows, tools, x86, Thomas Gleixner, Paul E . McKenney,
Greg Kroah-Hartman, Jonathan Corbet, Dmitry Vyukov, Randy Dunlap,
Cyril Hrubis, Kees Cook, Jake Edge, David Laight, Askar Safin,
Gabriele Paoloni, Mauro Carvalho Chehab, Christian Brauner,
Alexander Viro, Andrew Morton, Masahiro Yamada, Shuah Khan,
Ingo Molnar, Arnd Bergmann
In-Reply-To: <afNrbm8URHlClZ-8@levanger>
On Thu, Apr 30, 2026 at 04:47:10PM +0200, Nicolas Schier wrote:
> On Fri, Apr 24, 2026 at 12:51:22PM -0400, Sasha Levin wrote:
> > +# Generate API spec headers from kernel-doc comments
> > +ifeq ($(CONFIG_KAPI_SPEC),y)
> > +# Function to check if a file has API specifications
> > +has-apispec = $(shell grep -qE '^\s*\*\s*context-flags:' $(src)/$(1) 2>/dev/null && echo $(1))
> > +
> > +# Get base names without directory prefix
> > +c-objs-base := $(notdir $(real-obj-y) $(real-obj-m))
> > +# Filter to only .o files with corresponding .c source files
> > +c-files := $(foreach o,$(c-objs-base),$(if $(wildcard $(src)/$(o:.o=.c)),$(o:.o=.c)))
>
> Looks to me as if the two lines above are redundant, since 'find'
> (below) will find all files gathered in $(c-files).
Right, those two lines are dropped in v4. The replacement uses the
kbuild-derived file list described below, so neither set survives.
> > +# Also check for any additional .c files that contain API specs but are included
> > +extra-c-files := $(shell find $(src) -maxdepth 1 -name "*.c" -exec grep -l '^\s*\*\s*\(long-desc\|context-flags\|state-trans\):' {} \; 2>/dev/null | xargs -r basename -a)
> > +# Combine both lists and remove duplicates
> > +all-c-files := $(sort $(c-files) $(extra-c-files))
> > +# Only include files that actually have API specifications
> > +apispec-files := $(foreach f,$(all-c-files),$(call has-apispec,$(f)))
> > +# Generate apispec targets with proper directory prefix
> > +apispec-y := $(addprefix $(obj)/,$(apispec-files:.c=.apispec.h))
>
> To goal is to find any relevant C file in $(src)/ (but not deeper below)
> that holds KAPI documentation, right?
>
> I do not like the find call, as it picks up anything. Might it make
> sense to evaluate $(obj-) along with $(obj-y) and $(obj-m) to pick up
> all C files that are references in kbuild?
>
>
>
> # in top definition block -- before 'include $(kbuild-file)' et al.
> obj- :=
>
> # below the definitions of real-obj-{y,m}
> real-obj-any := $(call real-search, $(obj-y) $(obj-m) $(obj-), .o, -objs -y -m -)
>
> has-apispec = $(shell grep -lE '^\s*\*\s*context-flags:' $(1) 2>/dev/null)
> apispec-y := $(patsubst $(src)/%.c, $(obj)/%.apispec.h, $(call has-apispec,
> $(patsubst $(obj)/%.o, $(src)/%.c, $(real-obj-any))))
>
> #...
>
> # Source files that include their own apispec.h need to depend on it
> $(apispec-y:.apispec.h=.o): $(obj)/%.o: $(obj)/%.apispec.h
>
> (untested)
Thanks, the kbuild-driven approach is much cleaner. v4 takes your sketch
with two adjustments:
1. obj-m is already addprefix'd with $(obj)/ by line 116 of
Makefile.build at the point where this block runs, so calling
real-search again on the mixed list double-prefixes the module
entries (giving $(src)/$(obj)/foo.c). v4 uses the existing
$(real-obj-y)/$(real-obj-m) and strips the prefix in patsubst
instead:
apispec-c-files := $(call has-apispec, \
$(patsubst $(obj)/%.o,$(src)/%.c, \
$(filter-out %/built-in.a,$(real-obj-y) $(real-obj-m))))
apispec-y := $(patsubst $(src)/%.c,$(obj)/%.apispec.h,$(apispec-c-files))
2. The has-apispec grep needs to match the same set of keys that
tools/lib/python/kdoc/kdoc_apispec.py actually parses, which is
"contexts:", "context-flags:" and "context:" interchangeably (see
_get_section calls around line 866 of kdoc_apispec.py). The original
grep for "context-flags:" matched zero files in the tree (every
instrumented file uses "contexts:"), which is the latent bug behind
the build failure you saw. v4 widens the regex:
has-apispec = $(shell grep -lE \
'^[[:space:]]*\*[[:space:]]*(contexts|context-flags|context):' \
$(1) 2>/dev/null)
> > diff --git a/scripts/Makefile.clean b/scripts/Makefile.clean
> > index 6ead00ec7313b..f78dbbe637f27 100644
> > --- a/scripts/Makefile.clean
> > +++ b/scripts/Makefile.clean
> > @@ -35,6 +35,9 @@ __clean-files := $(filter-out $(no-clean-files), $(__clean-files))
> >
> > __clean-files := $(wildcard $(addprefix $(obj)/, $(__clean-files)))
> >
> > +# Also clean generated apispec headers (computed dynamically in Makefile.build)
> > +__clean-files += $(wildcard $(obj)/*.apispec.h)
>
> We have a list of wildcard clean patterns in top-level Makefile
> (line 2114 ff.); please add '*.apispec.h' there instead.
Will fix.
> When I apply the series on top of v7.1, compilation fails with
>
> ../fs/open.c:2148:10: fatal error: open.apispec.h: No such file or directory
> ../fs/read_write.c:2519:10: fatal error: read_write.apispec.h: No such file or directory
This is the symptom of (2) above. fs/open.c and fs/read_write.c only
declare "contexts: process, sleepable" (no "context-flags:" anywhere in
the tree, confirmed via grep), so apispec-files was always empty and no
*.apispec.h ever got generated. With CONFIG_KAPI_SPEC=y the
"#if IS_ENABLED(CONFIG_KAPI_SPEC)" guarded include then fails. Also
reproducible on v7.0; thanks for catching it.
Thanks for the review and the kbuild sketch!
--
Thanks,
Sasha
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox