mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 08:15:07 -04:00
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:
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user