macOS kernel heap (zone allocator)

macOS kernel heap (zone allocator)

TL;DR: XNU’s kernel heap is a per-type zone allocator — recent versions add kalloc_type isolation so an attacker can no longer reliably reclaim a freed object of type A with an attacker-controlled type B; defeating that means finding a confused-deputy across the same zone or a kalloc_data buffer that touches the right pointers.

What it is

The XNU kernel uses a “zone allocator” — each commonly-allocated structure (ipc_port, task, vm_map_entry, OSData) gets its own zone with a fixed element size and pre-typed freelist. The classic kmem heap-spray that worked on older macOS (overflow into adjacent attacker-controlled buffer in the same generic kalloc bucket) is dead on modern XNU because:

  • kalloc_type isolation (macOS 12+, iOS 15+): the compiler emits kalloc_type(...) calls that route allocations to a zone keyed on the static type of the allocation site. OSData::initWithBytes lands in OSData zone; nothing else lives there.
  • Per-CPU caching layer (zalloc_caching): small allocations come from a per-CPU magazine.
  • PGZ (Probabilistic Guard malloc): randomly samples allocations into a guarded zone with red-zone pages between them; catches OOB writes probabilistically.
  • Zone freelist hardening: freelist pointers are XOR’d with a per-zone secret cookie, so a UAF-derived freelist write can’t trivially aim the next allocation.

Preconditions / where it applies

  • macOS / iOS kernel heap exploit. Older XNU (pre-Big Sur) is much easier — most public CTF write-ups assume that environment.
  • You already have a kernel UAF, double-free, or OOB-write primitive and need a reliable target object to corrupt.

Technique

Step 1 — Identify the zone. Use zprint (live, requires root) or static analysis (kalloc_type macro expansion in headers) to determine which zone the vulnerable object lives in. If it lives in a dedicated kalloc_type zone, you can only spray that exact type.

Step 2 — Find a useful zone-mate. The classic recipe: free the vulnerable object, reallocate the slot with an object of the same zone whose contents include a function pointer / vtable you can leak or hijack. Modern kalloc_type makes this hard — but:

  • Many small heap-allocated buffers route to kalloc_data (raw data, untyped). If your vulnerable object has a kalloc_data adjacency (sequential allocation), you can spray kalloc_data with attacker-controlled bytes via IOSurface property dictionaries, mach-message inline OOL ports, etc.
  • OSObject-derived classes sometimes share zones if their static sizes match (zone-binning fallback).

Step 3 — Spray. Common kernel sprays:

  • IOSurface OSData properties (IOSurface_setValue) — attacker bytes in kalloc_data zones. Size-controlled.
  • Mach messages with inline OOL descriptors — port arrays land in the ipc_kmsg zone but pointer descriptors give read/write primitives.
  • pipe() buffer kernel-side allocations — Linux trick that partly applies (pipe pages live in vm_object, not zone).
  • vm_map_entry flooding via mapping many regions; useful for adjacency on the VM map zone.

Step 4 — Bypass PGZ. PGZ samples ~1 in N — exact rate is platform-specific. Reliability strategy: allocate enough victim objects that the probability of all being PGZ-protected is negligible (e.g., 100 sprays).

Step 5 — Defeat the freelist cookie. You can’t forge a next-pointer without leaking the per-zone secret. Two paths:

  • Leak the cookie via a related infoleak (sometimes one bug gives both primitives).
  • Skip freelist corruption entirely — use a UAF where the same slot is allocated to an attacker-controlled type without needing to redirect the freelist.

Detection and defence

  • Apple-side: PGZ + kalloc_type are the moving target; new XNU releases tighten the type universe.
  • Researchers / exploiters: track XNU source releases (xnu/osfmk/kern/zalloc.c) — kalloc_type rules and zone definitions change every major release.
  • Build kernel with KASAN for development to catch heap bugs early.

References