Heap Hardening

From The Apple Wiki

The Heap has been hardened since iOS6 to prevent well-known attack strategies. Three mitigations were put in place:

  • Pointer validation
  • Block poisoning
  • Freelist integrity verification

This is specific to the zone allocator (zalloc(), used by kalloc(), MALLOC(), MALLOC_ZONE()).

Pointer Validation

The goal is to prevent invalid pointers being entered into kalloc() zone's freelist. Additional checks are performed on pointers passed to zfree(). This is also performed as part of validation on pointers in freelist during allocation (zalloc()).

The pointer is verified to be in kernel memory (0x80000000-0xFFFEFFFF). If allows_foreign is set in zone, no more validation is performed (currently event_zone, vm_map_entry_reserved_zone, vm_page_zone). If the pointer is within kernel image, allow, otherwise ensure pointer is within zone_map.

Block poisoning

The goal is to prevent UAF-style attacks. The stategy involves filling blocks with sentinel value (0xdeadbeef) when being freed. This is done by add_to_zone(), called from zfree() and only on selected blocks with block sizes smaller than cache line size of processor (32 bytes on A5/A5X devices) and can be overridden with "‑zp", "‑no‑zp", "zp‑factor" boot parameters.

Freelist integrity verification

The goal is to prevent heap overwrites from being exploitable. Two random values are generated at boot time (zone_bootstrap()), 32-bit cookie for "poisoned blocks" and 31-bit cookie (low bit cleared) for "non-poisoned blocks". The value serves as a validation cookie.

The freelist pointers at the top of a free block are since iOS6 validated by zalloc(). This check is done by alloc_from_zone(). The encoded next pointer is placed at the end of block XORed with "poisoned_cookie" or "non-poisoned cookie".

The zalloc() ensures next_pointer matches the encoded pointer at the end of the block and tries both cookies. If the poisoned cookie matches, it checks the whole block for modification of sentinel (0xdeadbeef) values and kernel panics if either check fails. The next pointer and cookie is replaced by 0xdeadbeef when allocated as possible information leak protection.