Android Binder UAF in-the-wild (CVE-2019-2215)¶
"Bad Binder" — epoll hâlâ embedded wait queue'sine işaret ederken bir
binder_thread'i free etmek, gerçek in-the-wild Android privilege escalation'da kullanılan bir list-unlink primitive'i doğurur.
Mechanism¶
Struct'ından daha uzun yaşayan embedded wait queue
Her binder client thread'i bir struct binder_thread'tir
(drivers/android/binder.c içinde). İçine bir wait queue head gömülüdür:
wait_queue_head_t wait. Bir process binder fd'sini epoll üzerinden poll
ettiğinde, binder_poll() epoll'a o embedded wait member'ının içine bir
pointer verir; böylece epoll daha sonra bildirim alabilir ve queue üzerinde
kendini register/unregister edebilir.
Lifetime bug'ı şu: BINDER_THREAD_EXIT ioctl'ü binder_thread_release()'i
çağırır, bu da tüm binder_thread'i free eder — dolayısıyla embedded
wait queue'sini de — ama epoll'a haber verilmez. epoll, artık var olmayan
bir wait_queue_head_t'ye dangling bir pointer tutmaya devam eder. "Hiçbir
external subscriber, object free edildikten sonra ona bir pointer tutmamalı"
invariant'ı kırılır: epoll hâlâ bir subscriber'ken binder queue'yi tek
taraflı olarak free eder.
epoll daha sonra o queue'ye dokunduğunda — örneğin kendi teardown'ı sırasında
— remove_wait_queue() / __remove_wait_queue() çalıştırır; bu da free
edilmiş queue'nin içindeki task_list üzerinde bir list_del() yapar.
Primitive'in kalbi tam da budur: free edilmiş ve artık yeniden alloke edilmiş,
attacker'ın şekillendirdiği bellek üzerinde işleyen kontrollü bir list
unlink (list_del). list_del(entry),
entry->next->prev = entry->prev ve entry->prev->next = entry->next
yapar; yani reclaim edilen object'in içeriğine sahip olduğunda kontrol ettiğin
bir adrese kontrol ettiğin bir değeri yazar. Buradan kanonik yol, kernel
read/write elde etmek için komşu bir task_struct / addr_limit'i corrupt
etmek, ardından root için credential'ları overwrite etmektir.
Bu CVE'nin tarihsel olarak önemli olmasının sebebi patch lag: bug upstream'de Şubat 2018'de fix'lendi (Linux 4.14 dönemi, ayrıca 3.18/4.4/4.9 stable), ama fix Android Security Bulletins'e Ekim 2019'a kadar hiç propagate olmadı; böylece piyasadaki cihazlar (Pixel 2, Galaxy S7/S8/S9, Huawei P20, vb.) vulnerable kaldı ve bir exploit'in in-the-wild kullanıldığı tespit edildi.
Walkthrough¶
Trigger küçücüktür ve unprivileged bir app'ten erişilebilir. İyi bilinen syscall
dizisi (Project Zero RCA'sından), epoll hâlâ wait queue'sine referans verirken
binder_thread'i free eder:
int binder_fd = open("/dev/binder", O_RDONLY);
int epfd = epoll_create(1000);
struct epoll_event event = { .events = EPOLLIN };
epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event); /* epoll registers on thread->wait */
/* Frees the binder_thread (and its embedded wait queue) out from under epoll */
ioctl(binder_fd, BINDER_THREAD_EXIT, NULL); /* 0x40046208ul */
/* Removing the watch now touches freed memory -> list_del on a dangling wait queue */
epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);
Adım adım:
open("/dev/binder")birbinder_procallocate eder; bir thread'e ihtiyaç duyan ilk ioctl, embeddedwait'iyle birliktebinder_thread'i lazily oluşturur.EPOLL_CTL_ADD,binder_poll()'unthread->waitüzerine bir epoll waiter eklemesine yol açar — epoll artık thread object'ine bir pointer saklar.BINDER_THREAD_EXIT→binder_thread_release(),binder_thread'i free eder. epoll'ın pointer'ı artık dangling (use-after-free durumu oluştu).- Free edilmiş slot'u kontrollü bir object'le reclaim et ki wait queue'nin
task_list.next/prev'i attacker'ın seçtiği adresler olsun. EPOLL_CTL_DEL(ya da epoll close)__remove_wait_queue()→list_del()→ kontrollü write'ı çalıştırır.
Expected kernel symptom on a KASAN build
BUG: KASAN: use-after-free in remove_wait_queue+0x...
Write of size 8 at addr ffffffc0xxxxxxxx by task poc/...
Freed by task poc:
binder_thread_dec_tmpref
binder_thread_release
binder_ioctl (BINDER_THREAD_EXIT)
binder_thread slot'una yeniden alloke
edilmiş ne varsa onu sessizce corrupt eder; exploit bunu bir addr_limit
overwrite'ına ve ardından commit-creds tarzı bir root
escalation'a çevirir.
Detection¶
- KASAN, use-after-free'i doğrudan
remove_wait_queue/__remove_wait_queue'da,binder_thread_release'le biten bir free stack'iyle flag'ler. open /dev/binder → epoll_ctl ADD → BINDER_THREAD_EXIT → epoll_ctl DELpattern'ı, syscall-trace tabanlı detection için sıkı ve ayırt edici bir signature'dır.
Mitigation¶
- 2019-10-06 veya daha yeni bir Android security patch level'ı sağla (fix, thread release edildiğinde epoll waiter'ı kaldırır).
- Genel kernel hardening (SLAB randomization,
init_on_free, hardened usercopy) free edilmişbinder_thread'i reclaim etme maliyetini artırır.