Kernel UAF

Kernel UAF

TL;DR: A kernel object is freed but a dangling reference is still used; reclaim the freed slot with attacker-shaped bytes so the next field read, call, or write follows attacker control.

What it is

Use-after-free in kernel mode follows the user-mode pattern but with kernel-specific reclaim mechanics and far harsher mitigations. A driver decrements a refcount one too few times, doesn’t NULL a cached pointer after free, or races a free against a use across two CPUs; the freed pool chunk is then reused by attacker-driven allocations, and the next dereference of the dangling pointer reads attacker bytes.

Preconditions / where it applies

  • Bug exists: missing/incorrect refcount, missing locking, error path that frees without invalidating pointers, race between cleanup and IRP-completion paths.
  • Spray surface available: a sprayable kernel object of the same pool tag/size class.
  • The dangling pointer is used for a field that the attacker cares about: function pointer, length, data pointer, or any value passed to a privileged sink.

Technique

1. Find the bug. Look for ObReferenceObject/ObDereferenceObject mismatches, error returns after ExFreePoolWithTag that don’t clear the global handle table entry, IRP cancel/completion races.

2. Trigger free. Drive the driver into the path that frees the object while another reference path still holds a pointer.

3. Reclaim with controlled bytes. Choose a sprayable kernel object of the same allocation size and (ideally) same pool tag:

  • NtAllocateReserveObject reserve objects on older builds.
  • Named-pipe attribute values on 10/11.
  • Tagged WNF state data (NtUpdateWnfStateData).
  • _PIPE_QUEUE_ENTRY for variable-size brackets.

Spray enough copies that the freed slot is reclaimed by attacker bytes before the dangling use.

4. Use. When the driver dereferences the dangling pointer it now reads attacker-shaped data. Useful field choices:

  • Function-pointer slot → indirect call (constrained by cfg-cet-kernel).
  • Length slot → OOB read/write inside the using function (arbitrary-read-write-primitives).
  • Lock/state field → drive the routine into an unintended branch.

5. Stabilise. Repair the object so subsequent uses (or final free) don’t bug-check; restore the original refcount field, and make sure your reclaim object’s destructor is benign.

Race UAFs. If the bug is a free/use race across CPUs, pin two threads to different cores, prime one to repeatedly free, and the other to call the using IOCTL. Sometimes a single-CPU UAF requires nesting an APC or a callback to interleave free and use within the same logical thread.

Detection and defence

  • Driver Verifier with Special Pool catches many UAFs by leaving freed memory marked no-access.
  • Pool isolation, freelist hardening, and randomised slab placement in modern Windows degrade reclaim reliability.
  • HVCI + kCFI/kCET (cfg-cet-kernel) reduces what a hijacked vtable can reach.
  • KASAN-equivalents (KASAN on Linux; Driver Verifier “Force IRQL checking” on Windows) flag use-after-free at test time.

References

See also: type-confusion-kernel, double-fetch, arbitrary-read-write-primitives.