mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:19:34 +02:00
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>
341 lines
7.6 KiB
C
341 lines
7.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* xfrm_nat_keepalive.c
|
|
*
|
|
* (c) 2024 Eyal Birger <eyal.birger@gmail.com>
|
|
*/
|
|
|
|
#include <net/inet_common.h>
|
|
#include <net/ip6_checksum.h>
|
|
#include <net/xfrm.h>
|
|
|
|
static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv4) = {
|
|
.bh_lock = INIT_LOCAL_LOCK(bh_lock),
|
|
};
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv6) = {
|
|
.bh_lock = INIT_LOCAL_LOCK(bh_lock),
|
|
};
|
|
#endif
|
|
|
|
struct nat_keepalive {
|
|
struct net *net;
|
|
u16 family;
|
|
xfrm_address_t saddr;
|
|
xfrm_address_t daddr;
|
|
__be16 encap_sport;
|
|
__be16 encap_dport;
|
|
__u32 smark;
|
|
};
|
|
|
|
static void nat_keepalive_init(struct nat_keepalive *ka, struct xfrm_state *x)
|
|
{
|
|
ka->net = xs_net(x);
|
|
ka->family = x->props.family;
|
|
ka->saddr = x->props.saddr;
|
|
ka->daddr = x->id.daddr;
|
|
ka->encap_sport = x->encap->encap_sport;
|
|
ka->encap_dport = x->encap->encap_dport;
|
|
ka->smark = xfrm_smark_get(0, x);
|
|
}
|
|
|
|
static int nat_keepalive_send_ipv4(struct sk_buff *skb,
|
|
struct nat_keepalive *ka)
|
|
{
|
|
struct net *net = ka->net;
|
|
struct flowi4 fl4;
|
|
struct rtable *rt;
|
|
struct sock *sk;
|
|
__u8 tos = 0;
|
|
int err;
|
|
|
|
flowi4_init_output(&fl4, 0 /* oif */, skb->mark, tos,
|
|
RT_SCOPE_UNIVERSE, IPPROTO_UDP, 0,
|
|
ka->daddr.a4, ka->saddr.a4, ka->encap_dport,
|
|
ka->encap_sport, sock_net_uid(net, NULL));
|
|
|
|
rt = ip_route_output_key(net, &fl4);
|
|
if (IS_ERR(rt)) {
|
|
kfree_skb(skb);
|
|
return PTR_ERR(rt);
|
|
}
|
|
|
|
skb_dst_set(skb, &rt->dst);
|
|
|
|
local_lock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
|
|
sk = this_cpu_read(nat_keepalive_sk_ipv4.sock);
|
|
sock_net_set(sk, net);
|
|
err = ip_build_and_send_pkt(skb, sk, fl4.saddr, fl4.daddr, NULL, tos);
|
|
sock_net_set(sk, &init_net);
|
|
local_unlock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
|
|
return err;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
static int nat_keepalive_send_ipv6(struct sk_buff *skb,
|
|
struct nat_keepalive *ka,
|
|
struct udphdr *uh)
|
|
{
|
|
struct net *net = ka->net;
|
|
struct dst_entry *dst;
|
|
struct flowi6 fl6;
|
|
struct sock *sk;
|
|
__wsum csum;
|
|
int err;
|
|
|
|
csum = skb_checksum(skb, 0, skb->len, 0);
|
|
uh->check = csum_ipv6_magic(&ka->saddr.in6, &ka->daddr.in6,
|
|
skb->len, IPPROTO_UDP, csum);
|
|
if (uh->check == 0)
|
|
uh->check = CSUM_MANGLED_0;
|
|
|
|
memset(&fl6, 0, sizeof(fl6));
|
|
fl6.flowi6_mark = skb->mark;
|
|
fl6.saddr = ka->saddr.in6;
|
|
fl6.daddr = ka->daddr.in6;
|
|
fl6.flowi6_proto = IPPROTO_UDP;
|
|
fl6.fl6_sport = ka->encap_sport;
|
|
fl6.fl6_dport = ka->encap_dport;
|
|
|
|
local_lock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
|
|
sk = this_cpu_read(nat_keepalive_sk_ipv6.sock);
|
|
sock_net_set(sk, net);
|
|
dst = ip6_dst_lookup_flow(net, sk, &fl6, NULL);
|
|
if (IS_ERR(dst)) {
|
|
local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
|
|
kfree_skb(skb);
|
|
return PTR_ERR(dst);
|
|
}
|
|
|
|
skb_dst_set(skb, dst);
|
|
err = ip6_xmit(sk, skb, &fl6, skb->mark, NULL, 0, 0);
|
|
sock_net_set(sk, &init_net);
|
|
local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
static void nat_keepalive_send(struct nat_keepalive *ka)
|
|
{
|
|
const int nat_ka_hdrs_len = max(sizeof(struct iphdr),
|
|
sizeof(struct ipv6hdr)) +
|
|
sizeof(struct udphdr);
|
|
const u8 nat_ka_payload = 0xFF;
|
|
struct sk_buff *skb;
|
|
struct udphdr *uh;
|
|
|
|
skb = alloc_skb(nat_ka_hdrs_len + sizeof(nat_ka_payload), GFP_ATOMIC);
|
|
if (unlikely(!skb))
|
|
return;
|
|
|
|
skb_reserve(skb, nat_ka_hdrs_len);
|
|
|
|
skb_put_u8(skb, nat_ka_payload);
|
|
|
|
uh = skb_push(skb, sizeof(*uh));
|
|
uh->source = ka->encap_sport;
|
|
uh->dest = ka->encap_dport;
|
|
udp_set_len_short(uh, skb->len);
|
|
uh->check = 0;
|
|
|
|
skb->mark = ka->smark;
|
|
|
|
switch (ka->family) {
|
|
case AF_INET:
|
|
nat_keepalive_send_ipv4(skb, ka);
|
|
break;
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
case AF_INET6:
|
|
nat_keepalive_send_ipv6(skb, ka, uh);
|
|
break;
|
|
#endif
|
|
default:
|
|
kfree_skb(skb);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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_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 = 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)
|
|
goto out;
|
|
|
|
delta = (int)(ctx->now - x->lastused);
|
|
if (delta < interval) {
|
|
x->nat_keepalive_expiration = ctx->now + interval - delta;
|
|
next_run = x->nat_keepalive_expiration;
|
|
} else if (x->nat_keepalive_expiration > ctx->now) {
|
|
next_run = x->nat_keepalive_expiration;
|
|
} else {
|
|
next_run = ctx->now + interval;
|
|
nat_keepalive_init(&ka, x);
|
|
send_keepalive = true;
|
|
}
|
|
|
|
out:
|
|
spin_unlock_bh(&x->lock);
|
|
|
|
if (send_keepalive)
|
|
nat_keepalive_send(&ka);
|
|
|
|
if (next_run && (!ctx->next_run || next_run < ctx->next_run))
|
|
ctx->next_run = next_run;
|
|
}
|
|
|
|
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);
|
|
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,
|
|
(ctx.next_run - ctx.now) * HZ);
|
|
}
|
|
|
|
static int nat_keepalive_sk_init(struct sock_bh_locked __percpu *socks,
|
|
unsigned short family)
|
|
{
|
|
struct sock *sk;
|
|
int err, i;
|
|
|
|
for_each_possible_cpu(i) {
|
|
err = inet_ctl_sock_create(&sk, family, SOCK_RAW, IPPROTO_UDP,
|
|
&init_net);
|
|
if (err < 0)
|
|
goto err;
|
|
|
|
per_cpu_ptr(socks, i)->sock = sk;
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
for_each_possible_cpu(i)
|
|
inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
|
|
return err;
|
|
}
|
|
|
|
static void nat_keepalive_sk_fini(struct sock_bh_locked __percpu *socks)
|
|
{
|
|
int i;
|
|
|
|
for_each_possible_cpu(i)
|
|
inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
|
|
}
|
|
|
|
void xfrm_nat_keepalive_state_updated(struct xfrm_state *x)
|
|
{
|
|
struct net *net;
|
|
|
|
if (!x->nat_keepalive_interval)
|
|
return;
|
|
|
|
net = xs_net(x);
|
|
schedule_delayed_work(&net->xfrm.nat_keepalive_work, 0);
|
|
}
|
|
|
|
int __net_init xfrm_nat_keepalive_net_init(struct net *net)
|
|
{
|
|
INIT_DELAYED_WORK(&net->xfrm.nat_keepalive_work, nat_keepalive_work);
|
|
return 0;
|
|
}
|
|
|
|
int xfrm_nat_keepalive_net_fini(struct net *net)
|
|
{
|
|
disable_delayed_work_sync(&net->xfrm.nat_keepalive_work);
|
|
return 0;
|
|
}
|
|
|
|
int xfrm_nat_keepalive_init(unsigned short family)
|
|
{
|
|
int err = -EAFNOSUPPORT;
|
|
|
|
switch (family) {
|
|
case AF_INET:
|
|
err = nat_keepalive_sk_init(&nat_keepalive_sk_ipv4, PF_INET);
|
|
break;
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
case AF_INET6:
|
|
err = nat_keepalive_sk_init(&nat_keepalive_sk_ipv6, PF_INET6);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (err)
|
|
pr_err("xfrm nat keepalive init: failed to init err:%d\n", err);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL_GPL(xfrm_nat_keepalive_init);
|
|
|
|
void xfrm_nat_keepalive_fini(unsigned short family)
|
|
{
|
|
switch (family) {
|
|
case AF_INET:
|
|
nat_keepalive_sk_fini(&nat_keepalive_sk_ipv4);
|
|
break;
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
case AF_INET6:
|
|
nat_keepalive_sk_fini(&nat_keepalive_sk_ipv6);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(xfrm_nat_keepalive_fini);
|