Hầu hết kernel exploits cần heap manipulation. Zone allocator là allocator chính của XNU kernel. Hiểu nó = hiểu cách control kernel memory layout.


Tổng Quan

XNU dùng zone allocator (zalloc) làm primary memory allocator. Trên nền zone allocator, kalloc cung cấp general-purpose allocation theo size.

User code:  malloc(size)  → libmalloc (userspace)
Kernel:     kalloc(size)  → kalloc zones → zalloc → physical pages
            zalloc(zone)  → specific zone → physical pages

Zone Allocator (zalloc)

Concept

Zone = pool of fixed-size elements:

Zone "ipc_ports" (elem_size = 168 bytes):
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ port │ port │ FREE │ port │ FREE │ port │ port │ FREE │
│  #1  │  #2  │      │  #4  │      │  #6  │  #7  │      │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
         ↑               ↑               ↑
         └── Allocated ──┘── Free list ──┘

Zone Structure

struct zone {
    const char      *z_name;          // "ipc_ports", "kalloc.256", ...
    vm_size_t        z_elem_size;     // Element size
    zone_id_t        z_index;         // Zone index
    uint32_t         z_chunk_pages;   // Pages per chunk

    // Free list
    zone_element_t   z_recirc;        // Magazine/depot free lists
    uint32_t         z_elems_free;    // Count of free elements

    // Statistics
    uint64_t         z_elems_avail;   // Total elements
    uint64_t         z_alloc_count;   // Total allocations
    // ...
};

Allocation Flow

void *zalloc(zone_t zone) {
    // 1. Try magazine (per-CPU cache)
    elem = magazine_alloc(zone);
    if (elem) return elem;

    // 2. Try zone free list
    elem = zone_free_list_pop(zone);
    if (elem) return elem;

    // 3. Expand zone (allocate new pages)
    zone_expand(zone);
    elem = zone_free_list_pop(zone);
    return elem;
}

void zfree(zone_t zone, void *elem) {
    // 1. Clear element memory (security: prevent info leaks)
    bzero(elem, zone->z_elem_size);

    // 2. Push to magazine or free list
    magazine_free(zone, elem);
}

Free List

Pre-iOS 15: In-band free list (freelist pointer stored IN freed element)
  ┌──────────┐    ┌──────────┐    ┌──────────┐
  │ next ────┼───→│ next ────┼───→│ NULL     │
  │ (data)   │    │ (data)   │    │ (data)   │
  └──────────┘    └──────────┘    └──────────┘

Post-iOS 15: Out-of-band free list (bitmap or separate metadata)
  → Prevents freelist pointer overwrite attacks

Kalloc

Kalloc Zones

Kalloc wraps zalloc cho general-purpose allocations:

kalloc(size) → chọn zone nhỏ nhất chứa được size:

  Size range      Zone name         Actual allocation
  ─────────────────────────────────────────────────
  1-16 bytes  →   kalloc.16         16 bytes
  17-32       →   kalloc.32         32 bytes
  33-48       →   kalloc.48         48 bytes
  49-64       →   kalloc.64         64 bytes
  65-80       →   kalloc.80         80 bytes
  81-96       →   kalloc.96         96 bytes
  97-128      →   kalloc.128        128 bytes
  129-160     →   kalloc.160        160 bytes
  161-192     →   kalloc.192        192 bytes
  193-224     →   kalloc.224        224 bytes
  225-256     →   kalloc.256        256 bytes
  257-288     →   kalloc.288        288 bytes
  ...
  513-1024    →   kalloc.1024       1024 bytes
  1025-2048   →   kalloc.2048       2048 bytes
  2049-4096   →   kalloc.4096       4096 bytes
  4097-6144   →   kalloc.6144       6144 bytes
  6145-8192   →   kalloc.8192       8192 bytes
  >8192       →   kernel_map (vm_allocate, page-level)

kalloc_type (iOS 16+)

Type-isolated allocation — objects of same C type go to same zone:

// Old: mọi 256-byte objects chia sẻ kalloc.256
struct ipc_port *p = kalloc(sizeof(struct ipc_port));  // kalloc.176
struct pipe_buf *b = kalloc(sizeof(struct pipe_buf));   // kalloc.176 (same zone!)

// New: type-specific zones
struct ipc_port *p = kalloc_type(struct ipc_port, Z_WAITOK);  // zone: ipc_port
struct pipe_buf *b = kalloc_type(struct pipe_buf, Z_WAITOK);  // zone: pipe_buf (different!)

Impact cho exploitation:

  • Pre-kalloc_type: allocate object A cùng size với freed object B → type confusion dễ
  • Post-kalloc_type: cần object A và B cùng type hoặc cùng zone group → khó hơn nhiều

Heap Exploitation Techniques

1. Heap Spray

Mục đích: Fill zone với controlled data để tăng probability target object adjacent.

// OOL message spray — spray kalloc.256 zone
for (int i = 0; i < 1000; i++) {
    struct {
        mach_msg_header_t hdr;
        mach_msg_body_t body;
        mach_msg_ool_descriptor_t ool;
    } msg;

    msg.ool.address = spray_data;    // Controlled content
    msg.ool.size = 256;              // → kalloc.256
    msg.ool.type = MACH_MSG_OOL_DESCRIPTOR;
    msg.body.msgh_descriptor_count = 1;

    mach_msg(&msg.hdr, MACH_SEND_MSG, sizeof(msg), 0, 0, 0, 0);
}

2. Heap Grooming

Mục đích: Arrange heap layout để target object nằm ngay sau vulnerable object.

Step 1: Spray to fill zone
  [spray][spray][spray][spray][spray][spray][spray]

Step 2: Create holes (free selected spray objects)
  [spray][    ][spray][    ][spray][    ][spray]

Step 3: Allocate victim objects into holes
  [spray][victim][spray][victim][spray][victim][spray]

Step 4: Free remaining spray, allocate vulnerable object
  [vuln][victim][     ][victim][     ][victim][     ]
    ↑       ↑
    │       └── Adjacent! Overflow from vuln corrupts victim
    └── Trigger vulnerability here

3. Zone Transfer (Cross-Zone Attack)

1. Allocate object X in zone A
2. Free object X → X returns to zone A's free list
3. Bug: dangling pointer to X still exists
4. Force zone A to release pages back to page allocator
5. Force zone B to expand → zone B gets X's physical pages
6. Allocate object Y (different type) in zone B → Y occupies X's memory
7. Use dangling pointer to X → actually accessing Y → type confusion!

4. Freelist Poison (Pre-iOS 15)

Old-style in-band freelist:
  Free element: [next_ptr | padding...]

Overflow into freed element → overwrite next_ptr:
  [overflow data | CONTROLLED_ADDR | ...]

Next allocation returns CONTROLLED_ADDR → write-what-where primitive!

Post-iOS 15: freelist is out-of-band → this technique no longer works.

5. Probabilistic Grooming

Khi không thể guarantee adjacency:

1. Spray N objects (large N, e.g., 10000)
2. Free every other object → create N/2 holes
3. Allocate target objects → ~50% chance adjacent to spray
4. Trigger vulnerability
5. Check multiple targets → find which one was corrupted

Trade-off: reliability vs speed vs memory pressure

Mitigations Timeline

iOS Version Mitigation Impact
iOS 10 Zone poisoning (fill freed memory with pattern) Detect UAF sooner
iOS 11 Randomized zone free lists Make grooming harder
iOS 14 zone_require() — validate object belongs to expected zone Prevent zone transfer
iOS 15 Out-of-band free lists Kill freelist overwrite
iOS 15.4 Sequestered zones (sensitive zones isolated) Harder cross-zone
iOS 16 kalloc_type() — type-isolated zones Kill same-size type confusion
iOS 17 SPTM-enforced page types Prevent physical UAF escalation

Tài Nguyên


Bài Tập

  1. Đọc zalloc.c: Trace allocation path từ zalloc() → element return
  2. Identify zone for struct: Cho 1 kernel struct, tính size, xác định kalloc zone
  3. Write heap spray: Dùng OOL messages spray 1 specific kalloc zone trên macOS
  4. Analyze kalloc_type: Đọc Apple blog post, hiểu cách type isolation hoạt động
  5. Study real exploit: Đọc kfd source code, identify heap grooming technique used