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