Virtual Memory & Page Tables
The virtual memory subsystem manages translation between virtual addresses (used by code) and physical addresses (actual RAM). Page tables are the data structure holding this mapping. Bugs in the VM subsystem lead to physical read/write β the most powerful primitive.
Why You Need to Understand VM
- Physical UAF (PUAF) exploits target bugs in the VM subsystem
- Page table manipulation is how to bypass PPL/SPTM
- physrw (physical read/write) is achieved through dangling page table entries
- KASLR relies on VM β understanding VM = understanding how to leak the kernel base
- pmap code is a target for many kernel bugs
ARM64 Address Translation
Translation Table Walk
Virtual Address (48-bit):
ββββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββββββββ
β L1 idx β L2 idx β L3 idx β Page β Page offset β
β [47:39]β [38:30]β [29:25]β [24:14]β [13:0] β
β 9 bits β 9 bits β 5 bits β 11 bitsβ 14 bits β
ββββββββββ΄βββββββββ΄βββββββββ΄βββββββββ΄βββββββββββββββ
(iOS uses 16KB pages β offset = 14 bits)
Translation:
TTBR1_EL1 (kernel) or TTBR0_EL1 (user)
β
βΌ
L1 Table ββ[L1 idx]βββ L1 Entry β L2 Table base
β
βΌ
L2 Table ββ[L2 idx]βββ L2 Entry β L3 Table base
β
βΌ
L3 Table ββ[L3 idx]βββ L3 Entry
β
βΌ
Physical Page
+ offset
β
βΌ
Physical Address
Page Table Entry (PTE) Format
L3 Page Table Entry (arm64, 16KB granule):
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 63 59β58 55β54 52β51 14β13 12β11 10β9 8β...β
β ignored β SW β res β Output Addr β res β AP β SH β...β
β by HW β use β β (phys page) β β β β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ...β7 6β5β4 2β1β0β
β β resβ βMAIRβ βVβ
β β β βidx β βaβ
β β β β β βlβ
β β β β β βiβ
β β β β β βdβ
βββββββββββββββββββββββ
Key fields:
Bit 0 (Valid) : 1 = entry valid, 0 = fault on access
Bits 4:2 (AttrIdx): Memory attribute index (MAIR register)
Bits 7:6 (AP) : Access Permission
00 = EL1 R/W, EL0 no access
01 = EL1 R/W, EL0 R/W
10 = EL1 R/O, EL0 no access
11 = EL1 R/O, EL0 R/O
Bits 9:8 (SH) : Shareability (Inner/Outer/None)
Bit 10 (AF) : Access Flag (set by HW on first access)
Bit 54 (XN) : Execute Never (EL1)
Bit 53 (PXN) : Privileged Execute Never
Bits 51:14 : Physical address of page (shifted)
Address Permissions Summary
| AP[7:6] | EL1 (kernel) | EL0 (user) |
|---|---|---|
| 00 | Read/Write | No access |
| 01 | Read/Write | Read/Write |
| 10 | Read-only | No access |
| 11 | Read-only | Read-only |
XNU Virtual Memory Subsystem
VM Map
// vm_map = address space representation
struct vm_map {
struct vm_map_header hdr; // Sorted list of vm_map_entry
pmap_t pmap; // Machine-dependent page tables
vm_map_size_t size; // Virtual size
// ...
};
// vm_map_entry = one contiguous mapping
struct vm_map_entry {
struct vm_map_entry *prev, *next; // Doubly-linked list
vm_map_offset_t vme_start; // Start virtual address
vm_map_offset_t vme_end; // End virtual address
vm_prot_t protection; // Current protection
vm_prot_t max_protection; // Maximum allowed protection
// ...
union {
vm_object_t vme_object; // Backing object (file, anonymous)
};
};
Pmap (Physical Map)
// pmap = machine-dependent page table management
struct pmap {
tt_entry_t *tte; // Top-level page table pointer
uint64_t asid; // Address Space ID (for TLB)
// ...
};
Key pmap functions:
// Create mapping: virtual β physical
pmap_enter_options(pmap, va, pa, prot, fault_type, flags, options);
// Remove mapping
pmap_remove(pmap, start, end);
// Change protection
pmap_protect(pmap, start, end, prot);
// Query mapping
pmap_find_phys(pmap, va); // Returns physical page number
TLB (Translation Lookaside Buffer)
TLB = cache for page table entries. The CPU does not walk page tables on every memory access β it checks the TLB first.
CPU needs virtual β physical translation
βββ Check TLB (fast path)
β βββ TLB hit: use cached translation
β βββ TLB miss: walk page tables (slow path)
β βββ Insert result into TLB
βββ Access physical memory
TLB invalidation (TLBI instruction) is needed when:
- Page table entries are changed
- Context switch (ASID change)
Bug pattern: Kernel changes a PTE but forgets to invalidate TLB β stale translation is still used β security violation.
Physical Use-After-Free (PUAF)
Concept
1. Process A maps virtual address VA β physical page P
2. Bug: kernel frees physical page P but DOES NOT remove the mapping from process A's page tables
3. Kernel reallocates physical page P for another purpose (kernel object, page table, ...)
4. Process A still reads/writes P through VA β reads/writes kernel data!
PUAF β physrw Escalation
If page P becomes a Level 3 page table:
βββ Process A writing to P = modifying page table entries
βββ Create a new PTE pointing to ANY physical address
βββ Map all of DRAM into the address space
βββ Full physical read/write!
If page P becomes a kernel object:
βββ Process A writing to P = corrupting a kernel object
βββ Escalate to kread/kwrite
Defenses (iOS 17+ SPTM)
SPTM ensures:
- Physical pages track their "type" (user, kernel, page table, ...)
- User pages CANNOT be reallocated as kernel pages until next boot
- Page table modifications MUST go through SPTM (EL2)
β PUAF from user β kernel is blocked
β PUAF user β user still possible (less useful)
Page Lifecycle
Free page pool
β
ββ vm_page_grab() β Allocate physical page
β ββ pmap_enter() β Map into address space
β
ββ pmap_remove() β Remove mapping
β ββ vm_page_free() β Return to free pool
β
ββ PUAF bug: vm_page_free() WITHOUT pmap_remove()
β Page returned to free pool but still mapped!
Kernel Memory Regions
βββββββββββββββββββββββββββββββββββββββββββ
β Kernel Virtual Address Space β
β β
β βββββββββββββββββββββββ β
β β Kernelcache β Fixed mapping β
β β (__TEXT, __DATA) β KASLR slide β
β βββββββββββββββββββββββ β
β βββββββββββββββββββββββ β
β β Zone maps β Dynamic β
β β (kalloc, ipc, ...) β β
β βββββββββββββββββββββββ β
β βββββββββββββββββββββββ β
β β Kernel map β General β
β β (large allocations) β purpose β
β βββββββββββββββββββββββ β
β βββββββββββββββββββββββ β
β β MMIO regions β Device β
β β (hardware registers)β mappings β
β βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ
Resources
- XNU source:
osfmk/vm/vm_map.c,osfmk/arm64/pmap.c - Apple β Memory and Virtual Memory
- Project Zero β The Core of Apple is PPL
- Alfie CG β Kernel Exploit Guide (VM exploitation sections)
- ARM Architecture Reference Manual β Chapter D5 (Address Translation)
Exercises
- Manual page table walk: Given a virtual address, TTBR value, and page table dumps β compute the physical address by hand
- Read pmap.c: Trace
pmap_enter_options()β understand what each parameter does - Analyze a PUAF exploit: Read the kfd source code, trace the PUAF primitive (smith/landa/physpuppet)
- Draw a memory map: For a running process, use
vmmap(macOS) to draw the full address space layout - Compute PTE values: Given a physical address + desired permissions, construct the PTE value by hand