Skip to content

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:

  1. open("/dev/binder") bir binder_proc allocate eder; bir thread'e ihtiyaç duyan ilk ioctl, embedded wait'iyle birlikte binder_thread'i lazily oluşturur.
  2. EPOLL_CTL_ADD, binder_poll()'un thread->wait üzerine bir epoll waiter eklemesine yol açar — epoll artık thread object'ine bir pointer saklar.
  3. BINDER_THREAD_EXITbinder_thread_release(), binder_thread'i free eder. epoll'ın pointer'ı artık dangling (use-after-free durumu oluştu).
  4. 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.
  5. 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)
KASAN olmadan, unlink free edilmiş 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 DEL pattern'ı, 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.

References