Skip to content

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.

  1. GPU user client'larını aç. Userspace'ten IOAccelSharedUserClient2'i aç ve bir IOAccelCommandQueue2 oluştur. Ham IOAccelKernelCommand byte'larını yazabilmek için command segment'inin shared memory'sini map'le.

  2. 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.

  3. Segment'in ardına bir ipc_kmsg groom'la. kernel_map'i öyle şekillendir ki segment'i takip eden page bir ipc_kmsg'nin başlangıcı olsun. Timestamp write, ipc_kmsg size field'ını corrupt eder; corrupt edilen size, timestamp değerinin aralığıyla sınırlıdır (kabaca 0x0003ffa90x0400a8ff).

  4. 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 bir kmem_alloc() spray (KMA_ATOMIC olmadan), 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.

  5. 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_port inşa edilsin. Message'ı receive etmek, userspace'e o fake port'a bir Mach send right verir.

  6. Kernel R/W'ı bootstrap et. Fake port'un field'larını manipüle ederek kernel belleğini read/write et, kernel_map ve ipc_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.

References