CVE-2021-46954 - Linux Kernel Stack Out-of-Bounds Read in sch_frag When Fragmenting IPv4 Packets
In this deep dive, we'll look at CVE-2021-46954, a subtle but impactful vulnerability in the Linux kernel network stack, specifically in the sch_frag traffic control module. The bug allowed an out-of-bounds stack memory read when handling certain IPv4 packets, potentially leaking kernel stack data or causing stability issues. We'll explore what caused the issue, how it was triggered, view code snippets, discuss exploitation, and show how the problem was fixed. All technical, but written simply for developers and sysadmins.
What is CVE-2021-46954?
This CVE relates to a bug in the Linux kernel's handling of packet fragmentation via the sch_frag Qdisc (queueing discipline). When the kernel fragmented IPv4 packets that had been previously reassembled (using connection tracking), it could read past the end of a stack-allocated structure, causing a "stack out-of-bounds" read.
- Affected File: net/sched/sch_frag.c
- Linux versions: Affected up to and including v5.13-rc1 (fixed in commit)
Reported by: Paolo Abeni, Red Hat
- CVE page: CVE-2021-46954 on NVD
When triggering the bug, sysadmins would see kernel splats (errors) like this
BUG: KASAN: stack-out-of-bounds in ip_do_fragment+x1b03/x1f60
Read of size 1 at addr ffff888147009574 by task ping/947
Call Trace:
...
ip_do_fragment+x1b03/x1f60
sch_fragment+x4bf/xe40
...
Here, ip_do_fragment is called by sch_fragment. The problem stems from how sch_fragment set up a temporary routing structure (dst_entry), expecting it would be handled like a generic destination, but the kernel's IPv4 code expected a larger, more specific rtable structure.
Background:
sch_frag is used for software-based packet fragmentation in the Linux traffic control system (tc). It tries to fragment large packets so as not to exceed path MTUs.
When a packet goes through
sch_fragment()
-> ip_do_fragment()
-> ip_skb_dst_mtu()
-> ip_dst_mtu_maybe_forward()
-> ip_mtu_locked()
...the code uses a pointer to a temporary struct dst_entry, but actually expects a struct rtable (which is bigger and has extra fields at the end).
Code Snippet Before the Fix
/* net/sched/sch_frag.c, old code */
struct dst_entry dst = { .dev = dev, .ops = &dst_ops };
IPCB(skb)->flags |= IPSKB_FRAGMENTED;
ip_do_fragment(dev, skb, ... , &dst)
The problem: While dst_entry is big enough for the base destination structure, several functions expect to access fields only present in struct rtable — which extends dst_entry with additional values. So, when the code tried to access, say, rt_mtu_locked, it would read beyond the stack variable.
Who could exploit this?
- Local attackers with the ability to inject and manipulate packets into the network stack via tc rules.
- A misconfigured system, e.g., with permissive traffic shaping, could be susceptible even to remote input triggering the flaw.
Set up connection tracking (act_ct): So packets are *reassembled*.
2. Apply mirroring (act_mirred): To copy/redirect packets.
3. Enable software fragmentation (sch_frag): On a traffic control ingress/egress path.
Send a sufficiently large IPv4 packet: That needs to be fragmented.
On a vulnerable kernel, this action could cause an OOB stack read, as observable in the kernel log.
Here's a minimal setup to experiment (run as root, not in production)
# 1. Enable connection tracking on an interface (e.g., eth)
tc qdisc add dev eth ingress
tc filter add dev eth ingress protocol ip prio 1 flower \
action ct
# 2. Mirror and fragment packets
tc filter add dev eth ingress protocol ip prio 2 flower \
action mirred egress redirect dev veth \
action frag max_size 140
# 3. Send a big packet (using ping, curl, or custom tool)
sudo ping -s 300 <target IP>
Result: Kernel log shows KASAN OOB splats or possible crash.
The Fix
The fix, merged here, simply replaced the use of a too-small dst_entry stack structure with a sufficiently large rtable when fragmenting IPv4 packets. This matches what the code already did correctly for IPv6.
Fixed Code Excerpt
#ifdef CONFIG_IPV6
...
struct rt6_info rt;
...
#else
struct rtable rt;
...
#endif
ip_do_fragment(dev, skb, ..., &rt.dst)
This ensures that all expected fields are present, avoiding the OOB access.
Linux Kernel Commit:
net/sched: sch_frag: fix stack OOB read while fragmenting IPv4 packets
CVE page:
lkml.org post:
Conclusion
If you maintain Linux systems and use advanced traffic control with packet fragmentation, you should patch now (v5.13+ or backported fix). CVE-2021-46954 shows how easy it is to get C structures wrong in kernel code, and why security fixes should always be applied — even for features you might not use every day.
Stay safe, patch your kernels, and keep an eye out for these subtle OOB bugs!
If you found this post useful, please share it with your Linux admin friends. For more in-depth kernel CVE breakdowns, follow our blog!
Timeline
Published on: 02/27/2024 19:04:06 UTC
Last modified on: 04/10/2024 20:15:05 UTC