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'ı, biripc_port'un reference count'unu ve type bit'lerini sıfırlamak içinipc.portszone'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).
-
Zone'ları
kalloc.16veipc.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: birmach_msgiçinde gönderilen bir port name dizisi kalloc'lanmış bir backing buffer allocate eder, ve alıcı taraf gerçekipc_portobject'leri materyalize eder. -
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.
-
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 biripc_entrytutmaya devam eder — yani free edilmiş bir port'a send/receive right. Beklenen state: task'ın port namespace'inde dangling bir port name. -
Bir zone garbage-collection'ı zorla, böylece free edilmiş
ipc_portsayfası 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'unip_contextfield'ı ile örtüşsün. -
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
-
Free edilmiş sayfayı tekrar reallocate et, bu sefer içeriği fake bir
ipc_portoluş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ğruio_bits, fake birip_kobject, vb.). -
Fake port'u,
ip_kobject'i kontrollü fake birtask'a işaret eden bir fake kernel task port'a (IKOT_TASK) yükselt. O port'a karşımach_vm_read/mach_vm_writeardı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ındanmach_port_set_attributesve hızlı Mach port allocation/free hareketliliğinin gelmesi, EDR/syscall telemetry'sinde işaretlenmeye değer anormal bir dizidir. - Bozulmuş
ipc_portio_bits/io_references'lı kernel panic'leri, ya daipc.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'ınbufferSize'ını aşamaz. - Sonraki XNU hardening'i (zone-require check'leri,
zone_idvalidation'ı, 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.