AFD Attack Surface Deep-Dive
No single Windows kernel driver has produced more privilege escalation vulnerabilities than afd.sys. With 13 CVEs in the KernelSight corpus and three confirmed in-the-wild exploits (including one attributed to the Lazarus Group), the Ancillary Function Driver for WinSock is the most consistently exploited networking component in the Windows kernel. Its bugs span nearly every vulnerability class: use-after-free, heap overflow, missing validation, integer overflow, and race conditions. What makes AFD remarkable is not any single bug, but the architectural reality that keeps producing them.
Why AFD Cannot Stop Being Vulnerable
The Ancillary Function Driver sits between every user-mode socket call and the transport layer. When an application calls connect(), send(), or recv(), those calls flow through ws2_32.dll into afd.sys, which translates them into kernel transport operations via TDI or WSK. Every networked Windows application touches this driver. No special privileges are needed to trigger most of its code paths.
User-Mode: WSASocket() → connect() → send()/recv() → closesocket()
↓ ↓ ↓ ↓
Kernel: AfdCreate AfdConnect AfdSend/Recv AfdCleanup
↓ ↓ ↓ ↓
Transport: TdiOpen TdiConnect TdiSend/Recv TdiClose
Five properties make this driver a persistent target:
Universal reach. Any process that opens a network socket interacts with afd.sys. The attack surface requires nothing beyond standard socket APIs.
High concurrency. Socket operations are asynchronous by design. Multiple threads can bind, unbind, send, receive, and close at the same time, and AFD must manage object lifetimes across all of these paths. Get the synchronization wrong in even one path, and you have a use-after-free.
Complex buffer lifetime. Registered I/O (introduced in Windows 8) pre-registers user buffers for kernel use, sharing memory between user and kernel mode. The kernel must track buffer validity across async operations that complete out of order. This is where the Lazarus Group found their zero-day.
Large IOCTL surface. Beyond standard socket APIs, AFD exposes dozens of internal IOCTLs for Registered I/O registration, notification management, and other subsystem operations. These paths often receive less testing coverage than the core socket lifecycle.
Legacy code. Parts of afd.sys date back to Windows NT 3.5. TDI is deprecated but still present. Later additions like RIO and the notification system layer on top of old code with no structural redesign.
The Architecture That Keeps Breaking
Key Components
Socket Objects are kernel structures representing open sockets, tracked via file objects and accessible through handles. Each socket carries state for connection, buffer tracking, and pending I/O.
TDI / WSK Interface connects afd.sys to transport protocol drivers like tcpip.sys. AFD translates user-mode socket operations into TDI (legacy) or WinSock Kernel calls.
Registered I/O (RIO) is the high-performance I/O model that uses pre-registered buffers and completion queues. RIO paths share memory between user and kernel mode, making lifetime management significantly harder.
Notification System manages asynchronous event delivery to user mode through AfdNotifyPostEvents and related functions. This is the subsystem where race conditions cluster.
I/O Completion uses I/O completion ports for async operations. Pending IRPs reference socket objects and buffers that must remain valid through completion.
CVE Timeline
| CVE | Year | Class | ITW | Notes |
|---|---|---|---|---|
| CVE-2023-21768 | 2023 | Missing ProbeForWrite | No | WinSock IO ring write-what-where |
| CVE-2023-28218 | 2023 | Integer Overflow | No | AfdCopyCMSGBuffer overflow |
| CVE-2024-38193 | 2024 | UAF / Lifetime | Yes | Registered I/O buffer race, Lazarus Group |
| CVE-2025-21418 | 2025 | Buffer Overflow (Heap) | Yes | Heap overflow allowing SYSTEM |
| CVE-2025-32709 | 2025 | UAF | Yes | Socket closure UAF |
| CVE-2025-49661 | 2025 | Untrusted Pointer | No | Pointer dereference in IOCTL handler |
| CVE-2025-49762 | 2025 | Race Condition | No | Concurrent operation race |
| CVE-2025-53147 | 2025 | UAF | No | Object lifetime error |
| CVE-2025-53718 | 2025 | UAF | No | Object lifetime error |
| CVE-2025-60719 | 2025 | UAF / Race | No | Socket unbind race |
| CVE-2025-62213 | 2025 | UAF | No | Object lifetime error |
| CVE-2025-62217 | 2025 | EoP | No | Elevation of privilege |
| CVE-2026-21241 | 2026 | UAF / Race | No | AfdNotifyPostEvents spinlock race |
The Patterns That Keep Repeating
Socket Teardown Races
Seven of the thirteen CVEs follow the same pattern. One thread closes or unbinds a socket while another thread still uses the socket object or its buffers. The close path frees the object; the concurrent path dereferences a stale pointer. The race window is typically between spinlock release and object deallocation.
CVE-2026-21241 illustrates this clearly: the notification spinlock is released before the notification object is fully torn down, so a concurrent AfdNotifyPostEvents call can hit freed memory. The window is narrow, but attackers have become skilled at widening it through thread scheduling manipulation.
Buffer Length Validation Failures
The driver accepts a user-supplied length or size without validating it against the actual buffer allocation. CVE-2025-21418 is a heap overflow from an unchecked length field. CVE-2023-28218 is an integer overflow in CMSG buffer size calculation, where the computed allocation size wraps to a small value while the subsequent copy uses the original, much larger size.
Missing User Buffer Validation
CVE-2023-21768 is a missing ProbeForWrite on an I/O ring buffer pointer. Without the probe, the kernel writes to a user-controlled address, providing a direct write-what-where primitive. This is the simplest class of AFD bug: a single missing check produces a clean exploitation primitive with no race or heap layout dependency.
Registered I/O Lifetime Management
RIO buffers are pre-registered with the kernel for performance. If a buffer is deregistered while an async operation still references it, the kernel operates on freed memory. CVE-2024-38193 exploited exactly this gap. The Lazarus Group used it for SYSTEM escalation in targeted campaigns, chaining it with other tooling for full compromise.
How AFD Exploits Work
A typical afd.sys exploitation chain progresses through a recognizable sequence. The attacker starts by creating a socket with WSASocket() to allocate the kernel socket object. They then set up concurrent threads, one to trigger the vulnerable operation and another to race the close or unbind path. When the race is won, a use-after-free condition appears on the socket or notification object. The freed pool region gets sprayed with controlled data, typically through named pipe attributes (the NPNX pool spray technique). The stale pointer dereference now reads attacker-controlled data, often a corrupted _IO_MINI_COMPLETION_PACKET_USER structure. The corrupted structure gives a bit-manipulation or arbitrary read/write primitive. CVE-2026-21241 uses RtlSetBit and RtlClearAllBits as kCFG-compliant primitives, avoiding the need for control-flow hijacking. From there, escalation happens via token swap, privilege bit-set, or DACL corruption to reach SYSTEM.
sequenceDiagram
participant A as Attacker Thread 1
participant K as afd.sys (Kernel)
participant B as Attacker Thread 2
participant P as Pool Memory
rect rgb(30, 41, 59)
A->>K: WSASocket() - allocate socket object
A->>K: Trigger vulnerable operation
B->>K: Close/unbind socket (race)
K->>P: Free socket/notification object
Note over P: Object freed, memory available
A->>P: NPNX pool spray (controlled data)
Note over P: Freed region reclaimed with fake struct
K->>P: Stale pointer dereference
Note over K: Reads attacker-controlled data
K->>K: RtlSetBit / token swap
Note over A: Process now runs as SYSTEM
end
The Mitigation Landscape
Microsoft has fixed individual afd.sys vulnerabilities with incremental patches: extending spinlock scope, adding reference counting, tightening buffer validation. No structural redesign has been announced. The driver's concurrency model remains the same across all supported Windows versions.
The Vulnerable Driver Blocklist does not apply to afd.sys since it ships inbox. HVCI and kCFG constrain exploitation techniques but do not prevent the underlying bugs. The bit-manipulation primitive from CVE-2026-21241 demonstrates that exploitation still works under kCFG, since the primitive uses legitimate kernel APIs rather than hijacking control flow.
AutoPiff Detection
AutoPiff monitors afd.sys patches for synchronization and lifetime changes:
added_spinlock_acquire- New spin lock acquisition around previously unprotected state accessmodified_object_free- Changes to socket or buffer deallocation paths, indicating lifetime fixadded_ref_count- Reference counting additions to object management codeadded_length_check- Buffer size validation in IOCTL handlers
The Broader Pattern
AFD is a case study in what happens when a complex, concurrent, legacy kernel component gets incremental patches instead of structural redesign. The driver's architecture, where multiple threads operate on shared objects with fine-grained locking, creates a combinatorial explosion of possible interleavings. Each patch closes one race window, but the fundamental design keeps producing new ones. Researchers looking for their next LPE should study AFD's notification and RIO subsystems: they combine shared memory, async completion, and reference counting in ways that consistently produce exploitable lifetime errors.
Related Case Studies
- CVE-2026-21241 - notification UAF with bit-manipulation primitive
- CVE-2024-38193 - Registered I/O UAF, Lazarus Group campaign
- CVE-2025-21418 - heap overflow, exploited ITW
- CVE-2025-32709 - socket closure UAF, exploited ITW
- CVE-2023-21768 - missing ProbeForWrite, I/O ring write-what-where