DEV Community

GUIDANCE WHITE
GUIDANCE WHITE

Posted on

CVE-2026-99999 (PwnEscape) Explained: How a Single Checksum Rewrote an IPv4 Header and Broke VM Isolation

Overview

Item Detail
CVE ID CVE-2026-99999 (named "PwnEscape" by the researchers)
Affected product The Linux kernel's networking stack (IPv4 checksum offload handling) — any host interacting with guest VMs over virtio-net
Scope Effectively every kernel branch prior to the mainline fix (6785011f8b16) — Debian, RHEL, AlmaLinux, CloudLinux, and others are each backporting independently
Fixed by 6785011f8b16 (ipv4: validate checksum_start before completing checksum), landed in net.git on 2026-10-08, merged to mainline 2026-10-14
CVSS cve.org: 9.3 (Critical), AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H — Red Hat scores it differently (8.8, Important) over a disagreement on the privileges-required metric
Type A logic flaw in the TOCTOU (time-of-check/time-of-use) family (CWE-367-adjacent) — metadata that crossed a trust boundary later altered a state that had already been validated
Reported by Paulos Yibelo (PWNAI Research) — found during Vercel's "$1 Million Sandbox Challenge," privately reported to kernel security maintainers on 2026-09-04
Nature A guest-to-host VM escape primitive. Not a flaw in KVM itself — a flaw in the host's own Linux networking stack that breaks hypervisor isolation from the other side

This is both the highest-impact and the most carefully-handled CVE covered on this blog so far. Unlike an authentication bypass or a file-upload flaw in a web application, this is a path that lets one virtual machine attack the physical host it's running on. Because this is a structural issue affecting cloud providers, sandboxing services, and multi-tenant hosting broadly, this post deliberately separates what's publicly documented from what the research team chose to keep undisclosed.

The story starts with a $1 million security challenge Vercel launched for its Sandbox product in August 2026. PWNAI's Paulos Yibelo started from the question "how much can an untrusted guest influence the host kernel without ever touching the hypervisor itself?" and, within three days, found a flaw buried in Linux networking code. The essence of the flaw is simple: after the kernel "validated" a part of a packet, it allowed other code to "rewrite" that exact same part afterward.


Where the flaw lives — a promise to "write the checksum later" (checksum offload)

Linux doesn't always compute transport-layer checksums immediately. It often carries a packet in a "not yet computed" state and fills it in later. To support this, sk_buff (the structure representing a single packet inside the kernel) carries the following bookkeeping:

skb->ip_summed     CHECKSUM_PARTIAL   (checksum computation is still pending)
skb->csum_start    where checksum computation should begin
skb->csum_offset   offset from that point to where the result should be written

             final write location = csum_start + csum_offset
Enter fullscreen mode Exit fullscreen mode

The crucial detail: these values aren't just "descriptive metadata." They are, themselves, authorization to write to a specific location in packet memory later. If a less-trusted party (here, a guest VM) can influence these values, their validity has to hold all the way from "when the metadata arrives" to "when the write actually happens."


Step-by-step source analysis

Step 1 — a packet has several different notions of "where it starts"

sk_buff tracks several pointers/offsets that each represent "where the packet starts," simultaneously:

skb->head              start of the underlying allocated memory block
skb->data              current data pointer (advances as headers get stripped)
skb->network_header    position of the network-layer (IPv4) header
skb->csum_start        the stored checksum-start position
skb_headlen(skb)       how much packet data is currently held linearly
Enter fullscreen mode Exit fullscreen mode

A packet arrives with link-layer headers like Ethernet still present, and those get stripped or skipped later for L3 (IP-layer) processing. The problem: "is this far enough into the entire frame?" and "is this past this specific packet's IPv4 header?" are completely different questions.

Step 2 — input validation was only "frame-relative" (flaw #1)

__virtio_net_hdr_to_skb(), which accepts checksum metadata sent by the guest over virtio-net, had an existing check based on skb_transport_offset(skb) — an offset relative to the current skb->data view.

/* Conceptual comparison — reconstructed from the public research patch series */
transport_offset >= minimum_network_header_length               // the existing check
transport_offset - network_offset >= minimum_network_header_length   // the check that was actually needed
Enter fullscreen mode Exit fullscreen mode

These two inequalities are not interchangeable. The first only guarantees "far enough into this entire frame"; it does not guarantee, like the second does, "far enough relative to the network header." When a packet still has extra link-layer structure present, the two coordinate systems can drift apart — what the researchers call "coordinate drift." This is the first trust failure PWNAI identified: checksum metadata from a less-trusted origin entered host packet processing without ever being compared against the reference (the IPv4 header) that later code would actually use.

Step 3 — the root cause: "validate first, then read the header" — except the order was reversed (flaw #2)

The most important detail here is the order of operations. The host receives a packet, parses it as IPv4, and carries out fragment processing and routing against a normal, trusted 20-byte header. The problem surfaces later, at the point where output-side fragmentation finally completes a pending checksum. Before the fix, ip_do_fragment() in net/ipv4/ip_output.c followed this ordering, conceptually:

/* Pre-fix ordering, abbreviated for explanation — from the public patch commit */
if (skb->ip_summed == CHECKSUM_PARTIAL)
    skb_checksum_help(skb);    /* performs the deferred checksum write FIRST */

iph = ip_hdr(skb);
/* fragmentation logic later derives header length (hlen) from iph->ihl */
Enter fullscreen mode Exit fullscreen mode

skb_checksum_help() isn't inherently dangerous. The danger here is that, with forged metadata, its write can land on bytes that have already been parsed as part of the IPv4 header — and if that overlap happens to hit IPv4's IHL field (Internet Header Length — the header length in 32-bit words, stored in the low 4 bits of the first byte), the consequences change completely.

First byte of a normal packet:   0x45  →  version 4, IHL 5 words  →  header length 20 bytes
After the checksum write:        0x4f  →  version 4, IHL 15 words →  header length 60 bytes
Enter fullscreen mode Exit fullscreen mode

The version nibble (4) stays the same — only the length interpretation flips. What makes this worse: every earlier stage had already made its decisions assuming a 20-byte header. Only the fragmentation code, much later, reads that quietly-updated field as if it had been that way all along. This isn't a race between threads — it's a fully deterministic, reproducible, semantic-level TOCTOU (a mismatch between the time of validation and the time of use).

Step 4 — believing the header is 60 bytes and copying that much comes up 32 bytes short

In the research case, the actual logical, contiguous data in that skb was only 28 bytes (the original 20-byte IPv4 header plus 8 bytes of payload present in the first fragment). But the forged IHL tells the fragmentation code "this header is 60 bytes."

skb_headlen(skb) = 28   ← the logical packet data that actually exists
header_copy_len  = 60   ← what the forged IHL claims the header length is
difference       = 32   ← copied out and emitted as if it were "IP options"
Enter fullscreen mode Exit fullscreen mode

The 32 bytes beyond that 28-byte boundary aren't part of the packet at all — they're whatever other host kernel memory happened to sit in the allocation's remaining tailroom. This isn't a read past the entire memory allocation (which is why it doesn't crash the kernel) — it's an out-of-bounds read relative to the data that skb is logically responsible for. The moment these bytes get carried out, disguised as IP options, in a fragment that's actually transmitted off the host (toward the guest), an internal state problem becomes externally observable information disclosure.

Step 5 — why this was an "escape primitive," not just a "leak"

A reproducible, attacker-controlled host memory disclosure is already serious in a multi-tenant environment on its own. But the research team went further in their own, researcher-owned KVM environment. Using this read capability to narrow down uncertainty about host memory state, they report demonstrating selected 64-byte host-kernel writes, and from there, within a bounded lab chain, executing a host-root helper and returning a host-generated marker back to the guest (confirmed across four independent, cold-boot reproductions).

The researchers drew a clear line here — they deliberately avoided claiming that "a 32-byte disclosure trivially becomes code execution." Proving a leak, proving a write, succeeding at execution in your own lab, and claiming it works in some arbitrary provider's production environment are all different levels of evidence. So the techniques that actually turned "read" into "write" and "execution" — allocator grooming to place objects at chosen locations, object-lifetime control, hypervisor-specific adaptation — were not published. This is standard responsible-disclosure practice, and it also buys time for other hypervisor and sandbox vendors to patch.


About the PoC — why this post doesn't contain working attack code

This CVE is different in character from every other post on this blog so far. As CloudLinux and several other vendor trackers explicitly state, no publicly distributed PoC exists, and the research team deliberately withheld the specific techniques (memory grooming, object placement, hypervisor-specific triggering) that were needed to get from disclosure to host writes and execution.

Reconstructing working attack code in this situation would be no different from creating and distributing a new guest-to-host VM escape weapon that doesn't currently exist publicly. Because this affects cloud infrastructure broadly, it needs to be handled by a different standard than the other "patched web application vulnerability" posts on this blog — so this post does not write or reconstruct executable exploit code.

What can be shared is the evidence methodology the researchers described publicly — how they validated this flaw without publishing the weaponization itself.

  • Control design: they compared an "identity case," where the packet's original header length stayed unchanged, against an "active case," where a later operation changed that interpretation.
  • Structural validity check: they confirmed the transmitted packet was structurally valid at the protocol level (the leak took the shape of a normal-looking IP packet, not an obviously corrupted one).
  • Out-of-bounds byte check: they confirmed nonzero bytes appeared past the known logical data boundary.
  • Difference-from-sender check: they confirmed those bytes differed from the "guard data" the sender (the attacker themselves) had planted, ruling out simply reading back their own data.
  • Dual-signal cross-verification: rather than relying on packet capture alone, they independently confirmed receipt at the application level, distinguishing a genuine host-side rewrite from an artifact of packet construction or capture.

This methodology itself is a good example of how to design proof that's rigorous and falsifiable without overclaiming — and it's enough to understand why this flaw is credibly established, even without any working attack code.


The patch — a one-line invariant: "validate before you complete"

The accepted fix (by Michael S. Tsirkin) opted for a small, targeted guard right before IPv4 checksum completion, rather than a broad redesign.

// include/net/ip.h (after the fix)
static inline int ip_check_csum_start(const struct sk_buff *skb)
{
    if (unlikely(skb->csum_start <
                 skb->network_header + ip_hdrlen(skb)))
        return -EINVAL;
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

The core invariant fits in one line:

checksum start position >= IPv4 network-header start + (parsed) IPv4 header length
Enter fullscreen mode Exit fullscreen mode

Assuming csum_offset is non-negative, this guarantees that the final location the checksum gets written to can never fall inside the IPv4 header. It doesn't certify that the check points at a valid TCP/UDP checksum field — but it provides exactly the guarantee needed: that the already-trusted IPv4 header can't be overwritten. This guard was added at each of the five call sites that complete a checksum.

// net/ipv4/ip_output.c · ip_do_fragment() (after the fix, reconstructed from the public commit)
if (skb->ip_summed == CHECKSUM_PARTIAL) {
    err = ip_check_csum_start(skb);
    if (!err)
        err = skb_checksum_help(skb);
    if (err)
        goto fail;
}
Enter fullscreen mode Exit fullscreen mode

As the researchers emphasize, this patch isn't a full redesign of the virtio checksum API — it's a defense-in-depth measure. Broader input validation across the entire ingress path remains a separate piece of follow-up work.


The broader pattern this case illustrates — deferred-metadata trust inversion

The research team summarized the essence of this flaw as a recurring anti-pattern:

untrusted metadata enters
        ↓
validated only against an early representation
        ↓
packet gets normalized, parsed, authorized
        ↓
(later) that metadata triggers an actual operation
        ↓
security-relevant bytes change
        ↓
the next consumer trusts the changed interpretation
        ↓
memory safety or policy boundary collapses
Enter fullscreen mode Exit fullscreen mode

The audit question to ask isn't "did we validate the checksum offsets?" It's: "after every transformation this value passes through, can a deferred write still reach bytes whose meaning has already been trusted?" This lesson isn't specific to Linux networking — it applies to any system handling asynchronous, deferred work alongside mutable metadata.


Remediation

  • Update your kernel to a version containing 6785011f8b16, or your distribution's published backport. Major distributions — Debian, the Red Hat family including AlmaLinux and CloudLinux, and others — are each backporting to their own stable branches, so check your distribution's security advisory directly.
  • This flaw directly and most immediately affects virtualization hosts (KVM-based) running untrusted guest VMs. As a stopgap before patching, disabling checksum offload for untrusted guests has been discussed as a mitigation, but it comes with a performance cost and should be treated as limited and unverified.
  • Even in shared hosting environments without VMs, check whether unprivileged user namespaces could reach a similar path. Disabling unprivileged user namespace creation, if you don't need it, reduces this attack surface as well.
  • If you use a cloud or sandbox provider, the most reliable step is checking their official advisories for what action they've taken on this CVE. This is a flaw in the Linux kernel itself, not a vendor-specific misconfiguration, so multiple hypervisors and sandbox products can be affected simultaneously.

Top comments (0)