CVE-2016-5195
CVE-2016-5195 (Dirty COW), Linux内核竞争条件漏洞,可导致非授权用户向任意可读文件写入任意内容,从而进行本地权限提升。影响范围为2.6.22-4.8.3,可以用于至今为止Android任意版本的root。
这个漏洞涉及不少内存管理的具体实现,所以分析学习起来很多东西对于我来说还是很不清晰,看来之后要把关键的内核代码多看一看。
漏洞分析
漏洞的根源在于对Copy-on-write的page强制写入的相关处理中,比如POC中通过/proc/self/mem对使用MAP_PRIVATE和PROT_READ方式映射的只读内存进行写入操作(或者是通过ptrace+PTRACE_POKEDATA来完成),这样的写入是没有问题的,因为写入的是COW的内存副本,并不会同步回原文件中去(而如果是使用MAP_SHARED方式映射,那么首先就需要对文件的写入权限才能映射成功,这样即便是将改动写回文件也不是越权操作)。
具体地,kernel会首先通过get_user_pages()获取目标page,然后再对目标page进行无视权限的写入操作。get_user_pages()的相关代码:
|
|
在这里的时候将会进入一个follow_page_mask()和faultin_page()间的循环处理。如果是正常流程的话,第一次执行进入follow_page_mask(),会由于对应pte为空而直接返回进入到falutin_page()处理缺页,在faultin_page()中经过handle_mm_fault()调用到handle_pte_fault进行处理,相关代码:
|
|
|
|
可以看到这一次调用,由于对应pte不存在,满足第一个if,会使用do_fault处理缺页,在do_fault中,由于对应的vma是个VM_PRIVATE同时要求写权限,会直接使用do_cow_fault来获取一个cow的page:
|
|
之后结束第一轮处理流程,进入retry第二次进入follow_page_mask()来获取pte,这次pte已经存在但是由于不具备写权限又一次直接返回NULL进入falutin_page(),这一次falutin_page()会使用do_wp_page()来处理对不具备写权限的page的cow操作。do_wp_page()会首先检查是否真的需要cow,由于之前已经进行过cow操作,所以目标page满足PageAnon()且引用计数为1,可以直接reuse这个page而避免一次cow,所以do_wp_page()直接进行reuse同时返回VM_FAULT_WRITE。在前面的faultin_page()的流程中可以看到,如果返回VM_FAULT_WRITE并且对应vma没有写权限,那么它会去除请求标志位中的FOLL_WRITE然后返回。这意味着同一个请页请求不再需要对应的page具有写权限。这样再一次retry通过follow_page_mask()就可以获取对应的page。
但是如果再第二次使用falutin_page()处理然后去掉了FOLL_WRITE请求标志位以后,在这个时候产生了一次madvise()的调用来将目标pte置空,就会出现问题。在这样的情况下,follow_page_mask()会发现pte为空而再次进入falutin_page()处理缺页,同时由于不需要写权限,这一次的缺页处理使用的是do_read_fault(),不会产生一个cow的副本,之后第四次调用follow_page_mask()也就会获得这个page从而对这个page进行强制写入操作,造成越权写。
Exploit
由于这个poc能够对任意可读文件进行写入,基本上root也就拿到了。对于各linux发行版来说,可选择的方法有很多,去写libc让root进程弹个shell或者改写带suid的bin的控制流拿shell,再或者写/etc/passwd也行。一些exploits的清单已经在github上了。
对于Android平台上的利用,像suid的bin和/etc/passwd这样的肯定是不能用了,不过在libc中留后门应该还是没有问题,清单中有一个是用patch VDSO的方式弹shell,这样可以直接让init弹个shell回来,直接就可以有init的context,省去了不少麻烦。可是还是一样的问题,就算有init的context,还是不能为所欲为,依然不能开shell或者是关SELinux,SELinux果然是最麻烦的东西。暂时还没看到什么好的办法,github上的那个exploit也已经明说了”a patch to sepolicy is still needed”。
不过在另一个repo的issue里还看到了另一种思路,如果拿到可以insmod的context可以用插入一个kernel module来关闭SELinux,这样也还是要取决于SEPolicy有没有给这个insmod的权限。
Patch
出于对性能和效率的考虑,对于此漏洞的修复采用额外的FOLL_COW标志来处理。在本来去掉FOLL_WRITE的地方改为了置位FOLL_COW,并把follow_page_pte()中对于如果要写不可写的页则返回NULL,改为了如果要写的页既不可写又不是强制写COW处理过的脏页则返回NULL(我觉得这样更好理解了一些orz),详见漏洞patch:https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619