develicit
← Back to posts

Heap Feng Shui — Precisely Shaping Heap Memory Layout for Exploitation

·15 min read

TL;DR — Heap feng shui is the meta-technique of coercing a non-deterministic heap allocator into the order, adjacency, and reuse pattern the attacker wants, turning a corruption primitive like UAF/overflow into reliable code execution. It's a core weapon in both userspace (Scudo, libmalloc) and the kernel (SLUB, kalloc), and the starting point for any study of modern mitigations (MTE, PAC, freelist randomization, SLAB_VIRTUAL, kalloc_type).

1. The core idea

To exploit a heap bug (UAF, heap overflow, double-free, type confusion, …), a specific object has to land at a specific location in memory. But a normal heap allocator is non-deterministic, for several reasons:

  • Freelist fragmentation — the distribution of free chunks depends on the prior alloc/free history
  • Thread caches (tcache, per-CPU cache, magazine) — per-thread/per-CPU LIFO caches split same-size objects across locations
  • Randomization mitigations — Scudo's chunk-header checksum, SLUB's randomized freelist, libmalloc's nano/scalable zone randomization
  • Allocator-internal policy — size-class rounding, region/slab/zone boundaries, large-allocation branches

Heap feng shui tames that non-determinism with a crafted sequence of allocations and frees, driving the heap into a deterministic state. The name comes from feng shui (風水), the art of spatial arrangement, and was coined in Alexander Sotirov's 2007 Black Hat USA paper "Heap Feng Shui in JavaScript."

2. History and evolution

YearEventSignificance
1997Solar Designer — return-into-libcFirst control-flow hijack from stack corruption
2007Sotirov — Heap Feng Shui in JavaScriptNamed IE browser heap shaping, released heaplib.js
2010Chen — Pool Feng Shui (Windows kernel)Extended the idea to the kernel heap
2013Tarjei Mandt — iOS kernel heap feng shuiFirst study of shaping the iOS kalloc / zone allocator
2016VUSec — Flip Feng Shui / DrammerRowhammer + heap placement to exploit a hardware fault
2020–2021Scudo default (Android 11, 2020) · kalloc_type (iOS 15, 2021 → expanded in iOS 16/Ventura)Sharply raised heap-exploit cost on both userspace and kernel
2021Maze (USENIX Security) — automated heap feng shuiSymbolic-execution-based automatic layout synthesis
2023CVE-2023-20938 (Android Binder UAF), CVE-2023-32434 (iOS kernel)SLUB/zone feng shui used in the wild on both mobile platforms
2024WOOT '24 — two Scudo bypassesEven a hardened allocator has structural weaknesses
2024Dirty Pagedirectory (CVE-2024-1086, nf_tables) — Flipping PagesEstablishes page-level feng shui: reuse a buddy-returned page as a PTE
2025CROSS-X (ACM CCS) — generalized, reliable cross-cacheStable, general cross-cache automation (the SLUBStick lineage)
2025Apple MIE (iPhone 17 / A19, EMTE-based)Always-on memory tagging across the kernel + 70+ userland processes — targets feng shui's "silent corruption" premise
2025CVE-2025-0072 (Mali GPU) — MTE bypass · CVE-2025-38352 in-the-wild LPEEven hardware tagging / strong mitigations can be bypassed — the mobile arms race continues
  1. 2007
    Heap Feng Shui in JS
    Sotirov (Black Hat) — shaped the IE heap, released heaplib.js. Named the technique.
  2. 2010
    Pool Feng Shui
    Extended to the Windows kernel pool (Chen).
  3. 2013
    iOS kernel feng shui
    Mandt — first study of XNU kalloc / zone shaping.
  4. 2016
    Drammer / Flip Feng Shui
    VUSec — Rowhammer bit flips + page placement for mobile root.
  5. 2020
    Scudo · kalloc_type
    Android 11 Scudo (2020) · iOS 15 kalloc_type (2021) — sharply raised exploit cost on both sides.
  6. 2021
    Maze (USENIX)
    Symbolic execution auto-synthesizes the heap layout sequence.
  7. 2023
    CVE-2023-20938 / -32434
    Android Binder UAF · iOS Triangulation — feng shui in the wild on both platforms.
  8. 2024
    Two Scudo bypasses (WOOT)
    Proved even a hardened allocator has structural weaknesses.
  9. 2024
    Dirty Pagedirectory
    CVE-2024-1086 (nf_tables) — reuse a buddy-returned page as a PTE: page-level feng shui.
  10. 2025
    CROSS-X (CCS)
    Generalized, reliable cross-cache (mostly 99%+, 94% for pipe_inode_info). The SLUBStick lineage.
  11. 2025
    Apple MIE (EMTE)
    iPhone 17/A19 — always-on EMTE across the kernel + 70+ userland processes. (Android MTE was bypassed by CVE-2025-0072 — a separate platform.)

Left stripe color marks the era — origins (userland) → kernel → mobile in the wild → hardware mitigations → automation.

Fig 1. A lineage of heap feng shui — from browser heap shaping to kernel and mobile, with hardware mitigations raising the cost.

Recent direction (2024–2026) — it boils down to three threads.

  1. Page-level feng shui — beyond slab/zone, the DirtyPagetable / Dirty Pagedirectory (CVE-2024-1086) family reuses a buddy-returned page as a page table (PTE) (arbitrary PTE manipulation → physical-memory R/W → kernel patching), now a powerful go-to path. That said, CROSS-X notes page-level reuse is relatively rare and may require extra privileges/conditions — it isn't the endpoint of every cross-cache.
  2. Cross-cache generalization — SLUBStick (99%+ success) and CROSS-X (ACM CCS '25) pushed cross-cache reliability from the old ~40% up to production level.
  3. The hardware-tagging arms race — Apple MIE (2025, iPhone 17/A19) applies EMTE always-on across the kernel + 70+ userland processes, attacking feng shui's "silent corruption" premise head-on. Yet the same year's Mali-GPU CVE-2025-0072 bypassed MTE — defenses raise the cost but don't end the game.

3. How it works

The overall flow runs in seven stages.

Placement control
1Defragmentation

Fill holes to compact the heap

2Hole creation

Free a target-sized slot deliberately

3Victim alloc

Place vulnerable object in the slot

4Adjacent placement

Attacker-controlled object next to it

Primitive use
5Trigger

Fire UAF / overflow / double-free

6Corruption

Overwrite metadata · vtable · fn ptr

7Pwned

Arbitrary R/W or RIP control

Steps 1–4 control placement (turning the heap deterministic); steps 5–7 use the primitive (the actual exploit).

Fig 2. The heap-feng-shui pipeline — seven stages from corruption primitive to reliable code execution.
  1. Defragmentation — fill holes to compact the heap
  2. Hole creation — deliberately free a target-sized slot
  3. Victim allocation — place the vulnerable object at the exact spot
  4. Adjacent placement — place an attacker-controlled object in the neighboring slot
  5. Trigger primitive — fire the UAF / overflow / double-free
  6. Corruption — overwrite metadata / vtable / function pointer
  7. Arbitrary R/W or RIP control — read/write anywhere or hijack control flow

3.1 The four core building blocks

  1. Massage — mass-allocate same-size objects to drain the freelist and force a fresh slab/region/zone
  2. Punch hole — free exactly the intended slot so the next allocation lands there
  3. Pin victim — control timing and size so the vulnerable object falls into the hole
  4. Place attacker chunk adjacent — put attacker-controlled, payload-ready data in the neighboring slot
0. Initial state
Heap is fragmented; hole positions unpredictable
F
A
F
A
A
F
A
F
A
F
1. Massage
Mass-allocate same-size chunks → drain freelist, force new slab
A
A
A
A
A
A
A
A
A
A
2. Punch hole
Free exactly the target slot so the next allocation lands there
A
A
A
A
H
A
A
A
A
A
3. Pin victim
Place the vulnerable object in the hole
A
A
A
A
V
A
A
A
A
A
4. Place attacker
Place attacker-controlled chunks adjacent to the victim
A
A
A
X
V
X
A
A
A
A
F
free
A
other alloc
H
punched hole
V
victim
X
attacker-controlled

Real heaps have hundreds to thousands of slots; this only highlights five.

Fig 3. Heap state evolution — how the same slot row transforms across Massage → Hole → Victim → Attacker.

4. Heap Spraying vs Heap Feng Shui

Heap SprayingHeap Feng Shui
GoalPaint shellcode/ROP gadgets across a wide regionPlace a specific object in a specific slot exactly
PrecisionLow (probabilistic, guess the address)Very high (deterministic, per slot)
UseStabilize the jump-target addressConvert a corruption primitive into code execution
Typical toolingNOP sled + repeated shellcodeSize-class matching + free-pattern control

Real exploits usually combine the two — feng shui builds the corruption, then a spray stabilizes the RIP jump target.

Heap Spraying
Paint payloads across memory so any jump lands on one
  • ·Probabilistic (guess address)
  • ·Low precision
  • ·NOP sled + repeated shellcode
  • ·Stabilize the jump target
Heap Feng Shui
Place exactly one slot next to the victim, deterministically
victimattacker
  • ·Deterministic (per slot)
  • ·Very high precision
  • ·Size-class match + free-pattern control
  • ·Primitive → code execution

Real exploits combine the two — feng shui builds the corruption, spraying stabilizes the jump target.

Fig 4. Heap Spraying vs Heap Feng Shui — blanket the heap to raise the odds vs deterministically occupy a single slot.

5. Allocators by platform

Android
AOSP / bionic / Linux kernel
Page-level protection
SLAB_VIRTUAL (experimental)
Isolates slab pages into a virtual pool
pkey · eBPF lockdown
Tighter permission policy
Kernel allocator
Linux SLUB
kmalloc-* size-class caches
FREELIST_HARDENED · RANDOM
XOR-ed pointers + randomized slot order
RANDOM_KMALLOC_CACHES (6.6+)
16 sibling caches per size (optional)
Userspace allocator
Scudo (Android 11+)
Primary region + Secondary mmap
cookie · quarantine
CRC32 checksum aborts on corruption
MTE (Pixel 8+)
ARMv8.5 HW tags · per-process opt-in
iOS
Darwin / XNU
Page-level protection
PPL → SPTM/TXM (A15+)
Page tables themselves are guarded by a separate monitor
MIE · EMTE (A19+, 2025)
Always-on Enhanced MTE
PAC-CFI
Signed function pointers and return addresses
Kernel allocator
XNU zalloc / kalloc
Hundreds of named zones
kalloc_type (iOS 15+)
Type signatures placed into randomized buckets
Zone require · GC
Zone-id check on free; reclaim empty pages
Userspace allocator
libmalloc
nano / tiny / small / large zones
PAC freelist (arm64e)
Freelist ptrs signed with IB key
Magazine secret · cookie
XOR-protected chunk metadata

Android is a ‘same-size cache' SLUB game. iOS, post-kalloc_type, is a ‘randomized bucket' game — same-size objects in different type buckets rarely share slots (probabilistic).

Fig 5. Android vs iOS heap stack — mitigations across userspace → kernel → page protection.

5.1 At a glance: Android vs iOS

Android (AOSP)iOS (Darwin / XNU)
Userspace allocatorScudo (Android 11+) — bionic libclibmalloc — nano / tiny / small / large zones
Kernel allocatorLinux SLUB (kmalloc-*)XNU zalloc / kalloc (zone-based)
Kernel object isolationGeneral caches + some dedicated caches (SLAB_ACCOUNT, kvmalloc)kalloc_type (iOS 15+, randomized bucketed type isolation)
Freelist protectionCONFIG_SLAB_FREELIST_HARDENED (XOR), CONFIG_SLAB_FREELIST_RANDOMZone freelist uses PAC-signed pointers (arm64e), zone secret cookie
Memory taggingMTE (ARMv8.5+, Pixel 8 onward — per-app/process opt-in, config-dependent)PAC (arm64e, A12+) + kASLR · zone redzone
CFILLVM CFI (kernel + userspace)PAC-based CFI (signed function pointers, return addresses)
Page-level protectionSLAB_VIRTUAL (experimental), pkey, eBPF lockdownPPL (Page Protection Layer) → SPTM/TXM (A15+) — page tables guarded by a separate monitor
Typical sandbox-escape targetssystem_server, mediaserver, binder, GPU driverlaunchd, MIG servers, IOKit user clients, AppleAVD/IOMobileFrameBuffer
Typical spray primitivesmsg_msg, pipe_buffer, sk_buff, Binder transaction buffermach_msg OOL descriptors, OSData, IOSurface, ipc_kmsg

5.2 Android Scudo (userspace)

From Android 11, the default userspace allocator changed to Scudo.

  • Primary allocator: region-based, using a size-class map that varies by version/config (on Android 14, the WOOT '24 analysis lists primary class IDs 1–32 — "exactly 16" is version-dependent). Each region's freelist runs through per-thread caches (TransferBatch) and slot randomization, so it's more involved than a plain LIFO.
  • Secondary allocator: large allocations (64 KB+ by default) get their own guard-page-wrapped mmap region.
  • Chunk header: header integrity is verified with a CRC32-based checksum incorporating a global secret/cookie — abort immediately on corruption.
  • Quarantine: freed chunks are briefly quarantined to block immediate UAF reuse.
  • MTE integration: on tag-enabled devices, a tag mismatch raises SIGSEGV.

WOOT '24 bypass summary

Technique 1 — abuse Scudo's internal cache mechanism to steer a chunk near an arbitrary address (since patched).

Technique 2 — leverage the secondary allocator's large-chunk handling — a structural issue, hard to patch.

5.3 Linux SLUB (Android kernel)

The Android kernel uses Linux SLUB.

  • General allocations go to kmalloc-* caches (8 B – 8 KB, powers of two plus the 96 B/192 B exceptions; recently kmalloc-cg-* is split out).
  • Only objects in the same size-class cache sit adjacent → the key is finding a useful kernel object of the same size as the target.
  • A three-level structure: per-CPU active slab + partial list + node partial.
  • CONFIG_SLAB_FREELIST_HARDENED — obfuscates freelist pointers as ptr XOR random XOR &ptr.
  • CONFIG_SLAB_FREELIST_RANDOM — randomizes slot order within a slab (once, at slab creation).
  • CONFIG_RANDOM_KMALLOC_CACHES — hash-picks one of 16 sibling caches per size (Linux 6.6+, optional — may be off on GKI/specific devices).
  • SLAB_VIRTUAL (experimental) — isolates slab pages into a virtual-address-only pool to block cross-cache reuse.

5.4 iOS libmalloc (userspace)

The default userspace allocator on iOS/macOS.

  • Nanozone: dedicated to tiny allocations ≤256 B. 16 B granularity, per-CPU magazine structure.
  • Scalable zone (tiny/small/large):
    • tiny: ≤1 KB, 16 B quantization
    • small: ≤15 KB (or 32 KB on 16 K pages), 512 B quantization
    • large: above that, direct vm_allocate
  • Pointer Authentication (arm64e): zone freelist pointers are PAC-signed with the IB key; forgery faults.
  • Magazine secret / cookie: XOR protection on chunk metadata.
  • Guard malloc / libgmalloc: a debugging mode that isolates every allocation to a page (disabled in production).

5.5 XNU zalloc / kalloc_type (iOS kernel)

The iOS kernel heap is structured differently from SLUB, so the feng shui strategy differs too.

  • Zone: a free list pooling allocations of the same element size and the same "purpose." Hundreds of named zones exist (kalloc.*, ipc.ports, OSData, …).
  • kalloc heap separation (iOS 14+): purpose-specific heaps like KHEAP_DEFAULT, KHEAP_DATA_BUFFERS, KHEAP_KEXT keep data and pointers out of the same zone.
  • kalloc_type (iOS 15+): bucketed type isolation that places type signatures into randomized buckets. Multiple types can share a bucket, but it sharply shrinks the pool of UAF realloc candidates (types that could land in the same slot), making type-confusion exploitation much harder — it does not hard-block all type confusion.
  • Zone require / zone_id check: verifies zone identity on free.
  • Zone GC: reclaims empty pages, making cross-zone reuse harder.
  • PPL → SPTM/TXM (A15+): a separate monitor validates page-table entries themselves, so an R/W gadget can't patch kernel text.
  • Memory Integrity Enforcement (MIE) · Enhanced MTE (A19/M5+, 2025): always-on EMTE that blocks memory-safety violations in hardware — the newest defense, breaking feng shui's premise of "silent corruption."

Android SLUB vs iOS zalloc in one line

SLUB is "everything in a size class goes into one pool → a game of finding a useful same-size object." iOS, post-kalloc_type, is "same-size objects in different type buckets rarely mix (probabilistic, since bucketing is randomized) → a game of finding what's useful within the same bucket."

Linux SLUB — by size
kmalloc-1k: every 1KB object shares one cache regardless of type
kmalloc-1k
victim (1KB)
msg_msg
tty_struct
pipe_buffer

Place a same-size useful object next to the victim → feng shui works

iOS kalloc_type — by type
Even at 1KB, type signatures are split into randomized buckets
bucket A (type A…)
victim (type A)
type A obj
bucket B (type B…)
attacker (type B)
type B obj

Types in other buckets rarely reuse the same slot → shrinks realloc candidates (probabilistic, not a hard block)

victimattacker
Fig 6. SLUB (by size) vs kalloc_type (by type) — what shares a pool decides how hard feng shui is.

6. A catalog of useful kernel/IPC objects

Exploit reliability comes down to "what object can you place in the adjacent slot at the moment of corruption."

Android (Linux SLUB)

ObjectSize / cacheWhy it's useful
msg_msgkmalloc-64 ~ kmalloc-4k (size controlled via msgsnd)Both size and contents are user-controlled; easy to turn into an OOB read/write primitive
pipe_bufferkmalloc-1k (in groups of 16)pipe_buf_operations function pointer → RIP control (Dirty Pipe family)
sk_buff (skb->data)kmalloc-512 / 1k / 2k / 4k (traffic size)Arbitrary size/content spray, remotely triggerable over the network
tty_structkmalloc-1ktty_operations function pointer, often used for info leaks
shm_file_data, seq_operationskmalloc-32 / 64Function pointers in small caches
Binder transaction bufferArbitrary size (per-process mmap region)Android-specific, shares a cache with system_server

iOS (XNU zalloc)

ObjectZoneWhy it's useful
ipc_kmsgipc.kmsgsSize/content spray via mach_msg, pointer spray via OOL descriptors
OOL ports arrayipc.portsArbitrary-size send-right arrays — used to forge fake ports
OSData / OSStringkalloc.data.* (KHEAP_DATA_BUFFERS)Spray directly from userland via an IOKit user client, full content control
IOSurface propertieskalloc.* (various sizes)set/get from userland via IOSurfaceRoot → the classic R/W-primitive conversion
Mach voucheripc.vouchersPowerful when refcount manipulation is combined with a UAF
vm_map_entryVM map entriesVM-subsystem corruption → hijack page mappings

7. Cross-cache / cross-zone attacks

When there's no useful object you can place in the same cache/zone, recycle the slab/zone page itself into a different cache.

1Victim isolated

Victim trapped in a dedicated/cg cache — no useful object shares it.

victim's dedicated cache
2Free all slabs

Empty every slab page in that cache so the page returns to the buddy allocator.

buddy allocator (page pool)
3Re-allocate page

Race so another cache (e.g. msg_msg) grabs that same physical page.

attacker cache
4Overlap achieved

Attacker object overlays the victim's old memory — cross-cache UAF.

attacker cache

Reusing the recycled page as a page table (PTE) — DirtyPagetable / Dirty Pagedirectory (CVE-2024-1086) — is a powerful path (though page-level reuse is relatively rare and condition-dependent). Defenses: SLAB_VIRTUAL (virtual-pool isolation), kvmalloc split, page tagging. iOS makes cross-zone far harder with kalloc_type + zone GC + PAC/SPTM.

Fig 7. Cross-cache attack — when no useful object shares the victim's cache, recycle the slab page itself through the buddy allocator into another cache.
  • Android: to target a victim trapped in kmalloc-cg-* or a dedicated cache, free all the slab pages in that cache so they return to the buddy allocator, then induce another cache (e.g. msg_msg) to grab those pages.
    • Key tools: racing pcpu_alloc, kvfree, __free_pages_ok.
    • Defenses: SLAB_VIRTUAL, kvmalloc separation, page tagging.
  • iOS: since kalloc_type, only the same type tends to share a zone, so cross-zone is harder. Instead, "zone-level feng shui" research uses zone garbage collection to get a page reassigned to a different zone.
    • PAC and SPTM additionally block cross-zone fake-pointer attacks.

8. Real-world cases

CVE-2023-20938 — Android Binder UAF → Root

Google Android Offensive Security exploited a Binder UAF with SLUB feng shui to gain root from an untrusted app.

  • Affected GKI kernels 5.4 / 5.10. Patched in the 2023-02 / 2023-07 Security Bulletins.
  • Core: extend a binder_node UAF (kmalloc-128) via sendmsg spray and cross-cache reuse into eventpoll (epitem) objects (per the Google AndroidOffSec write-up).
Drammer — Rowhammer + Flip Feng Shui → Mobile Root

Rowhammer hardware bit flips + page-placement control to get root on Android from an unprivileged app.

  • Deterministic root with no software vulnerability — just a DRAM fault.
  • Works without memory deduplication — the result of using feng shui to secure the slot a PTE will land in within the page pool.
CVE-2023-32434 — iOS kernel integer overflow (Operation Triangulation)

The kernel-LPE stage of Kaspersky's disclosed "Operation Triangulation" chain. An integer overflow in XNU (the mach_make_memory_entry / vm_map family) yields physical-memory R/W, and a separate CVE-2023-38606 (undocumented MMIO registers) bypasses the hardware memory protection (PPL).

  • Patched in iOS 15.7.7 and 16.5.1.
  • Significance: shows that even under strong mitigations like PAC and PPL, a memory-safety bug can still drive a full exploit chain.
CVE-2021-30883 — IOMobileFrameBuffer (the LPE after FORCEDENTRY)

An IOMobileFrameBuffer heap overflow. Spray IOSurface properties into the adjacent zone to overwrite a function-pointer region and gain RIP control.

  • Used as an in-the-wild 0-day. Patched in iOS 15.0.2.

9. Modern defenses, organized

Massage
Hole
Adjacent
Trigger
Corruption
Exec
Freelist obfuscation
Slot-order randomization
Cache/zone isolation (kalloc_type)
Quarantine / GC
MTE / PAC · MIE/EMTE
CFI
PPL · SPTM/TXM
Full block
Partial
No impact

No single mitigation covers every stage — defense in depth is required.

Fig 8. Mitigation × attack-stage coverage — which defense blocks which step.
DefenseAndroidiOSEffect on feng shui
Freelist obfuscationSLAB_FREELIST_HARDENEDPAC-signed freelist (arm64e)Blocks injecting a fake free chunk
Slot-order randomizationSLAB_FREELIST_RANDOMzone slot randomizationAdjacency gets unpredictable — compensated by spray volume
Cache randomizationRANDOM_KMALLOC_CACHES (6.6+ optional)kalloc_type (randomized bucket)Probabilistically shrinks same-size matching candidates
Memory tagging / authenticationMTE (HW tag)PAC (HW signed pointer)Detects the UAF/overflow itself immediately (probabilistic/deterministic)
Page-permission monitorSLAB_VIRTUAL (experimental), pkeyPPL → SPTM/TXMAn R/W primitive can't patch kernel text
CFILLVM CFIPAC-CFICan't make an arbitrary jump by overwriting a vtable/fn ptr
Quarantine / GCScudo quarantinezone gc + zone requireBlocks immediate UAF reuse, forces a wider race window

10. Automation / research tooling

  • Maze (USENIX Security '21) — symbolic-execution-based automatic heap-layout synthesis. Input an exploit scenario → output an alloc/free sequence.
  • SLUBStick (USENIX Security '24) — an automation framework for Linux SLUB cross-cache attacks.
  • Syzkaller + KASAN — discover trigger sequences + track heap state.
  • kasld / kbase finder — automate KASLR-bypass info leaks.
  • On iOS: KTRR/SPTM bypass research, panic-log diffing — few public tools, so writing your own PoC is the norm.
  • MTE-aware fuzzing (HWASAN, scudo_standalone) — use tag mismatches as a corner case.

11. References

Foundations / history

Android / Linux SLUB

iOS / XNU


Comments