Skip to content

IOKit driver UserClient exploitation

Bir sürücünün IOUserClient::externalMethod selector'larına unprivileged bir uygulamadan ulaşmak, başlıca iOS/macOS kernel attack surface'idir; zayıf per-selector input validation'ı (örneğin IOMobileFramebuffer'da kontrol edilmemiş bir index) tek bir IOConnectCallMethod'u kernel içinde bir OOB read/write'a çevirir.

Mechanism

Note

Bir IOKit sürücüsüne her userspace bağlantısı tam olarak bir IOUserClient instance'ı (bir io_connect_t Mach port'u) tarafından aracılanır. Userspace, scalar ve structure in/out buffer'larıyla numaralı bir selector dispatch eder; kernel bunu IOUserClient::externalMethod üzerinden yönlendirir, bu da bir IOExternalMethodDispatch entry'si arar ve method'un function'ını çağırmadan önce argüman şeklini o entry'nin checkScalarInputCount / checkStructureInputSize / checkScalarOutputCount / checkStructureOutputSize alanlarına karşı doğrulaması gerekir. İnvariant: bir selector handler her scalar ve struct alanını tamamen attacker-kontrollü olarak ele almalı ve onu kendisi bounds-check etmeli — dispatch table yalnızca count/size'ları kontrol eder, asla değerleri değil. Bir handler bir scalar'a range kontrolü olmadan array index'i olarak güvendiğinde, genel dispatch katmanı onu durdurmak için hiçbir şey yapmaz ve kontrollü index kernel privilege'inde bir OOB erişime dönüşür. externalMethod surface'inin bu kadar verimli olmasının sebebi budur: onlarca sürücü boyunca yüzlerce selector, her biri yanlış olabilen elle yazılmış bir validation.

Legacy IOExternalMethod table şekli açık hale getirir:

struct IOExternalMethod {
    IOService    *object;   /* the user client / target */
    IOMethod      func;     /* the selector handler */
    IOOptionBits  flags;    /* scalar vs structure I/O */
    IOByteCount   count0;   /* input scalar count / struct size */
    IOByteCount   count1;   /* output scalar count / struct size */
};

Modern path, handler'a bir IOExternalMethodArguments (args->scalarInput[], args->structureInput, args->scalarOutput[]) geçirir.

Walkthrough

Çalışılmış örnek: CVE-2021-30807, IOMobileFramebufferUserClient'taki (IOMFB / AppleCLCD framebuffer sürücüsü) bir OOB read; Saar Amar tarafından dokümante edildiği gibi.

  1. User client'ı aç. Bir uygulamadan (veya WebContent'ten) service'i al ve bir bağlantı aç:
io_service_t  svc  = IOServiceGetMatchingService(kIOMasterPortDefault,
                        IOServiceMatching("AppleCLCD"));
io_connect_t  conn;
IOServiceOpen(svc, mach_task_self(), 0 /*type*/, &conn);
  1. Zafiyetli selector'ı çağır. Selector 83, IOMobileFramebufferUserClient::s_displayed_fb_surface'e map'lenir. Bir scalar girer, doğrudan index olarak kullanılır:
uint64_t scalars[1]      = { attacker_index };
uint64_t out[1]; uint32_t out_cnt = 1;
IOConnectCallMethod(conn, 83,
                    scalars, 1,        /* scalarInput, count   */
                    NULL, 0,           /* structureInput       */
                    out, &out_cnt,     /* scalarOutput         */
                    NULL, NULL);
  1. Kontrol edilmemiş index. Handler, args->scalarInput[0]'ı doğrudan IOMobileFramebufferLegacy::get_displayed_surface'e iletir; bu da üst sınır olmadan *(this + scalar0 + 0x331) hesaplar. Disassembly'de:
ADD  X9, X9, X11, LSL#3   ; this + scalar*8  (no bounds check)
LDR  X2, [X9]             ; OOB kernel read
  1. OOB read'i bir primitive'e çevir. Sızdırılan pointer bir IOSurface* olarak ele alınır ve IOSurfaceRoot::copyPortNameForSurfaceInTask üzerinden çalıştırılır; bu da attacker'ın kontrollü/sahte bir IOSurface materyalize etmesine ve IOSurfaceSendRight::init üzerinden virtual call'ları tetiklemesine izin verir — infoleak'ten control flow'a pivot.
Selector reversing metodolojisi

Bir sürücünün surface'ini map'lemek için: bağlantı type'ını öğrenmek için onun newUserClient'ını bul, sonra dispatch table'ı dökmek için externalMethod / getTargetAndMethodForIndex'i reverse et — her entry bir selector numarası, handler ve uyguladığı checkScalarInputCount / checkStructureInputSize'ı verir. Selector'ları aralık boyunca IOConnectCallMethod(conn, sel, ...) çağırarak fuzz'la; belirli bir selector'da (örneğin 83) bir panic eksik bir bounds check'i işaretler.

Warning

Erişilebilirlik yalnızca API'ye değil, entitlement'lara ve sandbox profillerine bağlıdır. CVE-2021-30807, WebKit WebContent process'inin tesadüfen tuttuğu com.apple.private.allow-explicit-graphics-priority'yi gerektiriyordu — onu sadece-yerel bir bug yerine remote-to-kernel bir LPE yapan şey buydu. iOS 16, hassas IOMFB selector'larını gate'lemeye (örneğin s_create_gain_map'i com.apple.private.gain-map-access arkasında) ve kısıtlı context'lerden hangi user client'ların açılabileceğini kısıtlamaya başladı.

Detection

  • Per-driver selector denetimi: externalMethod selector numaralarını ve argüman sayılarını logla; sandbox'lı process'lerden gelen politika-dışı selector'lar anormaldir.
  • Bir IOConnectCallMethod path'inde geçerli bir IOSurface/object base'inden uzak bir faulting adresle gelen panic, klasik OOB-index işaretidir.
  • Apple'ın PAC'ı, zone hardening'i ve iOS 16 user-client allowlist'i (ios16_restricted_iouserclients) erişilebilir kümeyi küçültür.

Mitigation

  • Her selector handler'ın içinde her scalar/struct alanını bounds-check et; dispatch table'ın count/size kontrollerinin değer geçerliliğini ima ettiğine asla güvenme.
  • Güçlü user client'ları entitlement'lar arkasında gate'le ve onları renderer/WebContent sandbox'larından kaldır.
  • IOKit vtable'ları üzerindeki PAC ve iOS 16 restricted-user-client listesi, bir OOB'u sahte-object virtual call'a pivot etmenin maliyetini yükseltir.

References