CVE-2021-24086 — Windows TCP/IP IPv6 Fragmentation NULL Dereference (“Packet of Death”)
Last updated: 2026-04-15
Severity: High
Component: tcpip.sys (IPv6 fragmentation)
Bug Class: Integer Truncation → NULL Dereference
Privilege Escalation: Remote → DoS (BSOD); not exploitable for code execution
Patch: February 9, 2021 (Patch Tuesday)
Related: Tcpip Stack, Cve 2024 38063, Cve 2021 24094, Integer Overflows, Null Deref
Tags:kernel-mode,integer-overflow,null-deref,tcpip,remote
Vulnerability Summary
A remote DoS vulnerability in every version of Windows with IPv6 enabled. A single crafted sequence of IPv6 packets crashes the target kernel (BSOD) via a NULL pointer dereference in tcpip!Ipv6pReassembleDatagram. No authentication or user interaction required.
Root cause: a uint16_t truncation in NetioRetreatNetBuffer causes an undersized MDL allocation, after which NdisGetDataBuffer is called with the full (non-truncated) size, detects a size mismatch, and returns NULL. The caller blindly dereferences this NULL pointer.
Discovered internally by Microsoft’s @piazzt. Public PoC written independently by Axel “0vercl0k” Souchet and Francisco Falcon (Quarkslab).
Root Cause Analysis
The Integer Truncation
In tcpip!Ipv6pReassembleDatagram, the total header length is computed as:
const uint32_t UnfragmentableLength = Reassembly->UnfragmentableLength;
const uint32_t TotalLength = UnfragmentableLength + Reassembly->DataLength;
const uint32_t HeaderAndOptionsLength = UnfragmentableLength + sizeof(ipv6_header_t); // 0x28
If UnfragmentableLength = 0xFFD0 (crafted via nested fragments, see below), then HeaderAndOptionsLength = 0xFFD0 + 0x28 = 0xFFF8.
The bug: NetioRetreatNetBuffer is called with a truncated uint16_t(HeaderAndOptionsLength):
// Step 1: allocate MDL — uint16_t truncation applied
if (NetioRetreatNetBuffer(FirstNetBuffer, uint16_t(HeaderAndOptionsLength), 0) < 0) ...
// uint16_t(0xFFF8) = 0xFFF8 (ok in this case — both same)
// BUT: if HeaderAndOptionsLength = 0x10028 (from specific configs), uint16_t truncates to 0x0028 — tiny MDL
The critical mismatch: NdisGetDataBuffer is later called with the full 32-bit HeaderAndOptionsLength:
// Step 2: get data pointer — full 32-bit size used
Buffer = (ipv6_header_t*)NdisGetDataBuffer(
FirstNetBuffer,
HeaderAndOptionsLength, // full 32-bit — may differ from what was retreated
NULL, // Storage = NULL
1, 0
);
When uint16_t(HeaderAndOptionsLength) != HeaderAndOptionsLength, the MDL allocated in step 1 is too small for the access requested in step 2. NdisGetDataBuffer with Storage = NULL returns NULL when the data is non-contiguous and the requested range exceeds the buffer. The function then dereferences this NULL:
*Buffer = Reassembly->Ipv6; // NULL dereference → BSOD (DRIVER_IRQL_NOT_LESS_OR_EQUAL 0xD1)
Triggering Condition: Crafting Large UnfragmentableLength
The UnfragmentableLength is the total size of IPv6 extension headers in the “unfragmentable part” — i.e., headers before the Fragment Header in the first fragment. Under normal conditions, this is limited by MTU (~1500 bytes).
The trick: nested IPv6 fragments. Windows (unlike FreeBSD and others) accepts fragments-within-fragments and performs recursive reassembly. The attacker crafts:
Outer fragments (ID=0x11111111):
Fragment 0: [HopByHop] + [Inner Fragment Header (ID=0x22222222, offset=0, M=1)]
+ 0x1ffa × [Routing Header (8 bytes each)] = 0xFFD0 bytes
+ [Inner Fragment Header (nh=ICMP, offset=0, M=1)]
Fragment 1: [HopByHop] + [Inner Fragment Header (ID=0x22222222, offset=last, M=0)]
When outer fragments reassemble, the inner payload is treated as a new fragmented packet. The inner packet contains 0x1ffa empty Routing Headers totaling 0xFFD0 = 65,488 bytes of extension headers. This exceeds the normal MTU constraint because the “first fragment” constraint doesn’t apply to nested reassembly — the inner payload is a single contiguous chunk.
The patch adds a bounds check: if EDX > 0xFFFF (extension headers + fragmentable data exceeds 16-bit limit), bail out.
A second patch in Ipv6pReceiveFragment checks for Jumbograms (size > 0xFFFF) and bails with IppSendError.
Exploitation Technique
Impact: DoS only. The crash is a NULL pointer write:
DRIVER_IRQL_NOT_LESS_OR_EQUAL (0xD1)
Arg1: 0000000000000000 (memory referenced = NULL)
Arg2: 0000000000000002 (IRQL = 2, DISPATCH_LEVEL)
Arg3: 0000000000000001 (write operation)
Arg4: fffff80170b9937b (tcpip!Ipv6pReassembleDatagram+0x14f)
tcpip!Ipv6pReassembleDatagram+0x14f:
movups xmmword ptr [rax], xmm0 ; rax=0 → BSOD
Stack trace:
tcpip!Ipv6pReassembleDatagram
tcpip!Ipv6pReceiveFragment
tcpip!Ipv6pReceiveFragmentList
tcpip!IppReceiveHeaderBatch
tcpip!IppFlcReceivePacketsCore
tcpip!IpFlcReceivePackets
tcpip!FlpReceiveNonPreValidatedNetBufferListChain
tcpip!FlReceiveNetBufferListChainCalloutRoutine
nt!KeExpandKernelStackAndCalloutInternal
No memory corruption occurs (NULL write → immediate page fault); code execution is not possible from this crash path.
Key Primitives Used
- Nested IPv6 fragments: bypass MTU-based limit on extension header length
uint16_ttruncation: undersized MDL relative to requested data size- NULL dereference write: BSOD delivery primitive
Proof-of-Concept Notes
Author: Axel “0vercl0k” Souchet + Francisco Falcon (Quarkslab) (independent, near-simultaneous)
Repository: github.com/0vercl0k/CVE-2021-24086 (archived, MIT license)
Technique summary:
0x1ffaemptyIPv6ExtHdrRoutingheaders (each 8 bytes) =0xFFD0bytes total extension headers- Outer fragments carry this payload; last inner fragment sent non-nested to trigger recursive reassembly
- Single invocation crashes target:
sudo python3 cve-2021-24086.py <target_ipv6> 66 fragments, total size 0xfff8 Sent 66 packets. [TARGET BSOD]
Patch Analysis
Ipv6pReassembleDatagram: added check — if(ExtHeadersLength + FragmentableLength) > 0xFFFF, delete reassembly set and bailIpv6pReceiveFragment: added check — if reassembled packet is a Jumbogram (total > 0xFFFF), callIppSendErrorand bail
Workaround (Microsoft official): netsh int ipv6 set global reassemblylimit=0 (disables IPv6 reassembly — breaks legitimate large IPv6 flows).
Notable: Windows Firewall does NOT protect against this — tested by Quarkslab with firewall on: crash still occurs.
References
- Axel “0vercl0k” Souchet, “Reverse-engineering tcpip.sys: mechanics of a packet of the death (CVE-2021-24086)”, doar-e.github.io, April 2021
- Francisco Falcon, “Analysis of a Windows IPv6 Fragmentation Vulnerability: CVE-2021-24086”, blog.quarkslab.com, March 2021
- McAfee Labs, “Researchers Follow the Breadcrumbs: The Latest Vulnerabilities in Windows Network Stack”, 2021
- MSRC, “Multiple Security Updates Affecting TCP/IP”, February 2021
