oob_timestamp (CVE-2020-3837)¶
IOAccelCommandQueue2::processSegmentKernelCommand()içindeki bir header-size hesaplama hatası, pageable bir shared-memory region'ın sonundan 8 byte'a kadar timestamp verisini taşırarak yazar; Brandon Azad bunu tam iOS/macOS kernel read/write'a dönüştürdü.
Mechanism¶
8 byte'lık bir timestamp write neden kernel R/W'a dönüşür
AGX GPU pipeline'ı, userspace'in bir IOAccelCommandQueue2 üzerinden
(IOAccelSharedUserClient2 ile erişilen) kernel command'ları submit
etmesine izin verir. Command'lar, hem userspace'in hem de kernel'in map'lediği
bir shared-memory segment'inden parse edilir; dolayısıyla command byte'ları
attacker-controlled'dır.
Her IOAccelKernelCommand, 8 byte'lık bir header ile başlar (bir command
type field'ı artı bir size field'ı) ve ardından type'a özgü veri gelir.
processSegmentKernelCommand(), segment'in belirli bir command için yeterli
byte tuttuğunu doğrularken size check'i 8 byte'lık header'ı hesaba katmaz.
Bu yüzden parser, buffer'ın gerçek sonundan 8 byte'a kadar taşan bir command'ı
kabul eder.
Çoğu command type, over-read olan bölgeyi yalnızca okur; bu sadece bir info
leak'tir. Ama command type 2, kIOAccelKernelCommandCollectTimeStamp, o
bölgeye bir timestamp yazar. Segment, kendi page'indeki son canlı allocation
olduğu için, write takip eden page'in ilk 1–8 byte'ını bir timestamp
değeriyle taşırır.
Kritik nokta şu: segment, genel kernel_map'ten (büyük / pageable / shared
allocation'lar için kullanılır) alınan büyük, pageable, shared bir
allocation'dır; zone_map'ten değil. Yani overflow bir zone object'ine
düşmez; attacker'ın kernel_map üzerinde sıraya gelmesi için groom'ladığı
çok-page'li allocation'ın üzerine düşer. Exploit oraya bir ipc_kmsg groom'lar
ve onun size field'ını corrupt eder; böylece neredeyse kontrolsüz 8 byte'lık
timestamp write'ı güçlü bir heap-shaping primitive'ine dönüştürür: receive
edildiğinde sahip olduğundan çok daha büyük bir region'ı free eden oversized
bir message.
Walkthrough¶
Public PoC ve exploit Brandon Azad'a (Project Zero) aittir; aşağıdaki adımlar o writeup'ı takip eder.
-
GPU user client'larını aç. Userspace'ten
IOAccelSharedUserClient2'i aç ve birIOAccelCommandQueue2oluştur. HamIOAccelKernelCommandbyte'larını yazabilmek için command segment'inin shared memory'sini map'le. -
Out-of-bounds bir timestamp command'ı hazırla. Bir
kIOAccelKernelCommandCollectTimeStamp(type 2) command'ını öyle yerleştir ki, header'ı dışlayan length check göz önüne alındığında, onun timestamp store'u segment'in son page'inin sonundan 1–8 byte ötesine düşsün. -
Segment'in ardına bir
ipc_kmsggroom'la.kernel_map'i öyle şekillendir ki segment'i takip eden page biripc_kmsg'nin başlangıcı olsun. Timestamp write,ipc_kmsgsize field'ını corrupt eder; corrupt edilen size, timestamp değerinin aralığıyla sınırlıdır (kabaca0x0003ffa9–0x0400a8ff). -
Oversized bir region'ı free et. Şişirilmiş size'a sahip message'ı receive etmek, gerçek
ipc_kmsg'nin çok ötesindeki belleği free eder. 80 MB'lık birkmem_alloc()spray (KMA_ATOMIColmadan), free edilen aralığın attacker-owned page'leri kapsamasını sağlar ve atomic-entry split'lerinde ya da map'lenmemiş delikleri free ederken panic atmaktan kaçınır. -
Controlled data ile reclaim et ve bir port forge et. Free edilen region'a yeniden spray yap (örn. out-of-line ports array'leri / IOSurface-backed data) ki attacker'ın da map'lediği belleğin içinde bir fake
ipc_portinşa edilsin. Message'ı receive etmek, userspace'e o fake port'a bir Mach send right verir. -
Kernel R/W'ı bootstrap et. Fake port'un field'larını manipüle ederek kernel belleğini read/write et,
kernel_mapveipc_space_kernel'i bul ve bir fake kernel task port assemble et. Stabil arbitrary kernel read/write için onu install et (örn. bir host special port olarak) — bkz. kernel-task-port ve mach-port-use-after-free.
Timestamp değeri neden önemli
Attacker arbitrary bir değer yazamaz — yalnızca bir timestamp. Exploit,
timestamp'in bilinen bir sayısal pencereye düştüğü gerçeğine dayanır;
dolayısıyla corrupt edilen ipc_kmsg size'ı büyük ama sınırlıdır. Grooming
(80 MB'lık spray), o penceredeki herhangi bir değerin yalnızca exploit'in zaten
kontrol ettiği belleği free etmesini sağlayacak şekilde boyutlandırılmıştır;
böylece kernel'in invalid page'lere dokunması engellenir.
Mitigation¶
- Apple, CVE-2020-3837'yi
processSegmentKernelCommand()içindeki size check'e 8 byte'lık header'ı dahil ederek düzeltti; böylece over-length command'lar reddedilir. - iOS 13.3.1 / macOS 10.15.3 dönemi güncellemelerinde patch'lendi; bunların ötesine güncelleyin.
- Daha sonraki mitigation'lar (zone isolation,
kalloc_type, iOS'ta PAC), fake-port ve reclaim aşamalarının maliyetini yükseltir ama orijinal bounds bug'ını ele almaz.