CVE-2025-13032 is a double fetch in Avast’s aswSnx kernel driver. A user-supplied string length is read once to size a paged-pool allocation and read again to drive the copy into it. A second thread toggling that field between a small and a large value wins the race, and the driver writes more bytes than it allocated. SAFA’s second post carries that controlled overflow through to SYSTEM on an up-to-date Windows 11 machine.

The choice of corruption target is the interesting part. It is the RegBuffers array inside the I/O Ring object:

  • The IORing object lives in non-paged pool, but its RegBuffers array of buffer-entry pointers is allocated in paged pool — the same pool the overflow lands in
  • The array is sized by the caller: N registered buffers allocates N x 8 bytes, so it can be parked in exactly the size bucket the overflowing chunk needs
  • One corrupted pointer is enough. Each entry’s Address is validated at registration time and then trusted for every later I/O operation
  • Windows does not implement SMAP, so an entry can be redirected to a fake buffer entry in userland memory and the kernel will happily dereference it

From there the chain is short:

  • Spray RegBuffers allocations, free a subset to punch holes in the right size class, then land the overflowing chunk next to a live entry
  • IoRingReadFile writes into the Address field, which is arbitrary kernel write; IoRingWriteFile reads from it, which is arbitrary kernel read
  • KASLR falls without a second bug: an IORing operation makes the kernel attach an MDL to the userland buffer, and since the fake entry sits in the process’s own memory its Mdl pointer can be read directly. One arbitrary read of MDL->Process yields the process’s _EPROCESS
  • Escalation is the classic token swap — walk the EPROCESS list, read SYSTEM’s token, write it into the current process

Teardown is where the real work is:

  • The overflow clobbers ProcessBilled in the pool chunk header, which bugchecks on free. Recover ExpPoolQuotaCookie from an uncorrupted IORing chunk, invert the obfuscation formula, and write the correct value back
  • Bump ReferenceCount on the fake entry so the kernel never releases a userland pointer
  • Release the RegBuffers registration rather than the MDL directly — MDL cleanup is a consequence of tearing down the registration, not a standalone fix

The design lesson is object selection, and the authors state their criteria plainly: same pool as the overflow, caller-controlled allocation size, a single-pointer corruption sufficient for the primitive, and a technique already demonstrated in public research. That is a reusable template for evaluating a heap target in any kernel — and it explains why fixing one driver’s arithmetic does not close the class.

The closing note matters more than the exploit. Newer Windows kernels use user-mode accessors that re-validate user buffers on every access, which removes the double fetch’s leverage instead of patching a single length check. The bug was in the validation that ran too early, not in the copy that ran too late.