首页 > 其他分享 >DirtyCOW-内核分析报告-cnblog

DirtyCOW-内核分析报告-cnblog

时间:2024-08-18 22:16:08浏览次数:7  
标签:fault pte page pages flags 内核 cnblog follow DirtyCOW

基础知识

  • mmap(void* start, size_t length, int prot,int flags,int fd, off_t offset)

    一个比较常用的函数,将磁盘上的文件映射到虚拟内存中,POC中参数prot为PROT_READ参数,参数flags为MAP_PRIVATE,请参考linux库函数mmap()原理及用法详解_linux mmap函数

  • madvice(caddr_t addr, size_t len, int advice)

    告诉内核内存addr~addr+len在接下来的使用状况,以便内核进行一些进一步的内存管理操作。

    advice为MADV_DONTNEED时,此系统调用相当于通知内核addr~addr+len部分的内存不再使用,内核将释放掉这一块内存以节省空间,相应的页表项也会被置空。

  • /proc/self/mem

    这个文件是一个指向当前进程的虚拟内存文件的文件,当前进程可以通过对这个文件进行读写以直接读写虚拟内存空间,并无视内存映射时的权限设置。也就是说我们可以利用写/proc/self/mem来改写不具有写权限的虚拟内存。可以这么做的原因是/proc/self/mem是一个文件,只要进程对该文件具有写权限,那就可以随便写这个文件了,只不过对这个文件进行读写的时候需要一遍访问内存地址所需要寻页的流程。因为这个文件指向的是虚拟内存。

准备工作

  1. 在ubuntu中编写dirtycow.c

    • vim dirtycow.c
      
  2. 在ubuntu中编译irtycow.c,使用如下命令:

    • gcc -pthread -static -m32 -g dirtycow.c -o dirtycow 
      

      image-20240715183257763

  3. 将生成的可执行文件dirtycow放入实验⽤用的qemu虚拟机

    • 将rootfs⽬目录下的⽂文件_install.tgz解压到/home/nudt⽬目录下:

      sudo tar xvzf /home/nudt/aos/lab/rootfs/_install.tgz  -C ~
      

      image-20240715183514197

    • 将可执行文件dirtycow拷入目录 ~/_install/tmp/

      sudo cp dirtycow ~/_install/tmp/
      

      image-20240715183742612

    • 重新打包⽣生成qemu虚拟机的根文件系统。可在rootfs目录下执⾏行行如下命令:

      ./mkrootfs.sh
      

      生成的根⽂文件系统放在映像⽂文件rooTs.img.gz中。

      image-20240715183932140

  4. 设置在dirtycow.c的main处加断点,

    • 在内核源码目录(“aos/lab/cur”)下,创建指向dirtycow.c和dirtycow所在目录的符号链接dirtycow,使用如下命令

      ln -s <dirtycow.c所在目录> dirtycow
      

      比如当前我把dirtycow.c和dirtycow放在~/aos/ 目录下

      ln -s ~/aos/ dirtycow
      

      image-20240715204602765

    • 查看可执行文件dirtycow的“.text”节(SecQon)的起始虚拟地址(VMA)。在可执行文件dirtycow所在目录(~/aos/ )运行:

      objdump -dlx dirtycow| less
      

      然后查看“.text”节的起始虚拟地址

      image-20240715205155697

    • 调试运行qemu虚拟机,在gdb中导入应用程序符号信息:

      dir dirtycow
      add-symbol-file dirtycow/dirtycow 0x80482d0
      注意:
      add-symbol-file dirtycow/dirtycow <可执行文件dirtycow的“.text”节的起始虚拟地址>
      

      image-20240715204932289

    • 设置断点:

      b dirtycow.c:main
      

      image-20240715205021479

调试过程分析

target remote localhost:1234
set logging file /mnt/hgfs/f/out.log
set logging on

add-symbol-file dirtycow/dirtycow 0x80482d0

b dirtycow.c:main

设置好断点之后,运行虚拟机qemu和调试器gdb,等待qemu中的linux开机后,输入如下命令

cd tmp
echo this is not a test > foo
ls -l foo
chmod 0404 foo
ls -l foo
cat foo
setuidgid 1000 sh
id
ls -l foo
./dirtycow foo youaremodified

在最后一句命令输入后,gdb调试器将停在断点main函数的入口处

image-20240729175100286

接着我们设置如下断点:

b dirtycow.c:83
b __access_remote_vm

当代码执行到 c+=write(f,str,strlen(str)),读写/proc/self/mem 这个文件,会通过系统调用,在内核中的实现是在 fs/proc/base.c 文件中

static const struct file_operations proc_mem_operations = {
	.llseek		= mem_lseek,
	.read		= mem_read,
	.write		= mem_write,
	.open		= mem_open,
	.release	= mem_release,
};

mem_write()函数主要是调用 __access_remote_vm来实现访问用户进程的进程地址空间

image-20240729183211433

image-20240729175212822

此刻通过continue命令到达__access_remote_vm,可以看到要写入的虚拟地址为3086991360(0xB7FF C000),通过手动计算可得出该虚拟地址的各项偏移为:

页目录项的偏移:0xB7C

页表项的偏移:0xFF0

页内偏移:0x0

通过p $lx_current().mm->pgd查看页目录地址,最终可以得到该虚拟地址对应的页表项为空,即用户空间那段内存(dirtycow 程序中map 指针指向的内存)其实还没有和实际物理页面建立映射关系,

image-20240729185213907

接着__access_remote_vm调用get_user_pages,将参数write=1 和force=1 以及 page 指针传递给get_user_pages

#define FOLL_WRITE	0x01	/* check pte is writable */
#define FOLL_TOUCH	0x02	/* mark page accessed */
#define FOLL_GET	0x04	/* do get_page on page */
#define FOLL_FORCE	0x10	/* get_user_pages read/write w/o permission */


long get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
		unsigned long start, unsigned long nr_pages, int write,
		int force, struct page **pages, struct vm_area_struct **vmas)
{
	return __get_user_pages_locked(tsk, mm, start, nr_pages, write, force,
				       pages, vmas, NULL, false, FOLL_TOUCH);
}

get_user_pages调用并传递给__get_user_pages_locked,在__get_user_pages_lockedflags被转换成 FOLL_WRITE | FOLL_FORCE |FOLL_TOUCH| FOLL_GET 标志位(因此下图中__get_user_pages的参数flag的值为23,不信可以手动算一下)

image-20240729195546097

__get_user_pages_locked调用__get_user_pages,并将flags传给__get_user_pages中的gup_flags

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)
{
	long i = 0;
	unsigned int page_mask;
	struct vm_area_struct *vma = NULL;

	if (!nr_pages)
		return 0;

	VM_BUG_ON(!!pages != !!(gup_flags & FOLL_GET));

	/*
	 * If FOLL_FORCE is set then do not force a full fault as the hinting
	 * fault information is unrelated to the reference behaviour of a task
	 * using the address space
	 */
	if (!(gup_flags & FOLL_FORCE))
		gup_flags |= FOLL_NUMA;

	do {
		struct page *page;
		unsigned int foll_flags = gup_flags;
		unsigned int page_increm;

		/* first iteration or cross vma bound */
		if (!vma || start >= vma->vm_end) {
			vma = find_extend_vma(mm, start);
			if (!vma && in_gate_area(mm, start)) {
				int ret;
				ret = get_gate_page(mm, start & PAGE_MASK,
						gup_flags, &vma,
						pages ? &pages[i] : NULL);
				if (ret)
					return i ? : ret;
				page_mask = 0;
				goto next_page;
			}

			if (!vma || check_vma_flags(vma, gup_flags))
				return i ? : -EFAULT;
			if (is_vm_hugetlb_page(vma)) {
				i = follow_hugetlb_page(mm, vma, pages, vmas,
						&start, &nr_pages, i,
						gup_flags);
				continue;
			}
		}
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);
		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();
		} else if (PTR_ERR(page) == -EEXIST) {
			/*
			 * Proper page table entry exists, but no corresponding
			 * struct page.
			 */
			goto next_page;
		} else if (IS_ERR(page)) {
			return i ? i : PTR_ERR(page);
		}
		if (pages) {
			pages[i] = page;
			flush_anon_page(vma, page, start);
			flush_dcache_page(page);
			page_mask = 0;
		}
next_page:
		if (vmas) {
			vmas[i] = vma;
			page_mask = 0;
		}
		page_increm = 1 + (~(start >> PAGE_SHIFT) & page_mask);
		if (page_increm > nr_pages)
			page_increm = nr_pages;
		i += page_increm;
		start += page_increm * PAGE_SIZE;
		nr_pages -= page_increm;
	} while (nr_pages);
	return i;
}

image-20240729200304355

第一次follow_page_mask

__get_user_pages会调follow_page_mask来寻找虚拟地址所对应的page,这里按照我们之前手动的计算结果,该虚拟地址对应的页表项为空,用户空间那段内存(dirtycow 程序中map 指针指向的内存)其实还没有和实际物理页面建立映射关系,所以 follow_page_mask函数是不可能返回正确的 page 数据结构的,这里我们进入follow_page_mask验证一下

image-20240729201027632

follow_page_mask会依次获取pgd、pmd、pud等表项,然后调用follow_page_pte跟随最后的页表项,然后根据页表项获取虚拟地址对应的page,并最终返回给follow_page_mask。我们继续跟踪follow_page_pte

image-20240729201833921

image-20240729201854705

follow_page_pte跟随获取页表项pte,发现页表项为空(与我们之前手动得到的结果一致),进入no_page_table函数

image-20240729202109291

no_page_table函数返回NULL至follow_page_ptefollow_page_pte返回NULL至follow_page_maskfollow_page_mask返回NULL至__get_user_pages

image-20240729202530700

__get_user_pages获取到的page的值为NULL,命中下面的if判断语句,调用faultin_page函数进行处理,注意传入faultin_page的是参数foll_flags的引用(地址)foll_flags的值为FOLL_WRITE | FOLL_FORCE | FOLL_GET(数值为23),我们继续跟踪faultin_page

image-20240729203202671

faultin_page根据传入的faultin_page的值设置fault_flags的值

image-20240729203438776

通过上图可以看到,在faultin_page中只命中了如下判断代码

if (*flags & FOLL_WRITE)
		fault_flags |= FAULT_FLAG_WRITE;

因此参数fault_flags被设为FAULT_FLAG_WRITE(0x01),接着在faultin_page调用handle_mm_fault,并将fault_flags=1传给handle_mm_fault的参数flags

image-20240729204712137

handle_mm_fault调用__handle_mm_fault__handle_mm_fault调用__handle_pte_fault

image-20240729205117313

__handle_pte_fault根据页表项pte的值(0,即页表项为空)调用do_fault

image-20240729205358257

do_fault根据传入的参数flags进行判断

image-20240729205650063

flags的值为1,即FAULT_FLAG_WRITE,因此调用do_cow_fault

image-20240729205811002

do_cow_fault会分配一个新的页面 new_page,并且调用__do_fault函数,__do_fault通过文件系统相关的 API 把 page 读到 fault_page 中

image-20240729210048830

这里可以看到__do_fault调用的文件系统相关的 API就是filemap_faultfilemap_fault从cache中或者从磁盘中读取文件内容相应的page放入vmf中并返回至__do_fault

image-20240729211009366

__do_fault把vmf.page(刚通过filemap_fault从cache中或者从磁盘中所读取的page)向上通过引用传递返回至do_cow_fault

image-20240729211335252

do_cow_fault通过__do_fault获取到fault_page后继续运行,然后通过下面函数把文件内容拷贝到新页面 new_page 里。注意这里 fault_page 是 page cache,new_page 是匿名页面了。

copy_user_highpage(new_page, fault_page, address, vma);

继续跟踪

image-20240729212034437

image-20240729161904279

do_set_pte函数会使用新页面new_page和虚拟地址重新建立映射关系,我们这里可以看到do_set_pte执行之前pte的值为0,do_set_pte执行之后pte的值为12987173(十六进制为 0x07bdb065),并且根据32位页表项的格式可知,此刻页表项是有效的,脏的(dirty) 的并且是只读的,使用x/s 0xC7bdb000查看内存内容可以看到字符串内容“this is not a test\n”

image-20240729212929910

接着do_cow_fault继续执行,最后把 fault_page 释放了。do_cow_fault执行完之后一路返回至faultin_page

image-20240729213446840

faultin_page返回0至__get_user_pages

image-20240729213543696

第二次follow_page_mask

__get_user_pages继续运行,根据faultin_page返回的ret(值为0),在switch语句中转至retry标签继续运行,在运行至follow_page_mask时再次发生意外。

注意这时候传递给该函数的参数 foll_flags 依然没有变化,即 FOLL_WRITE | FOLL_FORCE |FOLL_GET。该 pte entry 的属性是:PRESENT 位被置位,Dirty 位被置位,只读位 RDONLY 也被置位了。

因此在 follow_page_pte 函数中,当判断到传递进来的 flags 标志是可写的,但是实际 pte entry 只是可读属性,那么这里就不会返回正确的 page 结构了,见 follow_page_pte 函数中的(flags & FOLL_WRITE) && !pte_write(pte)语句。

struct page *follow_page_mask(struct vm_area_struct *vma,
			      unsigned long address, unsigned int flags,
			      unsigned int *page_mask)
{	
    ...
    return follow_page_pte(vma, address, pmd, flags);
}

static struct page *follow_page_pte(struct vm_area_struct *vma,
		unsigned long address, pmd_t *pmd, unsigned int flags)
{
    ...
	if ((flags & FOLL_WRITE) && !pte_write(pte)) {
		pte_unmap_unlock(ptep, ptl);
		return NULL;
	}
	...
}

从 follow_page_pte返回 NULL至follow_page_mask,follow_page_mask也返回 NULL,这时候再次进入faultin_page

image-20240729213955616

image-20240730001841505

faultin_page函数根据flags(由__get_user_pages的参数foll_flags传入)的值(即 FOLL_WRITE | FOLL_FORCE |FOLL_GET)设置fault_flags=FAULT_FLAG_WRITE(即1),传入并调用handle_mm_fault函数

image-20240730002415963

handle_mm_faultflag=FAULT_FLAG_WRITE(即1)传入并调用__handle_mm_fault

image-20240730002622962

__handle_mm_fault获取页表项pte之后,调用并将flag=FAULT_FLAG_WRITE传入handle_pte_fault

注意:通过p pte命令我们可以看到,页表项pte是存在且有效的,结合下面handle_pte_fault函数的源码

/mm/memory.c
static int __handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,
			     unsigned long address, unsigned int flags)
{
	...
	return handle_pte_fault(mm, vma, address, pte, pmd, flags);
}


int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,
		    unsigned long address, unsigned int flags)
{
	...
	ret = __handle_mm_fault(mm, vma, address, flags);

	if (flags & FAULT_FLAG_USER) {
		...
	}

	return ret;
}

static int handle_pte_fault(struct mm_struct *mm,
		     struct vm_area_struct *vma, unsigned long address,
		     pte_t *pte, pmd_t *pmd, unsigned int flags)
{
	pte_t entry;
	spinlock_t *ptl;
	entry = *pte;
	barrier();
	if (!pte_present(entry)) {
		...
	}
    ...
    if (flags & FAULT_FLAG_WRITE) {
		if (!pte_write(entry))
			return do_wp_page(mm, vma, address,
					pte, pmd, ptl, entry);
		...
	}
	...
}

handle_pte_fault函数中的判断语句if (!pte_present(entry))不会命中,而判断语句if (flags & FAULT_FLAG_WRITE)会命中,跳转到do_wp_page函数

static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,
		unsigned long address, pte_t *page_table, pmd_t *pmd,
		spinlock_t *ptl, pte_t orig_pte)
	__releases(ptl)
{
	struct page *old_page;

	old_page = vm_normal_page(vma, address, orig_pte);
    if (PageAnon(old_page) && !PageKsm(old_page)) {
        ...
        if (reuse_swap_page(old_page)) {
            /*
             * The page is all ours.  Move it to our anon_vma so
             * the rmap code will not search our parent or siblings.
             * Protected against the rmap code by the page lock.
             */
            page_move_anon_rmap(old_page, vma, address);
            unlock_page(old_page);
            return wp_page_reuse(mm, vma, address, page_table, ptl,
                         orig_pte, old_page, 0, 0);
        }
     	...
    } 
    ...
}


static inline int wp_page_reuse(...)
{
	...
	return VM_FAULT_WRITE;
}

do_wp_page函数中,找到页表项orig_pte所映射的页old_page(即上文提到的new_page),此页是匿名页面并且是可以重用的页面(reuse),因此调用wp_page_reuse函数,wp_page_reuse函数最终会返回VM_FAULT_WRITEdo_wp_page,注意这里返回的值是 VM_FAULT_WRITE,这个是漏洞的关键所在。

VM_FAULT_WRITE经由wp_page_reusedo_wp_pagehandle_pte_fault__handle_mm_faulthandle_mm_fault一路返回至faultin_page的变量ret

image-20240730014108109

static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma,
		unsigned long address, unsigned int *flags, int *nonblocking)
{
	struct mm_struct *mm = vma->vm_mm;
	unsigned int fault_flags = 0;
	int ret;
	
    ...
        
	ret = handle_mm_fault(mm, vma, address, fault_flags);
	
    ...
        
	if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
		*flags &= ~FOLL_WRITE;
	return 0;
}

faultin_page函数继续执行,根据ret == VM_FAULT_WRITEVMA 为只读(VM_WRITE)的情况,清除了flagFOLL_WRITE 标记位,这里是该漏洞的核心之处

image-20240730014815826

最后faultin_page函数return 0__get_user_pages函数的变量ret__get_user_pages根据switch(ret)第二次跳转至标签retry处接着继续执行

image-20240730015108185

因为刚刚foll_flags ( 即上文中的 flag ) 中的 FOLL_WRITE 被拿掉了,所以这时是以只读的方式去调用follow_page_mask
了,正如武侠小说里写的一样,两大绝顶高手交锋正酣大战三百回合不分胜负,说时迟那时快,就在调用 follow_page_mask之前另外一个线程madviseThread像小李飞刀一样精准,注意retry标签处有一个cond_resched函数给小李飞刀一次出飞刀的机会,madvise(dontneed)系统调用在内核里的zap_page_range函数会去解除该页的映射关系

为了调试方便,我们使用gdb调试器中的set *(unsigned int *)0xc61c0ff0 = 0命令将页表项清零,来等效代替madvise(dontneed)

第三次follow_page_mask

这样在调用follow_page_mask时,又会像第一次调用 follow_page_mask那样因为页表项为空而返回NULL至变量page,我们进入 follow_page_mask来验证一下

根据上图,因为刚刚foll_flags ( 即上文中的 flag ) 中的 FOLL_WRITE 被拿掉了,因此传入follow_page_mask函数的参数flag = 22

#define FOLL_WRITE	0x01	/* check pte is writable */
#define FOLL_TOUCH	0x02	/* mark page accessed */
#define FOLL_GET	0x04	/* do get_page on page */
#define FOLL_FORCE	0x10	/* get_user_pages read/write w/o permission */

image-20240730022731681

follow_page_mask函数一路执行,最后把flag=22等参数传入并调用follow_page_pte函数

image-20240730023201083

follow_page_pte函数因为取到的页表项pte为 0,因此跳转至标签no_page:处(由于编译器优化,gdb调试未能显示出来)并接着调用no_page_table函数

image-20240730023630056

no_page_table未命中条件判断最终返回NULL至follow_page_ptefollow_page_pte返回NULL至follow_page_maskfollow_page_mask返回NULL至__get_user_pages的变量page,

image-20240730024155914

__get_user_pages函数接着命中条件语句 if (!page),调用faultin_page函数,传foll_flags = 22faultin_page函数的参数flags

image-20240730024759760

faultin_page函数根据flags将变量fault_flags的值设置为0,一路经过handle_mm_fault__handle_mm_faulthandle_pte_fault最终传入do_fault函数的形参flags

image-20240730025805702

do_fault函数根据flags调用do_read_fault函数

image-20240730030323879

image-20240730031201086

//如果map_pages函数不为空并且fault_around_bytes有效,
//map_pages是预读的操作函数,fault_around_bytes控制预读长度,一般64k
if (vma->vm_ops->map_pages && fault_around_bytes >> PAGE_SHIFT > 1) {
	//调用do_fault_around预读几个页的文件内容读取到vmf->page,为了减少页错误异常的次数
	do_fault_around(vma, address, pte, pgoff, flags);
	if (!pte_same(*pte, orig_pte))
			goto unlock_out;
    ...
}
原文链接:https://blog.csdn.net/sinat_22338935/article/details/118001063

do_read_fault函数调用do_fault_around函数预读取虚拟地址address周围的几页,之后判断新页表项与旧页表项是否不同(旧页表项为0,因此肯定不同),最终执行到标签unlock_out处,最终return ret

image-20240730033540887

do_fault_around函数执行完之后,页表项有效,且页表项所映射的页为文件的page cache,我们手动打印了新页表项的值,并根据物理地址获取到了page cache的内容,即字符串“this is not a test\n”。

之后do_read_fault函数将ret = 0(ret初始化为0,且此过程中未变过)经由do_faulthandle_pte_fault__handle_mm_faulthandle_mm_fault一路返回至faultin_page函数的ret变量

image-20240730034847939

faultin_page函数最终return 0__get_user_pages函数的变量ret

image-20240730035156513

第四次follow_page_mask

__get_user_pages根据条件语句switch (ret)ret的值为0,跳转至标签retry处,继续执行,第四次调用follow_page_mask函数

image-20240730035605007

follow_page_mask函数调用follow_page_pte函数

image-20240730040017131

这次follow_page_pte函数执行的很顺利,能够取到有效的页表项pte,因此也能获取到正确的page我们手动输出也能确定这一点。

image-20240730041259150

follow_page_pte将取到的有效page通过follow_page_mask传至__get_user_pages

__get_user_pages终于能够跳出if (!page) switch (ret)和标签retry所形成的循环,最终返回至get_user_pages

image-20240730041649485

get_user_pages又返回至__access_remote_vm

image-20240730042355676

__access_remote_vm最终调用copy_to_user_page函数,即memcpy函数将物理地址为0xc69a1000的数据(即"youaremodified)写入物理地址0xc61b3000

image-20240730042705405

memcpy__access_remote_vm函数执行完之后再次使用continue命令则程序运行结束

image-20240729020934478

在虚拟机中查看文件内容,成功被修改。

漏洞利用思路

基本思路就是在一个进程中创建两个线程,一个线程向只读的映射内存通过write系统调用写入数据,这时候发生写时复制,另一个线程通过madvice来丢弃映射的私有副本,两个线程相互竞争从而向只读文件写入数据。

主线程

  • 普通用户身份以只读模式打开指定的只读文件
  • 使用MAP_PRIVATE参数映射内存
  • 找到目标文件映射的内存地址
  • 创建两个线程

procselfmem线程

  • 向文件映射的内存区域写数据
  • 此时内核采用COW机制

madvise线程

  • 使用MADV_DONTNEED参数调用madvise来释放文件映射内存区
  • 干扰procselfmem线程的COW过程,产生竞争条件
  • 当竞争条件发生时就能成功将数据写入文件

原理总结

正常流程

  • 第一次调用follow_page_mask函数,查到页表项pte为空,因此需要在faultin_page进行缺页处理:分配一个新的页表(匿名页),把file对应的 cache 页复制到新页中,建立该新页与页表项的映射,设置页表项为有效的,脏的(dirty) 的并且是只读
  • 第二次调用follow_page_mask函数,由于当前操作为写操作,即flags为 FOLL_WRITE | FOLL_FORCE |FOLL_GET,但是页表项为只读的,所以该页不具有写权限,需要在faultin_page进行处理,因该页为匿名页,所以去掉flagsFOLL_WRITE标签
  • 第三次调用follow_page_mask函数,此时flags已经无 FOLL_WRITE 标签,并且页表项有效,因此能够成功顺利返回有效的 page,成功!此时的page不是filecache页,是对cache页复制的匿名页,所以对page的任何修改不会造成文件的更改。

POC流程

  • 第一次调用follow_page_mask函数,查到页表项pte为空,因此需要在faultin_page进行缺页处理:分配一个新的页表(匿名页),把file对应的cache页复制到新页中,建立该新页与页表项的映射,设置页表项为有效的,脏的(dirty) 的并且是只读
  • 第二次调用follow_page_mask函数,由于当前操作为写操作,即flags为 FOLL_WRITE | FOLL_FORCE |FOLL_GET,但是页表项为只读的,所以该页不具有写权限,需要在faultin_page进行处理,因该页为匿名页,所以去掉flagsFOLL_WRITE标签
  • 第2次调用follow_page_mask完成后,第3次调用follow_page_mask函数,另一个线程通过madvise函数释放上一步分配的COW页(在我们调试中,通过手动清空页表项模拟 madvise 函数)
  • 第三次调用follow_page_mask函数,因为另一个线程释放了分配的页,所以页表项为空,进入faultin_page进行缺页处理,因为此时flags已经无 FOLL_WRITE 标签,所以读取file的内容,并建立file对应的cache页与页表项的映射
  • 第四次调用follow_page_mask函数,此时flags已经无 FOLL_WRITE 标签,并且页表项有效,能够成功顺利返回有效的 page,成功!此时的pagefilecache页,所以对page的任何修改都会造成文件的更改。

参考致谢:

gup.c - mm/gup.c - Linux source code v4.4.6 - Bootlin

经典内核漏洞复现之 dirtycow | blingbling's blog (blingblingxuanxuan.github.io)

DirtyCow学习与调试记录-安全客 - 安全资讯平台 (anquanke.com)

回眸——CVE-2016-5195 脏牛漏洞浅析-安全客 - 安全资讯平台 (anquanke.com)

linux內核提权漏洞CVE-2016-5195-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com

linux提权之dirtycow_linux脏牛-CSDN博客

Dirty COW漏洞原理与简单利用_dirtycow-CSDN博客

经典内核漏洞复现之 dirtycow | blingbling's blog (blingblingxuanxuan.github.io)

条件竞争学习 之 DirtyCow分析 | Clang裁缝店 (xuanxuanblingbling.github.io)

奔跑吧-linux内核-内存管理-DirtyCow.pdf

标签:fault,pte,page,pages,flags,内核,cnblog,follow,DirtyCOW
From: https://www.cnblogs.com/xing1223/p/18366200

相关文章

  • Win7/Win10/Win11开启本地内核调试的方法
    具体内容微软官方文档上都有:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/performing-local-kernel-debugginghttps://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/setting-up-local-kernel-debugging-of-a-single-computer-manually......
  • Linux内核状态 system_state
    基于msm-5.4一、定义/*用于system_state的值,状态的顺序不得更改,代码检查时会用<、<=、>、>=STATE*/externenumsystem_states{//include/linux/kernel.hSYSTEM_BOOTING,//0,最初没有赋值的时候的默认值SYSTEM_SCHEDULING,SYSTEM_RUNNING,SYSTE......
  • windows 内核驱动通过哪些方式直接访问硬件
    Windows内核驱动可以通过以下几种主要方式直接访问硬件:1.内存映射I/O(Memory-MappedI/O,MMIO)  -使用MmMapIoSpace函数将物理地址映射到虚拟地址空间  -直接读写映射后的内存地址来访问硬件寄存器  示例:  ```c  PHYSICAL_ADDRESSphysicalAddre......
  • Linux--内核驱动框架(以字符设备驱动为例)
    下面将介绍一个简单的Linux内核驱动程序(部分),用于处理一个假设的字符设备(鼠标、键盘、串口、LED...),具体介绍如下:1头文件导入#include<linux/fs.h>//用于声明file_operations结构体和其他文件系统相关函数。#include<linux/module.h>//用于声明module_i......
  • Centos 7/8内核升级
    目录背景yum安装添加ELRepo仓库查看是否安装库成功:导入签名的密钥选择安装内核切换默认内核版本centos8设置新内核centos7设置新内核RPM安装查找版本下载内核RPM安装内核确认已安装内核版本设置启动背景默认在centos8.x版本上的内核版本为4.18,这个版本还是比较低的在操作软件时......
  • CentOS 7 停服后(2024-06-30)升级最新的Linux 内核
     1、CentOS7更新 USTC的源sudosed-i.bak\-e's|^mirrorlist=|#mirrorlist=|g'\-e's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirrors.ustc.edu.cn/centos-vault/centos|g'\/etc/yum.repos.d/CentOS-Base.repo 2......
  • linux内核模块 字符设备驱动模板
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、linux内核模块是什么?二、代码示例总结前言提示:这里可以添加本文要记录的大概内容:内核版本5.10.92linux内核模块字符设备驱动模板cdev注册字符设备,创建一个/dev/下设备节点和/sy......
  • 内核链表常用宏——container_of()
    定义#definelist_entry(ptr,type,member)\ container_of(ptr,type,member)#defineoffsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)#definecontainer_of(ptr,type,member)({ \ consttypeof(((type*)0)->member)*__mptr=(ptr); ......
  • windows核心编程 第三章,跨越进程边界共享内核对象,对象句柄的继承性,改变句柄的标志,命名
    windows核心编程3.3跨越进程边界共享内核对象3.3.1对象句柄的继承性3.3.2改变句柄的标志3.3.3命名对象3.3.4终端服务器的名字空间3.3.5复制对象句柄文章目录windows核心编程3.3跨越进程边界共享内核对象3.3.1对象句柄的继承性3.3.2改变句柄的标志3.3.3命名......
  • 【Linux系列】内核参数
    sysctl命令常用参数RAID性能参数调优网络协议栈调整:单位是字节TCP并发性能优化对于用不上IPV6的建议直接禁用TCPkeepalive时长控制memoryOOM控制安全防护模块保障TCP通信质量IO密集性服务器优化参数路由器选项控制路由机制控制内存大页面使用策略内核参数主要......