Huge DirtyCow 大脏牛漏洞分析

Posted by JenI on 2017-12-06 00:00:00+08:00

前言

最近,国外的Bindecy安全团队爆出Linux系统存在大脏牛漏洞,编号为CVE-2017–1000405。之所以叫它大脏牛漏洞并不是因为它的严重性高于2016年的脏牛漏洞,而是因为大脏牛漏洞针对的目标为Linux操作系统的大内存页和大零页。

2016年的脏牛漏洞影响了近十年的所有 Linux 发行版,移动端的安卓系统也在其影响范围内。而这次的大脏牛漏洞影响范围就没那么广了,因为 Linux 大内存页的使用有一定限制,且在 Android 系统上大内存页功能并未启用,因此大脏牛漏洞对 Android 系统几乎无影响。

下面是对该漏洞的简单分析。

漏洞分析

本次的大脏牛漏洞是由于对之前的脏牛漏洞 (CVE-2016-5195) 修补不完全导致的。2016 年的脏牛漏洞出现在 get_user_pages 函数中,该函数可以获取某一进程调用的虚拟地址所对应的物理地址。函数源码如下:

hugedirtycow-1

cond_resched 函数具有主动被调度的作用,所以当 retry 代码块生效时,会首先进行主动放权,然后等待下一次被调度。follow_page_mask 函数会从页表中获取指定地址的物理页,如果页表中不存在物理页则为缺页。此时函数返回 0,调用 faultin_page 函数进行处理。follow_page_mask 的 foll_flags 参数决定着当前的内存访问语义,如果它对应的权限与内存页的权限配置不一致时,会导致 follow_page_mask 返回值为 NULL。当返回值为 NULL 时,同样会调用 faultin_page 函数以处理目标地址中的错误信息。

hugedirtycow-2

该函数在处理缺页情况时,会从磁盘中调入页面,然后执行下一次 retry。如果以只读权限请求一个使用 FOLL_FORCE 标志即可读可写页面时,faultin_page 函数会进入 COW(Copy-On-Write) 流程,handle_mm_fault 将创建一个新的只读的 COW page,并将这个内存页标记为脏页(页内数据被修改后则为脏页),同时,它还移除了内存页的 FOLL_WRITE 状态,以防止下一次 retry 时,同样流程申请的新的 COW page和原来的 COW page 拥有同样权限,从而导致无限 retry,进入死循环。移除操作在 faultin_page 函数体的最后:

hugedirtycow-3

再回到 get_user_pages 函数,正常情况下,follow_page_mask 请求获取可写的内存页,页表中不存在物理页,以缺页情况交由 faultin_page 函数处理,faultin_page 函数从磁盘中调入内存页,之后进入下一次 retry,follow_page_mask 再次被调用,继续请求获取可写的内存页,由于内存页没有可写权限,返回 NULL,调用 faultin_page 函数复制一个只读内存页并移除 FOLL_WRITE 标志。进入下一次 retry,follow_page_mask 请求获取虚拟地址对应内存页,此时成功返回 page。

以上是正常情况下的 COW 流程,但是假设在第二次 retr 时(使用cond_resched主动放权后),在同一时刻通过另一个线程调用 madvice(MADV_DONTNEED) 换出对应内存页,此时便会产生脏牛漏洞。换出对应内存页,然后再次 retry 时,发生缺页现象,使用 faultin_page 函数从磁盘调入内存页,再次 retry,由于此时的 foll_flags 不包含 FOLL_WRITE,所以不会再一次创建脏页,因此当请求获取虚拟地址对应的内存页时,会返回直接绑定特权文件的原始页。之后再进行写入操作时,内存中的数据同步到磁盘,只读文件便被修改。

大脏牛漏洞的原理与上述脏牛漏洞原理相同,不过大脏牛漏洞目前只被发现可以攻击两个目标,一个是大内存页 (THP:Transparent Huge Pages),另一个是零页 (zero page)

一般情况下,Linux 的内存页为 4kb 大小,但是它允许 2M 和 1G 的大内存页以满足大内存程序的需求。大内存页是可以交换但是不可拆分的,能够用于三种映射情况,匿名,shmem 和 tmpfs 映射。

零页是 Linux 系统为了节省资源而存在的一个特殊内存页。当程序想系统申请匿名内存页时,会通过 mmap 将申请映射到一个值为零的物理内存页,当程序执行写入操作时,才会真正向系统申请内存,由于零页是只读的,所以为了避免系统分配多个未被写入的新建零页,同一个零页会被映射在多个不同的进程中。

对于原本只读的共享零页、大零页、大内存页,大脏牛漏洞都是可以对其值进行篡改的,下面的验证过程通过重新写入零页的值,造成系统上运行的程序在读取零页时出现 segmentation fault 而崩溃。由于浏览器的操作指令比较复杂,更容易触发漏洞,所以这里使用 Firefox 进行漏洞复现。

漏洞复现

当前 Linux 内核版本为 4.11.9-1

hugedirtycow-4

使用 gdb 调试火狐浏览器

hugedirtycow-5

开始调试

hugedirtycow-6

使用 gcc 编译 POC

hugedirtycow-7

运行可执行程序 a.out

hugedirtycow-8

正常操作浏览器,当浏览器进程读取零页时,gdb 成功捕获到了火狐浏览器的 segmentation fault,此时火狐浏览器崩溃。

hugedirtycow-9

POC

https://github.com/bindecy/HugeDirtyCowPOC

参考

https://medium.com/bindecy/huge-dirty-cow-cve-2017-1000405-11eca132de0(爬墙访问) http://www.52bug.cn/content/plugins/openlink/viewPage.html?url=https://www.anquanke.com/post/id/89096 http://blog.csdn.net/gatieme/article/details/52403013


作者:   JenI   转载请注明出处,谢谢


Comments !