Skip to content

empty_list (CVE-2018-4243)

XNU'nun getvolattrlist()'inde kısmen kontrollü 8-byte'lık bir heap out-of-bounds sıfır write'ı, bir ipc_port'un reference count'unu ve type bit'lerini sıfırlamak için ipc.ports zone'una karşı groom edilir; bu da dangling bir Mach port verir ve yukarıya doğru fake bir kernel task port'a yansıtılır.

Mechanism

Invariant: bir size uyuşmazlığı sabit-boyutlu bir buffer'ın allocation'ının ötesine yazılmasına izin verir

getvolattrlist() (XNU'nun VFS katmanındaki f_getattrlist/getvolattrlist volume-attribute path'i), bir kernel heap buffer'ını kullanıcı tarafından verilen bir bufferSize'dan boyutlandırır, ama gerçekte geri yazdığı attribute verisinin miktarını bufferSize değil, caller'ın hangi attribute bit'lerini istediği belirler. Packing kodu sabit bir prologue (ab.actual, bir struct attribute_buffer) artı sondaki bir uint32_t length emit eder, yani

bufferSize < sizeof(ab.actual) + sizeof(uint32_t)

olduğunda kernel yeni allocate edilmiş chunk'ın sonunun ötesine yazar. bufferSize = 0x10 ile allocation kalloc.16 zone'una düşer ve overflow tam olarak 0x14 + 0x4 - 0x10 = 0x8 byte'tır. Kritik olarak, taşan kuyruk dirattr/forkattr attribute field'larına denk gelir; attacker bunları sıfır bırakabilir — yani primitive, bir kalloc.16 object'inin sonundan taşan 8 NULL byte'lık kontrollü-konumlu bir write'tır. CWE-119: bir memory buffer'ın sınırları içindeki işlemlerin uygunsuz kısıtlanması.

Exploit'in kaldıracı, bir kalloc.16 object'inden 8 byte sonra ne durduğundan gelir. Ian Beer iki zone'u öyle groom eder ki sayfasının sonundaki bir kalloc.16 chunk'ının hemen ardından bir sonraki sayfadaki ilk ipc_port (ipc.ports zone) gelir. Bir ipc_port'un ilk 8 byte'ı io_bits (object type / active flag'leri) ve io_references (reference count) field'larıdır. Bunları sıfırlamak kernel'a port'un sıfır reference'ı olduğunu düşündürür; böylece bir reference düşüren sonraki işlem hâlâ referenced olan port'u free eder: klasik bir refcount-underflow'un use-after-free'ye dönüşmesi. SLUB tarzı bir zone'un freelist'i next-free pointer'ı free bir chunk'ın ilk byte'larında sakladığından, free bir kalloc.16 chunk'ını naif şekilde taşırmak freelist'i bozar ve panic yaptırır; bu yüzden exploit kalloc.16 freelist'ini tersine çevirir (tüm chunk'ları sırayla free et, yarısını reallocate et), böylece overflow her zaman port sayfasına bitişik, en sağdaki allocated object'e düşer, bir freelist link'ine değil.

Walkthrough

PoC empty_list, iOS 11.3.1 kernel'ı için yayımlandı (Project Zero issue 1564). Aşağıdaki aşamalar onun yapısını yansıtır; kavramsaldır ve yalnızca yetkili araştırma içindir.

Heap grooming exploit'in tamamıdır

Bug 8-byte'lık bir sıfır write'tır; yük taşıyan her şey bir kalloc.16 victim'inin tam olarak bir ipc_port'tan önce deterministik yerleşimidir. Grooming'i yanlış yaparsan ya no-op olur (kendi padding'ine taşar) ya da panic yaparsın (bir freelist link'ini taşırır).

  1. Zone'ları kalloc.16 ve ipc.ports'un ardışık bloklarına şekillendir. OOL (out-of-line) Mach port descriptor'ları ikisi için de elverişli bir allocation primitive'idir: bir mach_msg içinde gönderilen bir port name dizisi kalloc'lanmış bir backing buffer allocate eder, ve alıcı taraf gerçek ipc_port object'leri materyalize eder.

  2. kalloc.16 freelist'ini tersine çevir, böylece yeni allocate edilmiş bir victim chunk'ı bir sayfanın sonunda, takip eden sayfanın ilk ipc_port'u bir slot ötede olacak şekilde otursun:

// Trigger the OOB write: 0x10-byte buffer, request layout so the
// 8-byte trailer (dirattr/forkattr == 0) spills past the chunk.
struct attrlist al = {0};
al.bitmapcount = ATTR_BIT_MAP_COUNT;
al.commonattr  = /* attrs sized so packed output > bufferSize */;
char buf[0x10];
getattrlistat(AT_FDCWD, "/", &al, buf, sizeof(buf), 0); // writes 8 zero bytes OOB

Beklenen etki: bitişik ipc_port'un ilk 8 byte'ı (io_bits, io_references) 0 olur.

  1. Artık sıfırlanmış port'u düşürmek için reference-count'unu düş. mach_port_set_attributes(..., MACH_PORT_DNREQUESTS_SIZE, ...) çağırmak (ya da kernel port state'ini deallocate eden başka bir path) ipc_port'u free ederken user space dolu bir ipc_entry tutmaya devam eder — yani free edilmiş bir port'a send/receive right. Beklenen state: task'ın port namespace'inde dangling bir port name.

  2. Bir zone garbage-collection'ı zorla, böylece free edilmiş ipc_port sayfası geri verilsin ve farklı bir zone tarafından yeniden kullanılabilir olsun; sonra byte'ları attacker-controlled olan bir OOL ports array'i ile dangling port'un üzerine reallocate et. İkinci bir gerçek port'a bir pointer yerleştir, böylece dangling port'un ip_context field'ı ile örtüşsün.

  3. Bir kernel pointer leak et: dangling name üzerinde mach_port_get_context(), ip_context'te duran değeri döndürür ve örtüşen port'un adresini açığa çıkarır. Bu, heap object için kernel-pointer gizliliğini / KASLR'ı kırar.

mach_port_context_t ctx = 0;
mach_port_get_context(mach_task_self(), dangling_name, &ctx);
// ctx == kernel address of the overlapping ipc_port
  1. Free edilmiş sayfayı tekrar reallocate et, bu sefer içeriği fake bir ipc_port oluşturan pipe buffer'larla. Adım 5 sana kontrollü byte'ların kernel adresini söylediği için, kendi içinde tutarlı bir fake port kurabilirsin (doğru io_bits, fake bir ip_kobject, vb.).

  2. Fake port'u, ip_kobject'i kontrollü fake bir task'a işaret eden bir fake kernel task port'a (IKOT_TASK) yükselt. O port'a karşı mach_vm_read/mach_vm_write ardından arbitrary kernel read/write verir; modern XNU port-corruption chain'lerinin standart son state'i.

Sıfırlar neden yeterli

Çoğu heap overflow attacker-chosen değerlere ihtiyaç duyar. Burada kazanç yapısaldır: ipc_port reference count'u küçük ve unsigned'dır, dolayısıyla io_references/io_bits'e sıfır yazmak bir sonraki deallocation'ın canlı bir object'i free etmesi için yeterlidir. Overflow üzerinde kesin değer kontrolü gerekmez — sadece kesin konum.

Detection

  • Bir volume/attribute query'sinin (volume attr'larla getattrlist/getattrlistat) hemen ardından mach_port_set_attributes ve hızlı Mach port allocation/free hareketliliğinin gelmesi, EDR/syscall telemetry'sinde işaretlenmeye değer anormal bir dizidir.
  • Bozulmuş ipc_port io_bits/io_references'lı kernel panic'leri, ya da ipc.ports'taki zone-element integrity assert'leri bu corruption sınıfıyla tutarlıdır.

Mitigation

  • iOS 11.4, macOS 10.13.5, tvOS 11.4, watchOS 4.3.1'de getvolattrlist() bounds check'i düzeltilerek yamalandı; böylece çıktı caller'ın bufferSize'ını aşamaz.
  • Sonraki XNU hardening'i (zone-require check'leri, zone_id validation'ı, port-poisoning ve PAC-signed kernel pointer'ları) doğrudan adım 4-7'yi hedef alır: fake-port kurulumu ve kernel-task-port forgery.

References