Android Binder spinlock UAF (CVE-2022-20421)¶
Bir binder node refcount dengesizliği, embedded spinlock'u hâlâ canlı olan bir
binder_proc'u free eder; böylece free edilmiş bellek üzerinde tutulan bir lock, Pixel sınıfı cihazlarda kernel R/W primitive'ine dönüşür.
Mechanism¶
Free edilmiş bellekteki tutulan bir spinlock neden exploit edilebilir
Binder her IPC endpoint'ini bir binder_node olarak temsil eder ve başka kaç
process'in ona referans verdiğini strong/weak ref count'lar üzerinden takip
eder. BINDER_TYPE_HANDLE object'i taşıyan bir transaction işlendiğinde,
binder_inc_ref_for_node()'un node referansını tam olarak bir kez artırması
gerekir; bu, transaction buffer release edildiğinde gelen bir decrement ile
dengelenir. CVE-2022-20421 işte bu muhasebedeki bir uyumsuzluktur: bir saldırgan
decrement path'inin eşleşen increment olmadan çalışmasını sağlayabilir, böylece
reference count underflow eder ve kernel sahip object'i — nihayetinde
binder_proc'u — olması gerekenden erken düşürür.
Bu bug'ı sıra dışı yapan şey, geride bıraktığı primitive'dir. Çoğu kernel
UAF'i sana hemen yeniden allocate edeceğin dangling bir object pointer'ı verir.
Burada ise dangling olan şey, free edilmiş binder_proc içindeki
embedded bir spinlock_t'tir. Binder code path'leri bu lock'u almaya ve
bırakmaya devam eder (spin_lock(&proc->...)), dolayısıyla kernel — lock'un
val/owner word'lerini — zaten page allocator'a iade edilmiş ve muhtemelen
yeniden kullanılmış belleğe write eder. Bir spinlock'un tek başına hijack
edilecek yararlı bir data alanı yoktur, bu yüzden exploit'in tamamı o page'i
artık neyin beslediği ile ilgilidir.
İhlal edilen invariant lifetime ⊇ all uses'tir: proc'u dereference eden
her code path (lock dahil) object'in lifetime'ı içinde olmalıdır. Refcount
underflow, canlı kod hâlâ bir pointer tutarken lifetime'ı kısaltır. Lock
operasyonları sabit bir offset'te küçük, deterministik write'lar olduğundan,
free edilmiş page (bir
page-level cross-cache attack ile)
groom edilebilir; böylece kontrol edilebilir bir structure — örn. pipe buffer
page'leri — bayatlamış lock'un altına yerleşir. İki least-significant bit'i
temizlenmiş bir pointer (spinlock word'ü yeniden kullanılan bir pointer'ın
alt kısmına alias yapar), exploit'in bir arbitrary read/write'a ve
anon_pipe_buf_ops gibi bir kernel .text pointer'ını leak ederek bir KASLR
break'ine çevirdiği bir type confusion ortaya çıkarır.
Walkthrough¶
Bu, savunma/eğitim amaçlı bir yeniden kurgudur. Public exploit, Zhenpeng Lin
(0xkol) tarafından yazılan badspin'dir; aşağıdaki adımlar onun şeklini tarif
eder, çalıştırılabilir bir drop'u değil.
- Binder device'ını aç ve node/ref API yüzeyini öğren:
int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
/* Drive transactions via BINDER_WRITE_READ with BC_TRANSACTION commands
carrying flat_binder_object entries of type BINDER_TYPE_HANDLE. */
-
Refcount dengesizliğini tetikle. Transaction'ları öyle kur ki
binder_transaction_buffer_release()üzerinden geçen failure / cleanup path'i,binder_inc_ref_for_node()'un hiç artırmadığı bir node referansını decrement etsin. Underflow, strong ref'i erkenden sıfıra düşürür ve sahipbinder_proc'u free eder. -
Spinlock hâlâ kullanımdayken page'i free et. Free edilmiş
binder_procbirkmalloc/page-backed allocation içinde yaşar; binder hâlâ embedded lock üzerindespin_lock/spin_unlockyaparken backing page'in tamamının page allocator'a iade edilmesini sağla. -
Page'i, bir page-level cross-cache spray kullanarak saldırgan kontrollü içerikle reclaim et (pipe page'leri temiz bir seçimdir — bkz. pipe-buffer-hijack). Bayatlamış lock write'ları artık saldırganın geri okuyabildiği structure'ların içine düşer.
-
Type confusion kur. Lock word'ü, reclaim edilmiş object'teki iki LSB'si temizlenmiş bir pointer ile çakışır; o object'i normal bir read path üzerinden geri okumak kısmen değiştirilmiş pointer'ı açığa çıkarır ve onun üzerinden write yapmak kısıtlı bir kernel write verir.
Beklenen son durum (badspin writeup'ından)
[*] binder_proc freed, spinlock still hot
[*] page reclaimed by pipe buffer spray
[+] leaked kernel text ptr (anon_pipe_buf_ops): 0xffffffc00xxxxxxx
[+] KASLR base resolved
[+] arbitrary kernel read/write established
[+] creds overwritten -> uid=0, SELinux permissive
untrusted_app SELinux domain'inden root + SELinux
bypass; başka bir memory-corruption primitive'i gerektirmez.
Arbitrary R/W'den root'a geçiş, alışılmış commit-creds / credential-overwrite pattern'ini izler.
Detection¶
dmesg'te binder ref accounting'ten gelen kernelBUG/WARN'ları (binder_dec_node,binder_noderef underflow), denenen bir kötüye kullanımın güçlü bir işaretidir.- KASAN build'leri, spinlock erişiminde
binder_procuse-after-free'ini yakalar. - Yoğun binder transaction churn'ü artı çok sayıda
pipe()/page-spray allocation yapan SELinuxuntrusted_appprocess'leri, işaretlenmeye değer anormal bir pattern'dir.
Mitigation¶
- 2022-10-05 Android security patch level'ı veya sonrasını uygula; fix,
binder.ciçindeki node reference accounting'i düzeltir. - Çıtayı yükselten genel hardening: SLAB freelist randomization,
init_on_freeve page-level cross-cache-attack reuse adımını zorlaştıran page-allocator hardening.