Basic info

CVE number: CVE-2016-5195

Description: Race condition in mm/gup.c in the Linux kernel 2.x through 4.x before 4.8.3

Fixed commit: [19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619](<https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619>)

POCs: poc1, poc2

漏洞原理:

有这样一种场景:fork 的子进程继承父进程中通过 mmap 映射到内存的文件时,内核不会直接为子进程复制这段内存,而是分别将父子进程对应的 PTE 设置为 read only 指向同一段物理内存,只有当其中一个进程写入这段内存的时候才会执行复制的操作(COW, Copy On Write);

当子进程通过 write 系统调用写入的时候,内核会进入 page fault 的处理函数,根据记录了页面属性的 foll_flags 判断 page fault 类型,如果只是 read 操作,则直接返回原物理页;如果是 write 操作则进入 COW 的流程,创建新的页面复制原物理页面的内容,但是在 COW 的流程中,有一个比较tricky的操作,因为 COW 机制使用了写保护来标记这个页面是 COW page(即便这个页面本身是可写的),因此内核在处理 COW 流程的时候会把访问请求的 flag 中的 WRITE 标记去掉,使得上层函数再次请求页面的时候不会再进入相同的 page fault 处理循环而是获取到刚才 page fault handler 新创建的 COW page,继续接下来的流程去操作新创建的 page,对这个page 的修改并不会影响原文件内容。但是上述操作中存在一个 race 窗口,在 page fault 处理函数清理掉 WRITE 请求标记后,和下一次申请到刚刚新建的 COW page 前,如果通过 madvise(MADV_DONTNEED) 将新创建的 COW page 清理掉,那么请求page 会再次失败,并再次进入 page fault handler,但是这一次,请求 flag 中的 WRITE 标记已经被清理了,因此会被认为是一次 read 请求,不再进入 COW 流程而是直接返回原物理页面,后面对该页面的修改会直接同步到原本 read only 的原文件中;

(完整的流程要经历两个 retry , 这里只是 race 的地方)

Race 的资源: PTE

write -> faultin_page: copy physical memory, set pte

madvise: zap pte