Merge tag 'ipsec-2026-08-18' of git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec

Steffen Klassert says:

====================
pull request (net): ipsec 2026-08-18

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.

* tag 'ipsec-2026-08-18' of git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec:
  xfrm: bound nat keepalive state collection
  Revert "esp: do not unref managed frag pages in esp_ssg_unref()"
  xfrm: fix xfrm_state_construct() auth-trunc leak
  xfrm: ah6: validate routing header segments_left
  xfrm: Fix skb double-free in xfrm_dev_direct_output()
  xfrm: avoid lock inversion in nat keepalive work
  xfrm: drop ESP-in-TCP packets with no ingress device
  xfrm: espintcp: fix UAF during close
  esp: do not unref managed frag pages in esp_ssg_unref()
  xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full
====================

Link: https://patch.msgid.link/20260818092920.653034-1-steffen.klassert@secunet.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-08-20 11:38:13 -07:00
6 changed files with 73 additions and 26 deletions

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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();
}
@@ -515,7 +521,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);

View File

@@ -155,25 +155,50 @@ 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 xfrm_state *batch[NAT_KEEPALIVE_BATCH_SIZE];
unsigned int nr;
time64_t next_run;
time64_t now;
};
static int nat_keepalive_work_single(struct xfrm_state *x, int count, void *ptr)
static int nat_keepalive_work_collect(struct xfrm_state *x, int count, void *ptr)
{
struct nat_keepalive_work_ctx *ctx = ptr;
if (!READ_ONCE(x->nat_keepalive_interval))
return 0;
if (ctx->nr == ARRAY_SIZE(ctx->batch))
return NAT_KEEPALIVE_BATCH_FULL;
xfrm_state_hold(x);
ctx->batch[ctx->nr++] = x;
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,14 +212,14 @@ 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)
@@ -202,13 +227,23 @@ static void nat_keepalive_work(struct work_struct *work)
struct nat_keepalive_work_ctx ctx;
struct xfrm_state_walk walk;
struct net *net;
int err, i;
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);
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);
if (ctx.next_run)
schedule_delayed_work(&net->xfrm.nat_keepalive_work,

View File

@@ -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,

View File

@@ -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;