# CVE-2019-2215: Android use-after-free in Binder

Binder driver's binder_thread wait queue outlives the freed struct, letting epoll trigger a UAF unlink primitive and kernel read/write for root.

- Payout: Real-world hack
- Severity: high
- Program: Google Android
- Credited: Maddie Stone, Project Zero (Originally posted on [Project Zero blog](https://googleprojectzero.blogspot.com/p/rca.html) 2020-07-27)
- Disclosed: 2019-09-26
- CVE: CVE-2019-2215
- Write-up quality: 8/10
- Original: https://github.com/googleprojectzero/0days-in-the-wild/blob/main/0day-RCAs/2019/CVE-2019-2215.md
- Library page: https://bugbountylibrary.com/r/p0%3ACVE-2019-2215
- Tags: domain:mobile impact:privesc impact:rce kind:incident platform:project-zero sev:high tech:android tech:linux technique:chained technique:source-review vuln:memory-corruption vuln:privilege-escalation

**Target:** /dev/binder kernel driver (binder_thread wait_queue_head_t)
**Root cause:** binder_poll registers the wait queue embedded in binder_thread with epoll, but BINDER_THREAD_EXIT frees binder_thread without POLLFREE, leaving epoll pointing at freed memory.
**Steps:**
1. Open `/dev/binder` with `O_RDONLY`.
2. Create epoll fd via `epoll_create(1000)`.
3. Register binder fd with `epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event)` where events=EPOLLIN.
4. Call `ioctl(fd, BINDER_THREAD_EXIT, NULL)` to free binder_thread.
5. Trigger epoll removal/cleanup so `remove_wait_queue` runs `list_del` on the freed wait queue, yielding an unlink primitive.
**How found:** Originally reported by syzkaller in Nov 2017 via KASAN crash in remove_wait_queue; Project Zero analyzed the in-the-wild 0-day and hypothesized the unlink-to-addr_limit method from attacker notes mentioning CONFIG_DEBUG_LIST and CONFIG_ARM64_UAO.
**Exploitation:** Use the list_del unlink primitive from the freed wait queue to overwrite the address limit in task_struct, gaining kernel read/write and root; CONFIG_DEBUG_LIST breaks the primitive and CONFIG_ARM64_UAO hinders it.
**Escalation:** Unlink primitive -> arbitrary kernel write -> overwrite task_struct addr_limit -> kernel read/write -> root; chained with DAC/SELinux/Knox/RKP bypass on Samsung S8.
**Insight:** When auditing poll handlers, check whether the wait queue passed to poll_wait is embedded in a struct whose lifetime is shorter than the file's; if the driver frees it without POLLFREE, epoll retains a dangling wait queue giving a list_del unlink primitive.
