If you’re running modern Linux networking setups—especially with virtual networks (VXLAN, Geneve, GUE, or other UDP-based tunnels)—you need to know about CVE-2021-47036. This is a subtle but potentially serious flaw in the Linux kernel’s UDP Generic Receive Offload (GRO) logic that could cause packet and protocol corruption in busy, real-world cloud or container environments.

This post dives into exactly what CVE-2021-47036 is, how it happened, example code to demonstrate the bug, references to the original fix, and what an attacker or stress-tester could do with this kind of behavior. I’ll keep things as clear and practical as possible.

The Quick Explanation

“GRO” (Generic Receive Offload) tries to merge (“aggregate”) multiple small packets into bigger ones on the receive path to speed up networking—but it must *never* merge packets when their logical payloads or tunnel headers are different.

Bug: If certain kernel network offload features were enabled (NETIF_F_GRO_FRAGLIST, NETIF_F_GRO_UDP_FWD), and UDP tunnels (like VXLAN) were in use, the kernel’s udp_gro_receive() code could wrongly aggregate/merge packets at the outer UDP level… *even if their inner tunnel headers (VXLAN ID, for instance) were different.*

Impact: The kernel could treat packets with different inner tunnel data (different VXLAN networks!) as the same, smashing together logically separate conversations. This breaks isolation, delivery order, and can completely jumble things for any protocols running inside these tunnels—think sudden connection resets, garbage data, missing packets, etc.

GRO merges packets for efficiency in software.

- UDP tunnels use UDP as a carrier but contain more headers inside (VXLAN header, then the real packet).
- GRO must *not* merge packets that have fundamentally different inner headers/contexts.

Bad Path (Before Fix)

The function udp_gro_receive() was aggregating UDP packets *at the tunnel’s outer UDP level*. It wasn’t checking properly for actual tunnel usage inside (e.g., vxlan). So it could merge:

UDP packet carrying VXLAN header (VNI=200)

It then glues these distinct streams together into one segment, erasing the distinction between different logical networks. This can cause all sorts of catastrophic confusion for virtualized networking.

The Kernel Fix

The practical fix (see commit) is simple: skip UDP L4 and FRAGLIST GRO aggregation for packets that could be UDP tunnels.

Patch Snippet (for illustration)

static struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb)
{
    /* ... */
    if (udp_tunnel_could_be_here) {
        // Don't do L4 or fraglist aggregation!
        goto tunnel_gro_receive;
    }
    // Otherwise, do our normal UDP aggregation...
}

The fix just *bails out* of the “aggregate at UDP level” code path for possible tunnel packets.


### Exploit / Test Scenario (Proof-of-Concept)

While this is *not* a remote root, it’s easy to imagine an attacker with permission to inject raw packets (or just running containers in a multi-tenant cloud!) deliberately sending streams with different VXLAN headers. If unlucky network settings (NETIF_F_GRO_FRAGLIST or NETIF_F_GRO_UDP_FWD enabled) are present, they could cause cross-tenant data confusion or denial of service.

Test Script (simulated with Scapy)

# WARNING: For demo/educational use only
from scapy.all import Ether, IP, UDP

def make_vxlan_packet(vni):
    # Build a simple UDP+VXLAN (8 bytes) wrapper over inner payload
    outer = Ether(dst="de:ad:be:ef:00:01")/IP(dst="10...2")/UDP(dport=4789, sport=1234)
    vxlan_header = b'\x08' + b'\x00'*3 + (vni.to_bytes(3, 'big')) + b'\x00'
    inner = Ether(dst="de:ad:be:ef:00:02")/IP(dst="192.168.1.2")/UDP(dport=5555, sport=2222)
    pkt = outer / vxlan_header / bytes(inner)
    return pkt

p1 = make_vxlan_packet(100) # VXLAN ID 100
p2 = make_vxlan_packet(200) # VXLAN ID 200

# Send these two packets into a Linux interface with the vulnerable kernel & GRO options enabled!

All Linux distros with kernel 5.x and affected networking stacks already have security updates.

- See Debian security tracker
- Red Hat advisory

2. Verify your kernels

- Run uname -r and check release notes or kernel.org for the fix.

If you *must* use GRO, ensure you don’t enable risky combinations unless you’ve patched.

- Use ethtool -k eth to list kernel/NIC offloads.


## References/Further Reading

- Original Linux Kernel Commit
- CVE Details
- Debian Bug #990936
- Red Hat Security Advisory

Summary

CVE-2021-47036 shows how tricky and subtle offload bugs can mess up the boundaries between virtual networks—blending traffic that should be isolated. The fix is a quick kernel patch, and the problem only bites in particular offload/tunneling situations. Still, *anyone using VXLAN, GENEVE, or similar UDP tunnels on Linux (especially for cloud/multitenant setups) should apply the patch ASAP*.

If you’ve read this far, you now understand not only *the risk* but also *why it happens*—and you can clearly explain the bug to your team!

Timeline

Published on: 02/28/2024 09:15:39 UTC
Last modified on: 01/10/2025 18:25:11 UTC