CVE-2023-28218 — afd.sys CMSGBuffer Integer Overflow Heap Corruption

Last updated: 2026-04-28 Component: afd.sys (Ancillary Function Driver for WinSock) Bug Class: Integer overflow → heap-based buffer overflow (paged pool) Patch: April 2023 Patch Tuesday Exploited ITW: No public attribution Discoverer: Frontier Squad (Theori) — disclosed at Hexacon 2023 Related: Integer Overflows, Pool Internals, Primitives Tags: afd, heap-overflow, integer-overflow, kernel-mode, lpe, winsock


Summary

afd.sys mis-aligns a user-controlled chunk size in AfdCopyCMSGBuffer. A 32-bit integer overflow during the alignment lets the size shrink to zero, bypassing the subsequent bounds check and producing a wild paged-pool write driven by user-controlled data. Frontier Squad chained this with an _IO_COMPLETION_CONTEXT heap spray to land an arbitrary decrement, then flipped KTHREAD.PreviousMode to gain kernel R/W.

afd.sys IOCTL surface remains one of the most reachable code paths from a low-IL process; this CVE is one of several in the post-2023 wave (see also CVE-2025-60719 and the broader afd.sys / WinSock attack surface).


Root cause

In AfdCopyCMSGBuffer, the driver aligns a user-supplied kernelChunkSize to 8 bytes:

alignedChunkSize = (kernelChunkSize + 7) & 0xFFFFFFF8;

For values in [0xFFFFFFF5 .. 0xFFFFFFFB], the addition wraps the 32-bit register and alignedChunkSize becomes 0. The downstream bounds check passes against kernelBufferSize, then a memcpy-class operation copies user data using the unaligned original size — paged-pool overflow with attacker-controlled length and data.

A second wrinkle is a double-fetch between AfdComputeCMSGLength (the size pre-validation) and AfdCopyCMSGBuffer (the actual copy). A racing thread flips chunk-size values between the two calls so a benign-looking computation is followed by an overflowing copy.

The lack of SMAP enforcement is leveraged secondarily: by mapping a guard page just past the user payload, the wild copy faults on the user side and stops at a controlled boundary, producing a precise overwrite instead of a system crash.


Exploitation

  1. Trigger. AfdSendMessage IOCTL 0x120D3 with a crafted control-message buffer; race a second thread to flip the cmsg_len field between AfdComputeCMSGLength and AfdCopyCMSGBuffer.
  2. Spray. _IO_COMPLETION_CONTEXT objects (0x18 bytes each) via NtSetInformationFile(FileCompletionInformation) to surround the vulnerable allocation in paged pool.
  3. Overwrite the Port member of an adjacent _IO_COMPLETION_CONTEXT.
  4. Arbitrary decrement. Trigger IopReplaceCompletionPort via FileReplaceCompletionInformation; the path drops a reference on the now-attacker-controlled pointer through ObfDereferenceObjectWithTag. Each call decrements an arbitrary kernel address by one.
  5. PreviousMode flip. Decrement KTHREAD + 0x232 (PreviousMode) from 1 (UserMode) to 0 (KernelMode). All subsequent Nt* calls treat the calling thread as kernel-mode → arbitrary R/W via NtReadVirtualMemory / NtWriteVirtualMemory.
  6. Token steal as usual.

The patch swaps in safe-arithmetic wrappers (RtlULongSub, RtlSizeTAdd, RtlSizeTAlignUp, RtlSizeTSub) and gates the affected paths.


Detection

  • afd.sys IOCTL 0x120D3 with control-message lengths near 0xFFFFFFFx.
  • _IO_COMPLETION_CONTEXT allocation patterns adjacent to afd.sys allocations.
  • Process whose PreviousMode byte transitions from 1 to 0 outside a syscall boundary (rare and a strong indicator across many afd.sys / kernel UAFs).

References