摘要:0x00 概述 该漏洞是Linux的一个本地提权漏洞,发现者是Phil Oester,影响=2.6.22的所有Linux内核版本,修复时间是2016年10月18号。该漏洞的原因是get_user_page内核函数在处理Copy-on-Write(以下使用COW表示)的过程中,可能产出竞态条件造成COW过程被破坏,

0x00 概述
该漏洞是Linux的一个本地提权漏洞,发现者是Phil Oester,影响>=2.6.22的所有Linux内核版本,修复时间是2016年10月18号。该漏洞的原因是get_user_page内核函数在处理Copy-on-Write(以下使用COW表示)的过程中,可能产出竞态条件造成COW过程被破坏,导致出现写数据到进程地址空间内只读内存区域的机会。当我们向带有MAP_PRIVATE标记的只读文件映射区域写数据时,会产生一个映射文件的复制(COW),对此区域的任何修改都不会写回原来的文件,如果上述的竞态条件发生,就能成功的写回原来的文件。比如我们修改su或者passwd程序就可以达到root的目的。
0x01 POC分析
POC的地址如下:[https://github.com/dirtycow/dirtycow.github.io/blob/master/dirtyc0w.c], 下面是POC关键部分的伪代码:
Main:
    fd = open(filename, O_RDONLY)
    fstat(fd, &st)
    map = mmap(NULL, st.st_size , PROT_READ, MAP_PRIVATE, fd, 0)
    start Thread1
    start Thread2
     
Thread1:
    f = open("/proc/self/mem", O_RDWR)
    while (1):
        lseek(f, map, SEEK_SET)
        write(f, shellcode, strlen(shellcode))
         
Thread2:
    while (1):
        madvise(map, 100, MADV_DONTNEED)
首先打开我们需要修改的只读文件并使用MAP\_PRIVATE标记映射文件到内存区域,然后启动两个线程:
其中一个线程向文件映射的内存区域写数据,这时内核采用COW机制。
另一个线程使用带MADV_DONTNEED参数的madvise系统调用将文件映射内存区域释放,达到干扰另一个线程的COW过程,产生竞态条件,当竞态条件发生时就能写入文件成功。
还有一种方法:使用ptrace系统调用的PTRACE_POKETEXT参数来写文件映射的内存区域,参考见[https://github.com/dirtycow/dirtycow.github.io/blob/master/pokemon.c]。
0x02 漏洞原理分析
先附上一份[https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails]中的源代码分析结果:
faultin_page
  handle_mm_fault
    __handle_mm_fault
      handle_pte_fault
        do_fault 
      do_cow_fault 
        alloc_set_pte
          maybe_mkwrite(pte_mkdirty(entry), vma) 
# Returns with 0 and retry
follow_page_mask
  follow_page_pte
    (flags & FOLL_WRITE) && !pte_write(pte) 
     
faultin_page
  handle_mm_fault
    __handle_mm_fault
      handle_pte_fault
        FAULT_FLAG_WRITE && !pte_write
      do_wp_page
        PageAnon() 
        reuse_swap_page 
        wp_page_reuse
          maybe_mkwrite 
          ret = VM_FAULT_WRITE
((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE)) 
 
# Returns with 0 and retry as a read fault
cond_resched -> different thread will now unmap via madvise
follow_page_mask
  !pte_present && pte_none
faultin_page
  handle_mm_fault
    __handle_mm_fault
      handle_pte_fault
        do_fault 
      do_read_fault 
Copy-on-Write(COW)
当我们用mmap去映射文件到内存区域时使用了MAP\_PRIVATE标记,我们写文件时会写到COW机制产生的内存区域中,原文件不受影响。其中获取用户进程内存页的过程如下:
1. 第一次调用follow_page_mask查找虚拟地址对应的page,带有FOLL_WRITE标记。因为所在page不在内存中,follow_page_mask返回NULL,第一次失败,进入faultin_page,最终进入do_cow_fault分配不带_PAGE_RW标记的匿名内存页,返回值为0。
2. 重新开始循环,第二次调用follow_page_mask,带有FOLL_WRITE标记。由于不满足((flags & FOLL_WRITE) && !pte_write(pte))条件,follow_page_mask返回NULL,第二次失败,进入faultin_page,最终进入do_wp_page函数分配COW页。并在上级函数faultin_page中去掉FOLL_WRITE标记,返回0。
3. 重新开始循环,第三次调用follow_page_mask,不带FOLL_WRITE标记。成功得到page。
以下代码以liux 4.7([https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.7.tar.xz])的源码为例,具体解读一下流程。首先从关键的获取用户进程内存页的函数函数get_user_pages看起,get_user_pages系列函数用于获取用户进程虚拟地址所在的页(struct page),返回的是page数组,该系列函数最终都会调用\__get_user_pages。
long __get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
        unsigned long start, unsigned long nr_pages,
        unsigned int gup_flags, struct page **pages,
        struct vm_area_struct **vmas, int *nonblocking)