A physical use-after-free is a type of bug that has been used to gain kernel read/write primitives in recent years. It is a powerful bug class because it is largely unaffected by modern XNU exploit mitigations.
Background
When you allocate memory (e.g. using mach_vm_allocate()
) you will be given a virtual memory address. Virtual memory is the memory management system used by XNU, wherein physical pages of memory are represented by virtual memory addresses, which are then translated upon a fault.
Virtual memory can give the illusion of one contiguous address space, while in reality, each physical page within a contiguous virtual memory region may be at completely different locations.
In order to translate each page of virtual memory, the kernel (or the PPL/SPTM on newer devices) manages page tables. These keep track of the corresponding physical address range for each virtual one. At the lowest level in the hierarchy (level 3 page tables), each page table entry represents a single page of memory. This means that mapping in exactly one page of virtual memory will take up a single page table entry in a level 3 page table.
On top of the page table system, there is the virtual memory (VM) layer of the kernel. This will keep track of all the allocations made by a process, so when you call mach_vm_allocate()
to allocate memory, the VM layer will know about this. Similarly, the VM layer will also know when you free an allocated region of memory.
Physical use-after-free
A physical use-after-free occurs when there is a discrepancy between the VM layer and the physical map (pmap) layer - wherein the VM layer frees some memory but the mapping is not fully removed from the associated page tables.
This means that the VM layer may re-allocate this memory, but because the entry still exists in your page table, you can still read and write to the physical pages of memory as well.
Exploiting a physical use-after-free is fairly simple, even on more recent iOS versions. Assuming that you have an array of pages that have been freed only at the VM layer, the general exploitation strategy goes something like this:
- Spray a large number of objects into kernel memory (that you can distinguish as your own)
- Check all of the freed pages you can access for one of these objects
- If none found, repeat the process
- If an object is found on your freed page, manipulate it's fields to obtain kernel read and write primitives
The kfd GitHub repository has three examples of this strategy, named sem_open
, kqueue_workloop_ctl
(reading only) and dup
(writing only). opa334's fork also has an implementation that uses the IOSurface exploitation strategy from weightBufs.
Impact of mitigations
Physical use-after-free vulnerabilities are largely unaffected by most modern mitigations present in XNU. Kernel ASLR, PAN, PPL and kalloc_type hardly impact exploitation of these bugs. Data PAC and zone_require are the only mitigations that have a considerable effect on these bugs, because they prevent an attacker from forging some kernel pointers that could otherwise be used to gain read/write primitives with certain kernel objects.
With early iOS 17 betas being vulnerable to two bugs from kfd (PhysPuppet and Landa), it was observed that SPTM prevented exploitation of these bugs, as shown here.