* [PATCH 0/10] pull request (net): ipsec 2026-08-18
@ 2026-08-18 9:28 Steffen Klassert
2026-08-18 9:28 ` [PATCH 01/10] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full Steffen Klassert
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
1) xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full
Tighten the secpath-depth check so a full chain can't write
past xvec[].
2) Add and revert "esp: do not unref managed frag pages in esp_ssg_unref()"
The patch does not fully fully resolve the issue, a corrected version
will follow.
3) xfrm: espintcp: fix UAF during close
Synchronize espintcp close with the xfrm_trans_reinject work
queue so the freed socket message isn't dereferenced again.
4) xfrm: drop ESP-in-TCP packets with no ingress device
Drop queued ESP-in-TCP records whose saved ingress device has
gone away, avoiding a NULL device deref in the XFRM input path.
5) xfrm: avoid lock inversion in nat keepalive work
Split the NAT keepalive walk into a reference-collection phase
and a per-state lock phase to break the AB-BA with state removal.
This patch has some issues that are fixed with a followup patch.
6) xfrm: Fix skb double-free in xfrm_dev_direct_output()
Stop freeing the skb unconditionally in xfrm_dev_direct_output(),
letting local_out()'s result indicate when ownership has moved on.
7) xfrm: ah6: validate routing header segments_left
Validate the segments_left/hdrlen invariant before rearranging
the routing-header addresses, avoiding an OOB memmove on
malformed HDRINCL packets.
8) xfrm: fix xfrm_state_construct() auth-trunc leak
Detect an already-attached auth-trunc allocation by the pointer
rather than inferring it from the algorithm id, so a prior
attach isn't overwritten and lost.
9) xfrm: bound nat keepalive state collection
Replace the per-state allocation in the NAT keepalive walk
with a fixed-size batch that drains under BH-disabled locking
and resumes from the cursor, bounding the worker's memory.
Please pull or let me know if there are problems.
Thanks!
The following changes since commit 3f1f755366687d051174739fb99f7d560202f60b:
net: openvswitch: reject oversized nested action attrs (2026-07-11 13:09:11 +0200)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec.git tags/ipsec-2026-08-18
for you to fetch changes up to 4e9442ce551ebd84b52ad649df721e2dc28af95a:
xfrm: bound nat keepalive state collection (2026-08-18 07:35:01 +0200)
----------------------------------------------------------------
ipsec-2026-08-18
----------------------------------------------------------------
Asim Viladi Oglu Manizada (1):
xfrm: ah6: validate routing header segments_left
Maher Azzouzi (1):
esp: do not unref managed frag pages in esp_ssg_unref()
Sabrina Dubroca (1):
xfrm: espintcp: fix UAF during close
Sanghyun Park (1):
xfrm: Fix skb double-free in xfrm_dev_direct_output()
Steffen Klassert (1):
Revert "esp: do not unref managed frag pages in esp_ssg_unref()"
Xiang Mei (1):
xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full
Zhiling Zou (1):
xfrm: drop ESP-in-TCP packets with no ingress device
Zihan Xi (3):
xfrm: avoid lock inversion in nat keepalive work
xfrm: fix xfrm_state_construct() auth-trunc leak
xfrm: bound nat keepalive state collection
net/ipv6/ah6.c | 29 ++++++++++++++---------
net/ipv6/xfrm6_input.c | 2 +-
net/xfrm/espintcp.c | 9 +++++++-
net/xfrm/xfrm_nat_keepalive.c | 53 +++++++++++++++++++++++++++++++++++--------
net/xfrm/xfrm_output.c | 4 +---
net/xfrm/xfrm_user.c | 2 +-
6 files changed, 73 insertions(+), 26 deletions(-)
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 01/10] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 02/10] esp: do not unref managed frag pages in esp_ssg_unref() Steffen Klassert
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Xiang Mei <xmei5@asu.edu>
The depth check in xfrm6_input_addr() is off by one:
if (1 + sp->len == XFRM_MAX_DEPTH)
goto drop;
...
sp->xvec[sp->len++] = x;
xfrm_input() can leave sp->len == XFRM_MAX_DEPTH, and the transport-mode
receive path re-enters IPv6 input via xfrm_trans_reinject() with that
secpath preserved. If the inner packet carries a destination-options HAO
option or a type-2 routing header, xfrm6_input_addr() is called with
sp->len == XFRM_MAX_DEPTH; the check (1 + 6 == 6) is false, so
sp->xvec[sp->len++] writes one slot past the 6-element xvec[]. The write
stays within the sec_path allocation (invisible to KASAN); UBSAN_BOUNDS
flags it and panics under panic_on_warn.
Use "sp->len >= XFRM_MAX_DEPTH", matching xfrm_input(). This also
restores one chain level the old check rejected at sp->len == 5.
UBSAN: array-index-out-of-bounds in net/ipv6/xfrm6_input.c:309:10
index 6 is out of range for type 'xfrm_state *[6]'
Fixes: 9473e1f631de ("[XFRM] MIPv6: Fix to input RO state correctly.")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/ipv6/xfrm6_input.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 89d0443b5307..07edef258984 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -247,7 +247,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
goto drop;
}
- if (1 + sp->len == XFRM_MAX_DEPTH) {
+ if (sp->len >= XFRM_MAX_DEPTH) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR);
goto drop;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 02/10] esp: do not unref managed frag pages in esp_ssg_unref()
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
2026-08-18 9:28 ` [PATCH 01/10] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 03/10] xfrm: espintcp: fix UAF during close Steffen Klassert
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Maher Azzouzi <maherazz04@gmail.com>
esp_ssg_unref() releases the page references held on the source
scatterlist after the AEAD operation completes. It calls
skb_page_unref() on every frag page for an out-of-place transform
(req->src != req->dst), and in the error path of esp_output_tail()
(already_unref == true) on the request's own scatterlist.
This is wrong when the skb carries managed frags
(SKBFL_MANAGED_FRAG_REFS). Managed frags are owned by a zerocopy ubuf
and the skb does not hold a per-frag page reference; io_uring SEND_ZC
with a registered buffer attaches the bvec pages this way via
io_sg_from_iter(). The rest of the stack honours this invariant:
skb_release_data() skips the per-frag unref when SKBFL_MANAGED_FRAG_REFS
is set, and skb_zcopy_managed() is the guard used at the other unref
sites.
esp_ssg_unref() is missing that guard, so for a managed-frag skb it
drops a page reference the skb never acquired. This can underflow the
page reference count and free a page that is still in use.
Guard the function with skb_zcopy_managed() so both unref paths are
skipped for managed-frag skbs, matching skb_release_data().
Fixes: cac2661c53f3 ("esp4: Avoid skb_cow_data whenever possible")
Fixes: 03e2a30f6a27 ("esp6: Avoid skb_cow_data whenever possible")
Signed-off-by: Maher Azzouzi <maherazz04@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/ipv4/esp4.c | 7 +++++++
net/ipv6/esp6.c | 7 +++++++
2 files changed, 14 insertions(+)
diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
index dfc81ee969ae..fa1710e27e50 100644
--- a/net/ipv4/esp4.c
+++ b/net/ipv4/esp4.c
@@ -104,6 +104,13 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct aead_request *req;
struct scatterlist *sg;
+ /* Managed frags are owned by the zerocopy ubuf; the skb holds no
+ * per-frag page reference, so we must not drop one here. Mirrors
+ * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data().
+ */
+ if (skb_zcopy_managed(skb))
+ return;
+
if (x->props.flags & XFRM_STATE_ESN)
extralen += sizeof(struct esp_output_extra);
diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
index 296b57926abb..7d216b9c59f0 100644
--- a/net/ipv6/esp6.c
+++ b/net/ipv6/esp6.c
@@ -121,6 +121,13 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct aead_request *req;
struct scatterlist *sg;
+ /* Managed frags are owned by the zerocopy ubuf; the skb holds no
+ * per-frag page reference, so we must not drop one here. Mirrors
+ * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data().
+ */
+ if (skb_zcopy_managed(skb))
+ return;
+
if (x->props.flags & XFRM_STATE_ESN)
extralen += sizeof(struct esp_output_extra);
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 03/10] xfrm: espintcp: fix UAF during close
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
2026-08-18 9:28 ` [PATCH 01/10] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full Steffen Klassert
2026-08-18 9:28 ` [PATCH 02/10] esp: do not unref managed frag pages in esp_ssg_unref() Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 04/10] xfrm: drop ESP-in-TCP packets with no ingress device Steffen Klassert
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Sabrina Dubroca <sd@queasysnail.net>
ZDI reported and analyzed a race condition during close for espintcp
sockets:
espintcp_close() frees emsg->skb via kfree_skb() without holding
any socket lock. Concurrently, the xfrm_trans_reinject work queue
invokes esp_output_tcp_finish() -> espintcp_push_skb() ->
espintcp_push_msgs() -> skb_send_sock_locked(), which reads the
same skb as a data source.
Fix this by adding a synchronize_rcu() call after resetting sk_prot,
since esp_output_tcp_finish() runs under RCU and won't use a socket
with sk_prot == &tcp_prot. Simply taking the socket lock in
espintcp_close() could lead to leaks, if esp_output_tcp_finish()
re-adds an skb in the slot we just freed. After this, the existing
barrier() is no longer needed.
Cc: stable@vger.kernel.org
Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)")
Reported-by: zdi-disclosures@trendmicro.com
Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
Reviewed-by: Breno Leitao <leitao@debian.org>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/espintcp.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c
index 374e1b964438..cd817b855ba1 100644
--- a/net/xfrm/espintcp.c
+++ b/net/xfrm/espintcp.c
@@ -515,7 +515,8 @@ static void espintcp_close(struct sock *sk, long timeout)
strp_stop(&ctx->strp);
sk->sk_prot = &tcp_prot;
- barrier();
+
+ synchronize_rcu();
disable_work_sync(&ctx->work);
strp_done(&ctx->strp);
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 04/10] xfrm: drop ESP-in-TCP packets with no ingress device
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
` (2 preceding siblings ...)
2026-08-18 9:28 ` [PATCH 03/10] xfrm: espintcp: fix UAF during close Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 05/10] xfrm: avoid lock inversion in nat keepalive work Steffen Klassert
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Zhiling Zou <roxy520tt@gmail.com>
ESP-in-TCP receives records through the TCP strparser. handle_esp()
restores skb->dev from the saved skb_iif before passing the packet into
the XFRM input path.
Queued TCP data can be processed after the original ingress device has
been removed, for example during veth or net namespace teardown. In that
case dev_get_by_index_rcu() returns NULL. The XFRM IPv4 and IPv6 input
paths both expect skb->dev to be valid while building the route lookup,
so queued ESP-in-TCP data can dereference a NULL device.
Drop the packet if the saved ingress device can no longer be resolved.
Such a packet can no longer be routed through the normal XFRM receive
path, and this preserves the existing behaviour for packets whose ingress
device still exists.
Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <roxy520tt@gmail.com>
Assisted-by: Codex:gpt-5.4
Reviewed-by: Ren Wei <enjou1224z@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/espintcp.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c
index cd817b855ba1..674aedc5af5a 100644
--- a/net/xfrm/espintcp.c
+++ b/net/xfrm/espintcp.c
@@ -37,6 +37,11 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk)
rcu_read_lock();
skb->dev = dev_get_by_index_rcu(sock_net(sk), skb->skb_iif);
+ if (!skb->dev) {
+ XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR);
+ kfree_skb(skb);
+ goto out;
+ }
local_bh_disable();
#if IS_ENABLED(CONFIG_IPV6)
if (sk->sk_family == AF_INET6)
@@ -45,6 +50,7 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk)
#endif
xfrm4_rcv_encap(skb, IPPROTO_ESP, 0, TCP_ENCAP_ESPINTCP);
local_bh_enable();
+out:
rcu_read_unlock();
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 05/10] xfrm: avoid lock inversion in nat keepalive work
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
` (3 preceding siblings ...)
2026-08-18 9:28 ` [PATCH 04/10] xfrm: drop ESP-in-TCP packets with no ingress device Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 06/10] xfrm: Fix skb double-free in xfrm_dev_direct_output() Steffen Klassert
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Zihan Xi <xizh2024@lzu.edu.cn>
nat_keepalive_work() walks the state table while xfrm_state_walk()
holds net->xfrm.xfrm_state_lock. Its callback then acquires x->lock,
which conflicts with the delete path taking the same locks in reverse
order via xfrm_state_delete() and __xfrm_state_delete(). This creates
an AB-BA deadlock that is reported by lockdep when a NAT keepalive
worker races with SA deletion.
Fix this by splitting the keepalive walk into two phases. First,
collect the candidate states while the walk holds xfrm_state_lock and
take a reference on each state. Then, after the walk completes, process
each collected state and acquire x->lock without nesting it under
xfrm_state_lock.
Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <xizh2024@lzu.edu.cn>
Signed-off-by: Ren Wei <enjou1224z@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_nat_keepalive.c | 57 +++++++++++++++++++++++++++++------
1 file changed, 48 insertions(+), 9 deletions(-)
diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c
index eb1b6f67739e..8679c68c10a1 100644
--- a/net/xfrm/xfrm_nat_keepalive.c
+++ b/net/xfrm/xfrm_nat_keepalive.c
@@ -156,24 +156,51 @@ static void nat_keepalive_send(struct nat_keepalive *ka)
}
struct nat_keepalive_work_ctx {
+ struct list_head states;
time64_t next_run;
time64_t now;
};
-static int nat_keepalive_work_single(struct xfrm_state *x, int count, void *ptr)
+struct nat_keepalive_state {
+ struct list_head list;
+ struct xfrm_state *x;
+};
+
+static int nat_keepalive_work_collect(struct xfrm_state *x, int count, void *ptr)
{
struct nat_keepalive_work_ctx *ctx = ptr;
+ struct nat_keepalive_state *state;
+
+ if (!READ_ONCE(x->nat_keepalive_interval))
+ return 0;
+
+ state = kmalloc_obj(*state, GFP_ATOMIC);
+ if (!state)
+ return -ENOMEM;
+
+ xfrm_state_hold(x);
+ state->x = x;
+ list_add_tail(&state->list, &ctx->states);
+ return 0;
+}
+
+static void nat_keepalive_work_single(struct xfrm_state *x,
+ struct nat_keepalive_work_ctx *ctx)
+{
bool send_keepalive = false;
struct nat_keepalive ka;
- time64_t next_run;
+ time64_t next_run = 0;
u32 interval;
int delta;
+ spin_lock_bh(&x->lock);
+
+ if (x->km.state == XFRM_STATE_DEAD)
+ goto out;
+
interval = x->nat_keepalive_interval;
if (!interval)
- return 0;
-
- spin_lock(&x->lock);
+ goto out;
delta = (int)(ctx->now - x->lastused);
if (delta < interval) {
@@ -187,29 +214,41 @@ static int nat_keepalive_work_single(struct xfrm_state *x, int count, void *ptr)
send_keepalive = true;
}
- spin_unlock(&x->lock);
+out:
+ spin_unlock_bh(&x->lock);
if (send_keepalive)
nat_keepalive_send(&ka);
- if (!ctx->next_run || next_run < ctx->next_run)
+ if (next_run && (!ctx->next_run || next_run < ctx->next_run))
ctx->next_run = next_run;
- return 0;
}
static void nat_keepalive_work(struct work_struct *work)
{
+ struct nat_keepalive_state *state, *tmp;
struct nat_keepalive_work_ctx ctx;
struct xfrm_state_walk walk;
struct net *net;
+ int err;
+ INIT_LIST_HEAD(&ctx.states);
ctx.next_run = 0;
ctx.now = ktime_get_real_seconds();
net = container_of(work, struct net, xfrm.nat_keepalive_work.work);
xfrm_state_walk_init(&walk, IPPROTO_ESP, NULL);
- xfrm_state_walk(net, &walk, nat_keepalive_work_single, &ctx);
+ err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx);
xfrm_state_walk_done(&walk, net);
+ list_for_each_entry_safe(state, tmp, &ctx.states, list) {
+ nat_keepalive_work_single(state->x, &ctx);
+ xfrm_state_put(state->x);
+ kfree(state);
+ }
+ if (err == -ENOMEM) {
+ schedule_delayed_work(&net->xfrm.nat_keepalive_work, 0);
+ return;
+ }
if (ctx.next_run)
schedule_delayed_work(&net->xfrm.nat_keepalive_work,
(ctx.next_run - ctx.now) * HZ);
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 06/10] xfrm: Fix skb double-free in xfrm_dev_direct_output()
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
` (4 preceding siblings ...)
2026-08-18 9:28 ` [PATCH 05/10] xfrm: avoid lock inversion in nat keepalive work Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 07/10] xfrm: ah6: validate routing header segments_left Steffen Klassert
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Sanghyun Park <sanghyun.park.cnu@gmail.com>
A return value other than 1 from local_out() means that the skb has been
consumed or its ownership was transferred. xfrm_dev_direct_output()
nevertheless frees the skb on this path, causing a double-free when
netfilter drops the packet and invalidating any other owner.
Return the local_out() result directly, matching the ownership handling
in xfrm_output_resume().
Fixes: 5eddd76ec2fd ("xfrm: fix tunnel mode TX datapath in packet offload mode")
Signed-off-by: Sanghyun Park <sanghyun.park.cnu@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_output.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c
index cc35c2fcbbe0..e305ba32e356 100644
--- a/net/xfrm/xfrm_output.c
+++ b/net/xfrm/xfrm_output.c
@@ -636,10 +636,8 @@ static int xfrm_dev_direct_output(struct sock *sk, struct xfrm_state *x,
nf_reset_ct(skb);
err = skb_dst(skb)->ops->local_out(net, sk, skb);
- if (unlikely(err != 1)) {
- kfree_skb(skb);
+ if (unlikely(err != 1))
return err;
- }
/* In transport mode, network destination is
* directly reachable, while in tunnel mode,
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 07/10] xfrm: ah6: validate routing header segments_left
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
` (5 preceding siblings ...)
2026-08-18 9:28 ` [PATCH 06/10] xfrm: Fix skb double-free in xfrm_dev_direct_output() Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 08/10] xfrm: fix xfrm_state_construct() auth-trunc leak Steffen Klassert
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Asim Viladi Oglu Manizada <manizada@pm.me>
AH6 rearranges routing-header addresses before computing or verifying the
ICV. ipv6_rearrange_rthdr() assumes that segments_left is not larger than
the number of addresses described by the routing header's hdrlen field.
That assumption does not hold for raw IPv6 HDRINCL packets. A packet with
hdrlen equal to 2 describes one address, but can carry an arbitrary
segments_left value. With segments_left equal to 255, the function moves
its address pointer 4,064 bytes backwards and passes a 4,064-byte length to
memmove(), resulting in an out-of-bounds access.
Validate the invariant locally before modifying the routing header or
performing any address-pointer arithmetic, and propagate malformed-header
errors to the existing AH6 input and output error paths.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Assisted-by: avom-custom-harness:gpt-5.5-qwen3.6-mod-mix
Signed-off-by: Asim Viladi Oglu Manizada <manizada@pm.me>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/ipv6/ah6.c | 29 ++++++++++++++++++-----------
1 file changed, 18 insertions(+), 11 deletions(-)
diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c
index 76f7a2de9108..c96f7e0d0a48 100644
--- a/net/ipv6/ah6.c
+++ b/net/ipv6/ah6.c
@@ -232,26 +232,28 @@ static void ipv6_rearrange_destopt(struct ipv6hdr *iph, struct ipv6_opt_hdr *des
* Rearrange the destination address in @iph and the addresses in @rthdr
* so that they appear in the order they will at the final destination.
* See Appendix A2 of RFC 2402 for details.
+ *
+ * Return: 0 on success, -EINVAL if segments_left exceeds the number of
+ * addresses described by hdrlen.
*/
-static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr)
+static int ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr)
{
- int segments, segments_left;
+ unsigned int segments, segments_left;
struct in6_addr *addrs;
struct in6_addr final_addr;
segments_left = rthdr->segments_left;
if (segments_left == 0)
- return;
- rthdr->segments_left = 0;
+ return 0;
- /* The value of rthdr->hdrlen has been verified either by the system
- * call if it is locally generated, or by ipv6_rthdr_rcv() for incoming
- * packets. So we can assume that it is even and that segments is
- * greater than or equal to segments_left.
- *
- * For the same reason we can assume that this option is of type 0.
+ /* Raw locally generated packets can reach AH6 without the invariant
+ * required by the rt0-style address rearrangement below.
*/
segments = rthdr->hdrlen >> 1;
+ if (segments_left > segments)
+ return -EINVAL;
+
+ rthdr->segments_left = 0;
addrs = ((struct rt0_hdr *)rthdr)->addr;
final_addr = addrs[segments - 1];
@@ -261,6 +263,8 @@ static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr)
addrs[0] = iph->daddr;
iph->daddr = final_addr;
+
+ return 0;
}
static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir)
@@ -273,6 +277,7 @@ static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir)
} exthdr = { .iph = iph };
char *end = exthdr.raw + len;
int nexthdr = iph->nexthdr;
+ int err;
exthdr.iph++;
@@ -292,7 +297,9 @@ static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir)
break;
case NEXTHDR_ROUTING:
- ipv6_rearrange_rthdr(iph, exthdr.rth);
+ err = ipv6_rearrange_rthdr(iph, exthdr.rth);
+ if (err)
+ return err;
break;
default:
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 08/10] xfrm: fix xfrm_state_construct() auth-trunc leak
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
` (6 preceding siblings ...)
2026-08-18 9:28 ` [PATCH 07/10] xfrm: ah6: validate routing header segments_left Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 09/10] Revert "esp: do not unref managed frag pages in esp_ssg_unref()" Steffen Klassert
2026-08-18 9:28 ` [PATCH 10/10] xfrm: bound nat keepalive state collection Steffen Klassert
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Zihan Xi <zihanx@nebusec.ai>
attach_auth_trunc() can allocate x->aalg while leaving
x->props.aalgo at zero when the selected auth algorithm has no
sadb_alg_id. One real case is cmac(aes).
xfrm_state_construct() then treats !x->props.aalgo as "no auth
algorithm attached yet" and calls attach_auth(). That overwrites
x->aalg and loses the first allocation. Any later failure or teardown
only frees the replacement pointer.
Check whether x->aalg is already attached instead of inferring that
state from x->props.aalgo.
Fixes: 4447bb33f094 ("xfrm: Store aalg in xfrm_state with a user specified truncation length")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Signed-off-by: Ren Wei <enjou1224z@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_user.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index d6db63304ba6..6266a92cf302 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -940,7 +940,7 @@ static struct xfrm_state *xfrm_state_construct(struct net *net,
if ((err = attach_auth_trunc(&x->aalg, &x->props.aalgo,
attrs[XFRMA_ALG_AUTH_TRUNC], extack)))
goto error;
- if (!x->props.aalgo) {
+ if (!x->aalg) {
if ((err = attach_auth(&x->aalg, &x->props.aalgo,
attrs[XFRMA_ALG_AUTH], extack)))
goto error;
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 09/10] Revert "esp: do not unref managed frag pages in esp_ssg_unref()"
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
` (7 preceding siblings ...)
2026-08-18 9:28 ` [PATCH 08/10] xfrm: fix xfrm_state_construct() auth-trunc leak Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
2026-08-18 9:28 ` [PATCH 10/10] xfrm: bound nat keepalive state collection Steffen Klassert
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
This reverts commit 21697720ff43b8dfa25b8e8d9ca7f56f4597fc80.
The patch does not fix the issue completely, so revert for
now and wait for an updated version.
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/ipv4/esp4.c | 7 -------
net/ipv6/esp6.c | 7 -------
2 files changed, 14 deletions(-)
diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
index fa1710e27e50..dfc81ee969ae 100644
--- a/net/ipv4/esp4.c
+++ b/net/ipv4/esp4.c
@@ -104,13 +104,6 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct aead_request *req;
struct scatterlist *sg;
- /* Managed frags are owned by the zerocopy ubuf; the skb holds no
- * per-frag page reference, so we must not drop one here. Mirrors
- * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data().
- */
- if (skb_zcopy_managed(skb))
- return;
-
if (x->props.flags & XFRM_STATE_ESN)
extralen += sizeof(struct esp_output_extra);
diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
index 7d216b9c59f0..296b57926abb 100644
--- a/net/ipv6/esp6.c
+++ b/net/ipv6/esp6.c
@@ -121,13 +121,6 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct aead_request *req;
struct scatterlist *sg;
- /* Managed frags are owned by the zerocopy ubuf; the skb holds no
- * per-frag page reference, so we must not drop one here. Mirrors
- * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data().
- */
- if (skb_zcopy_managed(skb))
- return;
-
if (x->props.flags & XFRM_STATE_ESN)
extralen += sizeof(struct esp_output_extra);
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 10/10] xfrm: bound nat keepalive state collection
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
` (8 preceding siblings ...)
2026-08-18 9:28 ` [PATCH 09/10] Revert "esp: do not unref managed frag pages in esp_ssg_unref()" Steffen Klassert
@ 2026-08-18 9:28 ` Steffen Klassert
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Klassert @ 2026-08-18 9:28 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
From: Zihan Xi <zihanx@nebusec.ai>
The v1 nat keepalive fix allocates a GFP_ATOMIC object for every state
while collecting references for phase two. This makes the worker's
temporary memory use depend on the number of states and lets -ENOMEM abort
the scan.
Replace the allocated list with a fixed-size batch. When the batch is full,
return a private walk status so xfrm_state_walk() leaves a cursor; drain
the references after the walk releases xfrm_state_lock and resume from
the cursor. This bounds temporary memory use and avoids the allocation
failure path.
The v1 fix also moved nat_keepalive_send() out of the walk callback. Keep
the phase-two drain BH-disabled, as required by local_lock_nested_bh()
used by the keepalive sockets.
Fixes: 763fe700b7c5 ("xfrm: avoid lock inversion in nat keepalive work")
Cc: stable@vger.kernel.org
Cc: Eyal Birger <eyal.birger@gmail.com>
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_nat_keepalive.c | 46 ++++++++++++++++-------------------
1 file changed, 21 insertions(+), 25 deletions(-)
diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c
index 8679c68c10a1..5cd6d43164db 100644
--- a/net/xfrm/xfrm_nat_keepalive.c
+++ b/net/xfrm/xfrm_nat_keepalive.c
@@ -155,32 +155,30 @@ static void nat_keepalive_send(struct nat_keepalive *ka)
}
}
+enum {
+ NAT_KEEPALIVE_BATCH_SIZE = 16,
+ NAT_KEEPALIVE_BATCH_FULL = 1,
+};
+
struct nat_keepalive_work_ctx {
- struct list_head states;
+ struct xfrm_state *batch[NAT_KEEPALIVE_BATCH_SIZE];
+ unsigned int nr;
time64_t next_run;
time64_t now;
};
-struct nat_keepalive_state {
- struct list_head list;
- struct xfrm_state *x;
-};
-
static int nat_keepalive_work_collect(struct xfrm_state *x, int count, void *ptr)
{
struct nat_keepalive_work_ctx *ctx = ptr;
- struct nat_keepalive_state *state;
if (!READ_ONCE(x->nat_keepalive_interval))
return 0;
- state = kmalloc_obj(*state, GFP_ATOMIC);
- if (!state)
- return -ENOMEM;
+ if (ctx->nr == ARRAY_SIZE(ctx->batch))
+ return NAT_KEEPALIVE_BATCH_FULL;
xfrm_state_hold(x);
- state->x = x;
- list_add_tail(&state->list, &ctx->states);
+ ctx->batch[ctx->nr++] = x;
return 0;
}
@@ -226,29 +224,27 @@ static void nat_keepalive_work_single(struct xfrm_state *x,
static void nat_keepalive_work(struct work_struct *work)
{
- struct nat_keepalive_state *state, *tmp;
struct nat_keepalive_work_ctx ctx;
struct xfrm_state_walk walk;
struct net *net;
- int err;
+ int err, i;
- INIT_LIST_HEAD(&ctx.states);
ctx.next_run = 0;
ctx.now = ktime_get_real_seconds();
net = container_of(work, struct net, xfrm.nat_keepalive_work.work);
xfrm_state_walk_init(&walk, IPPROTO_ESP, NULL);
- err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx);
+ do {
+ ctx.nr = 0;
+ err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx);
+ local_bh_disable();
+ for (i = 0; i < ctx.nr; i++) {
+ nat_keepalive_work_single(ctx.batch[i], &ctx);
+ xfrm_state_put(ctx.batch[i]);
+ }
+ local_bh_enable();
+ } while (err == NAT_KEEPALIVE_BATCH_FULL);
xfrm_state_walk_done(&walk, net);
- list_for_each_entry_safe(state, tmp, &ctx.states, list) {
- nat_keepalive_work_single(state->x, &ctx);
- xfrm_state_put(state->x);
- kfree(state);
- }
- if (err == -ENOMEM) {
- schedule_delayed_work(&net->xfrm.nat_keepalive_work, 0);
- return;
- }
if (ctx.next_run)
schedule_delayed_work(&net->xfrm.nat_keepalive_work,
(ctx.next_run - ctx.now) * HZ);
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-08-18 9:29 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
2026-08-18 9:28 ` [PATCH 01/10] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full Steffen Klassert
2026-08-18 9:28 ` [PATCH 02/10] esp: do not unref managed frag pages in esp_ssg_unref() Steffen Klassert
2026-08-18 9:28 ` [PATCH 03/10] xfrm: espintcp: fix UAF during close Steffen Klassert
2026-08-18 9:28 ` [PATCH 04/10] xfrm: drop ESP-in-TCP packets with no ingress device Steffen Klassert
2026-08-18 9:28 ` [PATCH 05/10] xfrm: avoid lock inversion in nat keepalive work Steffen Klassert
2026-08-18 9:28 ` [PATCH 06/10] xfrm: Fix skb double-free in xfrm_dev_direct_output() Steffen Klassert
2026-08-18 9:28 ` [PATCH 07/10] xfrm: ah6: validate routing header segments_left Steffen Klassert
2026-08-18 9:28 ` [PATCH 08/10] xfrm: fix xfrm_state_construct() auth-trunc leak Steffen Klassert
2026-08-18 9:28 ` [PATCH 09/10] Revert "esp: do not unref managed frag pages in esp_ssg_unref()" Steffen Klassert
2026-08-18 9:28 ` [PATCH 10/10] xfrm: bound nat keepalive state collection Steffen Klassert
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox