ipsec-2026-09-16

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEH7ZpcWbFyOOp6OJbrB3Eaf9PW7cFAmqqagcACgkQrB3Eaf9P
 W7ervw//ValStkQ37oXUHXQkevKs+rAFhleUHogbxG+C/45nx7ToVgtL0oRFs/ss
 WmxjClmthosPdcgz9uLyIi/xiIWmwa9iYsmq/A7FF5bewz8ha0/jzkxi6EZvGFxK
 oHJWIe11pcDV3NoEjA33Z6k63vZcVhD3QVlIk4mg4b4leEDgTw6y8z/k5sv6/QTc
 xP7lykI6VeKPyBydSAdwHonJZ3BWLE0/Y2uiQtVVZAFT9Ln2cpqAPpwYGDFKpgM/
 H8NYqB4X3fhdxc/rQhqdgS0mdhEwukulyYg/znwUI/DYb5Dt4Eh51y0fpIcjFX2y
 Gp31bMXih7j66SuOwEXx48dSNgxHEXwbUdUoeQReVmFam0epbOyPoBgT5ZLskWfo
 JbdeZR7kAugCnX/XpTmI8pk3G52i6LvCY1GDPyIYTFGcVHg9Y9plw1WEs88pqGNC
 9+pkRapF04gdaxjl4FioKdtoKXTyYiVxEB/nWkBfUE9Q5iS16/G9Lrcd4Dw3aN8f
 imsTf1yW6kJ6RbF9QKS4kuzVJnPFwxg/EmmHFQtpZOkH23BwZEU7obqIwpNYgnoG
 /A9vnwQdAxGU4ar6n2wSPXABzFYlsJAwyMlOGrOkzWC9t/TAA5NHjkyskO5pY8Mt
 0bhWCiNfRYzbSF94UEltYsKZKrx9kV6c2lCltKCfR89KhY5Dorw=
 =skHa
 -----END PGP SIGNATURE-----

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

Steffen Klassert says:

====================
pull request (net): ipsec 2026-09-16

1) xfrm: iptfs: fix stack OOB read in iptfs_skb_reset_frag_walk()
   Add the up-front nr_frags guard iptfs_skb_add_frags() already has,
   so an out-of-range offset can't walk past the on-stack frags[] array.

2) xfrm: serialize state GC with device state flush
   Serialize xfrm_state destruction against the deferred-device pass
   with a dedicated mutex, since the device GC list doesn't hold a state
   reference and the two paths could free the same state.

3) xfrm: add missing RCU read lock in xfrm_send_migrate_state()
   Hold the RCU read lock around xfrm_nlmsg_multicast() so the
   rcu_dereference() of net->xfrm.nlsk doesn't warn.

4) xfrm: iptfs: fix runt reassembly panic from short inner tot_len
   Require the runt length to cover at least the minimum IP header,
   so a tot_len in [6, 19] (IPv4) can't write past the declared length
   and trip skb_over_panic().

5) ipv6: xfrm: use full sockets in local error paths
   Use skb_to_full_sk() in xfrm6_local_rxpmtu() and xfrm6_local_error()
   and bail out without a full socket, so a TCP_NEW_SYN_RECV request_sock
   isn't miscast as a full inet/IPv6 socket.

6) xfrm: fix compat ALLOCSPI request use-after-free
   Drop the redundant alloc_compat() in xfrm_alloc_userspi() so the
   compat translator no longer reads past the payload and publishes a
   child a multicast clone can still see after xfrm_user_rcv_msg() frees.

7) xfrm: add missing rcu_read_lock(), skb_dst_force() and dev_hold() for xfrm_trans_reinject()
   Force the dst before queuing, hold dev across the workqueue deferral,
   and take rcu_read_lock() around the finish() loop, so transport-mode
   reinjection doesn't deref non-refcounted dst/dev under workqueue.

8) xfrm: use hlist_del_init_rcu for state_cache and state_cache_input
   Switch to hlist_del_init_rcu() so a second __xfrm_state_delete() is
   a no-op instead of writing through LIST_POISON2, closing the UAFs.

9) esp: downgrade zerocopy managed frags before mutating skb frags
   Call skb_zcopy_downgrade_managed() before ESP rewrites the skb frag
   array, so per-frag unrefs in esp_ssg_unref() and skb_release_data()
   stay balanced for ubuf-owned managed frags.

10) xfrm: hold net_device reference under RCU in bundle creation
    Read dst->dev via dst_dev_rcu() and keep RCU active through
    xfrm_fill_dst(), so a concurrent RTM_DELLINK can't free dev
    under bundle creation.

11) xfrm: save input state data before secpath resets
    Save the state protocol on the stack while it's still valid and
    use the saved address family for transport_finish(), so post-reset
    dereferences (VTI, XFRM if, MAX_DEPTH error) can't UAF the state.

12) net: xfrm: reject unrepresentable espintcp transport headers
    Use the careful transport-header helper and drop the skb through
    the XFRM error path when the offset can't be represented, instead
    of silently truncating it.

* tag 'ipsec-2026-09-16' of git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec:
  net: xfrm: reject unrepresentable espintcp transport headers
  xfrm: save input state data before secpath resets
  xfrm: hold net_device reference under RCU in bundle creation
  esp: downgrade zerocopy managed frags before mutating skb frags
  xfrm: use hlist_del_init_rcu for state_cache and state_cache_input
  xfrm: add missing rcu_read_lock(), skb_dst_force() and dev_hold() for xfrm_trans_reinject()
  xfrm: fix compat ALLOCSPI request use-after-free
  ipv6: xfrm: use full sockets in local error paths
  xfrm: iptfs: fix runt reassembly panic from short inner tot_len
  xfrm: add missing RCU read lock in xfrm_send_migrate_state()
  xfrm: serialize state GC with device state flush
  xfrm: iptfs: fix stack OOB read in iptfs_skb_reset_frag_walk()
====================

Link: https://patch.msgid.link/20260916101938.118628-1-steffen.klassert@secunet.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-09-16 15:54:18 -07:00
commit 7c7d5e9d7e
9 changed files with 81 additions and 28 deletions

View File

@ -441,6 +441,12 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *
esp->inplace = false;
/* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before
* we mutate the frag array, so the per-frag unref stays balanced
* for zerocopy managed frags (see __ip_append_data()).
*/
skb_zcopy_downgrade_managed(skb);
allocsize = ALIGN(tailen, L1_CACHE_BYTES);
spin_lock_bh(&x->lock);

View File

@ -471,6 +471,12 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info
esp->inplace = false;
/* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before
* we mutate the frag array, so the per-frag unref stays balanced
* for zerocopy managed frags (see __ip_append_data()).
*/
skb_zcopy_downgrade_managed(skb);
allocsize = ALIGN(tailen, L1_CACHE_BYTES);
spin_lock_bh(&x->lock);

View File

@ -19,7 +19,10 @@
void xfrm6_local_rxpmtu(struct sk_buff *skb, u32 mtu)
{
struct flowi6 fl6;
struct sock *sk = skb->sk;
struct sock *sk = skb_to_full_sk(skb);
if (!sk)
return;
fl6.flowi6_oif = sk->sk_bound_dev_if;
fl6.daddr = ipv6_hdr(skb)->daddr;
@ -31,7 +34,10 @@ void xfrm6_local_error(struct sk_buff *skb, u32 mtu)
{
struct flowi6 fl6;
const struct ipv6hdr *hdr;
struct sock *sk = skb->sk;
struct sock *sk = skb_to_full_sk(skb);
if (!sk)
return;
hdr = skb->encapsulation ? inner_ipv6_hdr(skb) : ipv6_hdr(skb);
fl6.fl6_dport = inet_sk(sk)->inet_dport;

View File

@ -30,7 +30,11 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk)
{
struct tcp_skb_cb *tcp_cb = (struct tcp_skb_cb *)skb->cb;
skb_reset_transport_header(skb);
if (!skb_reset_transport_header_careful(skb)) {
XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR);
kfree_skb(skb);
return;
}
/* restore IP CB, we need at least IP6CB->nhoff */
memmove(skb->cb, &tcp_cb->header, sizeof(tcp_cb->header));

View File

@ -474,6 +474,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
struct xfrm_state *x = NULL;
xfrm_address_t *daddr;
u32 mark = skb->mark;
u8 xfrm_proto = nexthdr;
unsigned int family = AF_UNSPEC;
int decaps = 0;
int async = 0;
@ -485,6 +486,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
if (encap_type < 0 || (xo && (xo->flags & XFRM_GRO || encap_type == 0 ||
encap_type == UDP_ENCAP_ESPINUDP))) {
x = xfrm_input_state(skb);
xfrm_proto = x->type ? x->type->proto : nexthdr;
if (unlikely(x->km.state != XFRM_STATE_VALID)) {
if (x->km.state == XFRM_STATE_ACQ)
@ -592,11 +594,13 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
x = xfrm_input_state_lookup(net, mark, daddr, spi, nexthdr, family);
if (x == NULL) {
xfrm_proto = nexthdr;
secpath_reset(skb);
XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES);
xfrm_audit_state_notfound(skb, family, spi, seq);
goto drop;
}
xfrm_proto = x->type ? x->type->proto : nexthdr;
if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) {
secpath_reset(skb);
@ -604,6 +608,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
xfrm_audit_state_notfound(skb, family, spi, seq);
xfrm_state_put(x);
x = NULL;
xfrm_proto = nexthdr;
goto drop;
}
@ -728,7 +733,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
} while (!err);
rcu_read_lock();
err = xfrm_rcv_cb(skb, family, x->type->proto, 0);
err = xfrm_rcv_cb(skb, family, xfrm_proto, 0);
if (err) {
rcu_read_unlock();
goto drop;
@ -753,7 +758,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
xfrm_gro = xo->flags & XFRM_GRO;
err = -EAFNOSUPPORT;
afinfo = xfrm_state_afinfo_get_rcu(x->props.family);
afinfo = xfrm_state_afinfo_get_rcu(family);
if (likely(afinfo))
err = afinfo->transport_finish(skb, xfrm_gro || async);
if (xfrm_gro) {
@ -776,7 +781,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
drop:
if (async)
dev_put(dev);
xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1);
xfrm_rcv_cb(skb, family, xfrm_proto, -1);
kfree_skb(skb);
return 0;
}
@ -800,12 +805,17 @@ static void xfrm_trans_reinject(struct work_struct *work)
spin_unlock_bh(&trans->queue_lock);
local_bh_disable();
rcu_read_lock();
while ((skb = __skb_dequeue(&queue))) {
struct net *net = XFRM_TRANS_SKB_CB(skb)->net;
struct net_device *dev = skb->dev;
XFRM_TRANS_SKB_CB(skb)->finish(net, NULL, skb);
if (dev)
dev_put(dev);
put_net(net);
}
rcu_read_unlock();
local_bh_enable();
}
@ -821,12 +831,18 @@ int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb,
if (skb_queue_len(&trans->queue) >= READ_ONCE(net_hotdata.max_backlog))
return -ENOBUFS;
if (skb_dst(skb) && !skb_dst_force(skb))
return -EHOSTUNREACH;
BUILD_BUG_ON(sizeof(struct xfrm_trans_cb) > sizeof(skb->cb));
hold_net = maybe_get_net(net);
if (!hold_net)
return -ENODEV;
if (skb->dev)
dev_hold(skb->dev);
XFRM_TRANS_SKB_CB(skb)->finish = finish;
XFRM_TRANS_SKB_CB(skb)->net = hold_net;
spin_lock_bh(&trans->queue_lock);

View File

@ -416,6 +416,14 @@ static bool iptfs_skb_can_add_frags(const struct sk_buff *skb,
if (skb_has_frag_list(skb) || skb->pp_recycle != walk->pp_recycle)
return false;
/* Reject an @offset that is at or beyond the end of the walk's data
* before calling iptfs_skb_reset_frag_walk(), whose fragment-advance
* loop is otherwise unbounded and would index past walk->frags[].
* This mirrors the guard already present in iptfs_skb_add_frags().
*/
if (!walk->nr_frags || offset >= walk->total + walk->initial_offset)
return false;
/* Make offset relative to current frag after setting that */
offset = iptfs_skb_reset_frag_walk(walk, offset);
@ -820,8 +828,8 @@ static u32 iptfs_reassem_cont(struct xfrm_iptfs_data *xtfs, u64 seq,
* allocate an in progress skb
*/
ipremain = __iptfs_iplen(xtfs->ra_runt);
if (ipremain < sizeof(xtfs->ra_runt)) {
/* length has to be at least runtsize large */
if (ipremain < __iptfs_iphlen(xtfs->ra_runt)) {
/* length has to be at least the IP header size */
XFRM_INC_STATS(xs_net(xtfs->x),
LINUX_MIB_XFRMINIPTFSERROR);
goto abandon;

View File

@ -2770,9 +2770,12 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
xdst0->path = dst;
err = -ENODEV;
dev = dst->dev;
if (!dev)
rcu_read_lock();
dev = dst_dev_rcu(dst);
if (!dev) {
rcu_read_unlock();
goto free_dst;
}
xfrm_init_path(xdst0, dst, nfheader_len);
xfrm_init_pmtu(bundle, nx);
@ -2780,8 +2783,10 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
for (xdst_prev = xdst0; xdst_prev != (struct xfrm_dst *)dst;
xdst_prev = (struct xfrm_dst *) xfrm_dst_child(&xdst_prev->u.dst)) {
err = xfrm_fill_dst(xdst_prev, dev, fl);
if (err)
if (err) {
rcu_read_unlock();
goto free_dst;
}
xdst_prev->u.dst.header_len = header_len;
xdst_prev->u.dst.trailer_len = trailer_len;
@ -2789,6 +2794,7 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
trailer_len -= xdst_prev->u.dst.xfrm->props.trailer_len;
}
rcu_read_unlock();
return &xdst0->u.dst;
put_states:
@ -3058,11 +3064,15 @@ static struct xfrm_dst *xfrm_create_dummy_bundle(struct net *net,
xfrm_init_path((struct xfrm_dst *)dst1, dst, 0);
err = -ENODEV;
dev = dst->dev;
if (!dev)
rcu_read_lock();
dev = dst_dev_rcu(dst);
if (!dev) {
rcu_read_unlock();
goto free_dst;
}
err = xfrm_fill_dst(xdst, dev, fl);
rcu_read_unlock();
if (err)
goto free_dst;

View File

@ -226,6 +226,7 @@ static struct xfrm_state_afinfo __rcu *xfrm_state_afinfo[NPROTO];
static DEFINE_SPINLOCK(xfrm_state_gc_lock);
static DEFINE_SPINLOCK(xfrm_state_dev_gc_lock);
static DEFINE_MUTEX(xfrm_state_gc_mutex);
int __xfrm_state_delete(struct xfrm_state *x);
@ -632,8 +633,10 @@ static void xfrm_state_gc_task(struct work_struct *work)
synchronize_rcu();
mutex_lock(&xfrm_state_gc_mutex);
hlist_for_each_entry_safe(x, tmp, &gc_list, gclist)
xfrm_state_gc_destroy(x);
mutex_unlock(&xfrm_state_gc_mutex);
}
static enum hrtimer_restart xfrm_timer_handler(struct hrtimer *me)
@ -823,9 +826,9 @@ int __xfrm_state_delete(struct xfrm_state *x)
if (!hlist_unhashed(&x->byseq))
hlist_del_init_rcu(&x->byseq);
if (!hlist_unhashed(&x->state_cache))
hlist_del_rcu(&x->state_cache);
hlist_del_init_rcu(&x->state_cache);
if (!hlist_unhashed(&x->state_cache_input))
hlist_del_rcu(&x->state_cache_input);
hlist_del_init_rcu(&x->state_cache_input);
if (!hlist_unhashed(&x->byspi))
hlist_del_init_rcu(&x->byspi);
@ -1000,6 +1003,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali
out:
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
mutex_lock(&xfrm_state_gc_mutex);
spin_lock_bh(&xfrm_state_dev_gc_lock);
restart_gc:
hlist_for_each_entry_safe(x, tmp, &xfrm_state_dev_gc_list, dev_gclist) {
@ -1014,6 +1018,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali
}
spin_unlock_bh(&xfrm_state_dev_gc_lock);
mutex_unlock(&xfrm_state_gc_mutex);
xfrm_flush_gc();

View File

@ -1877,7 +1877,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh,
struct net *net = sock_net(skb->sk);
struct xfrm_state *x;
struct xfrm_userspi_info *p;
struct xfrm_translator *xtr;
struct sk_buff *resp_skb;
xfrm_address_t *daddr;
int family;
@ -1943,17 +1942,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh,
goto out;
}
xtr = xfrm_get_translator();
if (xtr) {
err = xtr->alloc_compat(skb, nlmsg_hdr(skb));
xfrm_put_translator(xtr);
if (err) {
kfree_skb(resp_skb);
goto out;
}
}
err = nlmsg_unicast(xfrm_net_nlsk(net, skb), resp_skb, NETLINK_CB(skb).portid);
out:
@ -3337,7 +3325,11 @@ static int xfrm_send_migrate_state(struct net *net,
return err;
}
return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE);
rcu_read_lock();
err = xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE);
rcu_read_unlock();
return err;
}
static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,