Kernel type confusion

Kernel type confusion

TL;DR: A kernel object is interpreted as a different type than it was allocated — fields meant as data are read as pointers (or vice versa), giving direct primitives without a memory-corruption “smash” step.

What it is

A driver routes an IRP, IOCTL, or system call through a tag/handle/dispatcher field, then casts the object to the type implied by that field without verifying it. If the attacker can influence the tag, the kernel uses the object’s bytes as the wrong structure — overlapping fields that the attacker controls onto fields the kernel will dereference, call through, or write to.

Preconditions / where it applies

  • A kernel allocator returns objects from the same pool/slab to multiple type users (common in drivers that share a single IOCTL dispatch path).
  • An “object type” field in the structure (enum, function-pointer index, magic) is attacker-controlled or attacker-influenceable.
  • Field-layout overlap places a pointer/length the kernel uses in slot N of type A on top of attacker-writable bytes in slot N of type B.

Technique

Pattern A — IOCTL dispatch confusion.

  1. Driver exposes one allocation routine returning OBJ_HEADER { ULONG type; ... }.
  2. Allocate with type T1 (small object with attacker-writable header).
  3. Submit an IOCTL whose handler reads obj->type, picks a virtual-function pointer table, and calls vtable[op](obj).
  4. Flip type to T2 between allocation and the call so a chosen handler runs with an object whose payload bytes are attacker bytes — handler treats them as length, ptr, etc.

Pattern B — sharing a pool allocation.

  1. Free the original T1 object; spray T2-shaped buffers of the same size so the pool returns the attacker-controlled bytes for a later T1 lookup.
  2. A stale T1 pointer still in use reads “T2-shaped” bytes through “T1-shaped” code → confusion without changing a tag.

Primitive that usually follows.

  • Read a kernel pointer from a slot the original type intended as data → leak.
  • Write attacker bytes to a kernel address from a slot the original type intended as a buffer pointer → AW; see arbitrary-read-write-primitives.
  • Indirect call through an overlapping function pointer → constrained by cfg-cet-kernel.

Real-world flavour. Win32k and graphics-driver bugs frequently rely on this shape: GDI objects and DirectX surface descriptors share allocators and have tag fields that, when forged, change interpretation mid-handler.

Detection and defence

  • Tagged unions with magic checks on every cast; helper macros that abort on mismatch.
  • KMDF/UMDF object types are reference-counted and tag-checked by the framework; legacy WDM drivers are the usual offenders.
  • Driver Verifier (/specrand and pool tracking flags) catches some mis-cast pool frees.
  • Compile drivers with kCFI (cfg-cet-kernel) so indirect calls via type-confused vtables fail the tag check.

References

See also: use-after-free-kernel, double-fetch, arbitrary-read-write-primitives.