CVE-2026-21241
AFD — race condition in AfdNotifyPostEvents spinlock release causes use-after-free EoP
Summary
| Field | Value |
|---|---|
| Driver | afd.sys |
| Vulnerability Class | Use-After-Free / Race Condition |
| Exploited ITW | No |
Affected Functions
AfdNotifyPostEventsAfdNotifySockAfdCleanupNotify
Root Cause
Race condition in the event notification subsystem. AfdNotifyPostEvents acquires a spinlock to manipulate the notification list, completes the associated I/O request, then releases the spinlock before its final dereference of the notification object (pool tag AfdN, 0x70 bytes, NonPagedPoolNx). Between spinlock release and that dereference, a concurrent AfdCleanupNotify on another thread can free the object — the cleanup path sees the notification as completed and unprotected.
The freed 0x70-byte slot is reclaimable via pool spray with controlled data. The stale pointer dereference in AfdNotifyPostEvents then operates on attacker-supplied content.
Vulnerable Code Path
AfdNotifySock (user request via WSAWaitForMultipleEvents / NtDeviceIoControlFile)
→ AfdNotifyPostEvents (processes completed notification)
→ KeReleaseInStackQueuedSpinLock (releases lock)
→ [RACE WINDOW — concurrent AfdCleanupNotify frees the AfdN object]
→ dereference of freed notification object (UAF)
Exploitation
Stage 1: Triggering the UAF
Socket notification objects populate NonPagedPoolNx with 0x70-byte AfdN allocations. Two threads race AfdNotifyPostEvents against AfdCleanupNotify to free one while a stale reference remains.
Stage 2: Pool Spray with Named Pipes (NPNX)
The freed slot is reclaimed via FSCTL_PIPE_INTERNAL_WRITE (NtFsControlFile). Named pipe DATA_QUEUE_ENTRY objects (tag Npfs, NonPagedPoolNx) can be sized to exactly 0x70 bytes. Thousands of pipe writes ensure reclamation.
The spray content overlaps with an _IO_MINI_COMPLETION_PACKET_USER structure — the ApcContext callback pointer field points to a chosen kernel function.
Stage 3: kCFG-Compliant Callback Abuse
When the I/O completion fires on the reclaimed object, the kernel calls the ApcContext function pointer through a legitimate indirect call site that passes kCFG validation. No arbitrary shellcode needed.
The chosen targets are RtlClearAllBits and RtlSetBit — both valid kCFG targets. Controlling the RTL_BITMAP pointer via the fake completion packet yields an arbitrary bit-clear/set primitive at arbitrary kernel addresses.
Stage 4: SepMediumDaclSd DACL Corruption
SepMediumDaclSd is a global security descriptor gating access to NtQuerySystemInformation(SystemModuleInformation). Since Windows 10 20H1, processes below Medium IL are blocked from querying kernel module addresses through this API. Zeroing the DACL removes the restriction, defeating KASLR without a separate info leak.
Stage 5: WIL Feature Flag Bypass
A second check still scrubs kernel addresses: the WIL feature flag Feature_RestrictKernelAddressLeaks__private_featureState. RtlSetBit flips the flag's state bits, disabling scrubbing entirely. Raw kernel pointers now flow to user mode.
Stage 6: Token Privilege Escalation
With kernel base known, locate the current process's _TOKEN. Rather than swapping the token pointer (Kernel Data Protection may detect this), RtlSetBit enables SeDebugPrivilege (bit 20) directly in _TOKEN.Privileges.Enabled.
Stage 7: SYSTEM Shell
SeDebugPrivilege allows OpenProcess(PROCESS_ALL_ACCESS) on a SYSTEM process, then CreateProcessAsUser with a duplicated SYSTEM token. Alternative: parent process spoofing via PROC_THREAD_ATTRIBUTE_PARENT_PROCESS.
Exploitation Primitive
UAF (0x70 AfdN) → NPNX pipe spray reclaim → _IO_MINI_COMPLETION_PACKET_USER callback
→ RtlClearAllBits/RtlSetBit (bit-manipulation primitive)
→ SepMediumDaclSd corruption (KASLR bypass step 1)
→ WIL feature flag disable (KASLR bypass step 2)
→ Token privilege bit-set (SeDebugPrivilege)
→ SYSTEM
Patch Analysis
The patch adds reference counting around the notification object in AfdNotifyPostEvents. The refcount is incremented before spinlock release, preventing AfdCleanupNotify from freeing the object while the stale reference exists. The reference drops only after the final dereference, closing the race window.
AutoPiff Detection
spinlock_acquisition_added— Detects extended spinlock hold duration around the notification dereferenceadded_refcount_guard— Detects addition of reference count increment before spinlock releaseadded_use_after_free_guard— Detects null-check and lifetime guard additions in the notification path
Detection
YARA Rule
rule CVE_2026_21241_AFD {
meta:
description = "Detects afd.sys versions vulnerable to notification UAF"
cve = "CVE-2026-21241"
author = "KernelSight"
severity = "high"
strings:
$mz = { 4D 5A }
$driver_name = "afd.sys" wide ascii nocase
$func_notify = "AfdNotifyPostEvents" ascii
$func_cleanup = "AfdCleanupNotify" ascii
$func_sock = "AfdNotifySock" ascii
$pool_tag = "AfdN" ascii
condition:
$mz at 0 and $driver_name and 2 of ($func_*) and $pool_tag
}
ETW Indicators
| Provider | Event / Signal | Relevance |
|---|---|---|
| Microsoft-Windows-WinSock-AFD | Socket notification events | Monitors creation and cleanup of notification objects in the race window |
| Microsoft-Windows-Kernel-Process | Process token modification | Detects SeDebugPrivilege appearing in a token without legitimate elevation |
| Microsoft-Windows-Security-Auditing | Event 4672 (Special Privileges) | Flags SeDebugPrivilege assigned without standard UAC elevation |
| Microsoft-Windows-Kernel-Audit | NtQuerySystemInformation calls | Detects SystemModuleInformation queries from unexpected integrity levels |
Behavioral Indicators
- Rapid creation and teardown of AFD socket notification objects across multiple threads (race attempts against
AfdNotifyPostEvents/AfdCleanupNotify) - Named pipe spray (
FSCTL_PIPE_INTERNAL_WRITE) targeting 0x70-byte NonPagedPoolNx allocations immediately after AFD notification cleanup - Low/Medium IL process successfully calling
NtQuerySystemInformation(SystemModuleInformation)— indicatesSepMediumDaclSdcorruption SeDebugPrivilegein a token not launched via Run As Administrator or UAC- Parent process spoofing via
PROC_THREAD_ATTRIBUTE_PARENT_PROCESStargeting a SYSTEM process (winlogon.exe,lsass.exe)
Techniques Used
| Technique | KernelSight Page |
|---|---|
| Pool Spray (NPNX via named pipes) | Pool Spray / Feng Shui |
| Bit-Manipulation Primitive | Bit-Manipulation Primitives |
| ACL/SD Corruption (SepMediumDaclSd) | ACL / SD Manipulation |
| KASLR Bypass (WIL feature flag) | KASLR |
| Token Privilege Manipulation | Token Manipulation |