A newly assigned CVE—CVE-2024-47685—revealed a subtle but important bug in the Linux kernel's handling of IPv6 network packet rejection. More specifically, the bug affected the nf_reject_ipv6 code, responsible for sending TCP reset packets when rejecting incoming connections. This flaw was reported by syzbot, an automated open-source kernel bug finder.
Let's break down what happened, how it can be exploited, and how it was fixed—using plain language, real-world context, and code samples.
What Is the Problem?
When the kernel rejects a TCP connection over IPv6 (using netfilter rules like nftables reject), it sends a TCP RST (reset) packet back to the sender. The function responsible for crafting this TCP header is nf_reject_ip6_tcphdr_put(). The bug? Some reserved bits in the TCP header were left *uninitialized*, meaning they could contain whatever random data happened to be in memory.
In computer security, sending out unpredictable, possibly sensitive, kernel memory—not even directly by the user's code, but as a *side effect* of normal firewall operation—represents an information disclosure vulnerability.
How Was The Vulnerability Discovered?
szybot fuzzed network inputs and, using Kernel Memory Sanitizer (KMSAN), flagged this issue with logs like:
BUG: KMSAN: uninit-value in nf_reject_ip6_tcphdr_put+x688/x6c net/ipv6/netfilter/nf_reject_ipv6.c:255
...
Uninit was stored to memory at:
nf_reject_ip6_tcphdr_put+x60c/x6c net/ipv6/netfilter/nf_reject_ipv6.c:249
That means the kernel sent packet data that included "uninitialized" (random) bits—precisely, in the four reserved TCP header bits.
The TCP header in IPv6 looks like this (simplified for relevance)
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
__u16 res1:4;
__u16 doff:4;
__u16 fin:1;
__u16 syn:1;
__u16 rst:1;
__u16 psh:1;
__u16 ack:1;
__u16 urg:1;
__u16 ece:1;
__u16 cwr:1;
__be16 window;
__be16 check;
__be16 urg_ptr;
};
The res1 field is 4 bits of "reserved" data. These must be set to zero. But due to a coding oversight, they weren't *explicitly* cleared before building the outgoing reset packet.
So when the kernel called code similar to this
th = skb_put(skb, sizeof(struct tcphdr));
/* ... now fill out th fields ... */
th->rst = 1;
It forgot to zero out *th to begin with, meaning the reserved bits could be anything.
How Can This Leak Be Exploited?
An attacker who can provoke the system to generate a lot of RST packets using IPv6 with netfilter (i.e., a rejected TCP connection) could:
This data might potentially include traces of previously accessed, sensitive data.
It is limited: the leak is just a few bits per packet, but it's a "kernel memory leak over the wire"—a class of bug that attackers love to chain for things like KASLR bypasses or as a piece in a bigger exploit.
Vulnerable (before)
struct tcphdr *th;
th = skb_put(skb, sizeof(struct tcphdr));
// Only some fields initialized, leaving reserved garbage.
th->rst = 1;
Fixed (after)
struct tcphdr *th;
th = skb_put_zero(skb, sizeof(struct tcphdr)); // zero out the header!
th->rst = 1;
// ...rest of header fields...
With skb_put_zero, all fields are guaranteed to be zero—including the reserved bits—before anything else is set.
See the commit:
- netfilter: nf_reject_ipv6: fix nf_reject_ip6_tcphdr_put() (kernel.org)
- Syzkaller report and patch discussion (lkml.org)
The exploit in the wild could look like this (in Python)
import socket
ipv6_addr = 'YOUR_TARGET_IPV6'
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
try:
s.connect((ipv6_addr, 9999)) # port can be any closed port with reject rule
except Exception as e:
pass # rejected; RST sent by target
# Use tcpdump or wireshark to analyze incoming RST packets.
# Check reserved TCP bits for random data.
You'd repeat this to capture hundreds of packets, analyzing the reserved bits in each one.
Mitigation and Patch
To fix:
Apply the patch. If your distribution updates the kernel, update right away.
2. Workaround (TEMPORARY): Use "drop" instead of "reject" in firewall rules for IPv6. This will not generate RST packets, so no faulty header fields are sent.
References
- Linux Kernel Patch on kernel.org
- Original syzbot report on lore.kernel.org
- CVE Record at Mitre (pending)
Final Notes
CVE-2024-47685 is a great example of how tiny bits—literally, just a few—of uninitialized memory can be a security risk in network code. While the exploit requires some effort and is limited in scope, it demonstrates why zeroing memory before sending it over a trusted interface is critical.
If you run Linux-based servers or network appliances, stay ahead: patch often, and keep an eye on the subtleties of kernel code!
Timeline
Published on: 10/21/2024 12:15:05 UTC
Last modified on: 12/19/2024 09:25:58 UTC