[PATCH] inet: frags: invalidate queues before flushing them
From: Yilin Zhang
Date: Thu Sep 03 2026 - 11:28:43 EST
fqdir_pre_exit() flushes the skbs from incomplete queues without
changing their completion state. A fragment which found a queue before
high_thresh was cleared can then acquire the queue lock and reuse stale
reassembly metadata.
For IPv6, this can make ip6_frag_reasm() use the old nhoffset with a
new skb and access memory out of bounds. The resulting heap corruption
can be leveraged for local privilege escalation when unprivileged
network namespaces are available.
Kill each queue before flushing it in fqdir_pre_exit(). This marks the
queue complete and releases any timer reference removed by
inet_frag_kill(), while leaving the hash reference for the asynchronous
fqdir teardown. In-flight fragments consequently take the existing
complete-queue drop path without changing the reusable-queue semantics
of inet_frag_queue_flush().
KASAN report:
BUG: KASAN: slab-out-of-bounds in ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2))
Write of size 1 at addr ff110001039c6e00 by task poc/771
Call Trace:
? ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2))
ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2))
ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:479 (discriminator 5))
ip6_input_finish (net/ipv6/ip6_input.c:534)
ipv6_rcv (include/net/dst.h:480 (discriminator 3) net/ipv6/ip6_input.c:119 (discriminator 3) net/ipv6/ip6_input.c:109 (discriminator 3) include/linux/netfilter.h:325 (discriminator 3) include/linux/netfilter.h:319 (discriminator 3) net/ipv6/ip6_input.c:351 (discriminator 3))
packet_sendmsg (net/packet/af_packet.c:3110 net/packet/af_packet.c:3142)
__x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880)
The buggy address belongs to the object at ff110001039c6b40
which belongs to the cache skbuff_small_head of size 704
The buggy address is located 0 bytes to the right of
allocated 704-byte region [ff110001039c6b40, ff110001039c6e00)
BUG: KASAN: slab-out-of-bounds in ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1))
Read of size 1 at addr ff110001039c6e08 by task poc/771
Call Trace:
? ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1))
ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1))
ip6_input_finish (net/ipv6/ip6_input.c:534)
ipv6_rcv (include/net/dst.h:480 (discriminator 3) net/ipv6/ip6_input.c:119 (discriminator 3) net/ipv6/ip6_input.c:109 (discriminator 3) include/linux/netfilter.h:325 (discriminator 3) include/linux/netfilter.h:319 (discriminator 3) net/ipv6/ip6_input.c:351 (discriminator 3))
packet_sendmsg (net/packet/af_packet.c:3110 net/packet/af_packet.c:3142)
__x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880)
packet_sendmsg (net/packet/af_packet.c:2959 net/packet/af_packet.c:3053 net/packet/af_packet.c:3142)
__x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880)
The buggy address belongs to the object at ff110001039c6b40
which belongs to the cache skbuff_small_head of size 704
The buggy address is located 8 bytes to the right of
allocated 704-byte region [ff110001039c6b40, ff110001039c6e00)
Fixes: 006a5035b495 ("inet: frags: flush pending skbs in fqdir_pre_exit()")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Kimi Security Team <bug-report@xxxxxxxxxxx>
Tested-by: Weiming Shi <shiweiming@xxxxxxxxxxx>
Signed-off-by: Yilin Zhang <yilinzhang@xxxxxxxxxxx>
---
net/ipv4/inet_fragment.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c
--- a/net/ipv4/inet_fragment.c
+++ b/net/ipv4/inet_fragment.c
@@ -235,15 +235,22 @@ void fqdir_pre_exit(struct fqdir *fqdir)
rhashtable_walk_start(&hti);
while ((fq = rhashtable_walk_next(&hti))) {
+ int refs = 0;
+
if (IS_ERR(fq)) {
if (PTR_ERR(fq) != -EAGAIN)
break;
continue;
}
spin_lock_bh(&fq->lock);
- if (!(fq->flags & INET_FRAG_COMPLETE))
+ if (!(fq->flags & INET_FRAG_COMPLETE)) {
+ inet_frag_kill(fq, &refs);
+ }
+
+ if (fq->flags & INET_FRAG_HASH_DEAD)
inet_frag_queue_flush(fq, 0);
spin_unlock_bh(&fq->lock);
+ inet_frag_putn(fq, refs);
}
rhashtable_walk_stop(&hti);
--
2.43.0