Loading
0

深入解读脏牛Linux本地提权漏洞(CVE-2016-5195)

unlock_page(old_page);
return wp_page_reuse(mm, vma, address, page_table, ptl,
orig_pte, old_page, 0, 0);
}
unlock_page(old_page);
}
}

static inline int wp_page_reuse(struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long address,
pte_t *page_table, spinlock_t *ptl, pte_t orig_pte,
struct page *page, int page_mkwrite,
int dirty_shared)
{
entry = maybe_mkwrite(pte_mkdirty(entry), vma); 带_RW_DIRTY,不带_PAGE_RW
if (ptep_set_access_flags(vma, address, page_table, entry, 1))
update_mmu_cache(vma, address, page_table);

return VM_FAULT_WRITE;
}
这里需要关注的是wp_page_reuse的返回值是VM_FAULT_WRITE,即handle_mm_fault返回VM_FAULT_WRITE,在faultin_page函数中会去掉查找标志FOLL_WRITE,然后返回0。
static int faultin_page(...)
{
ret = handle_mm_fault(mm, vma, address, fault_flags); /* 返回 VM_FAULT_WRITE */

/* 去掉FOLL_WRITE标记, */
if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
*flags &= ~FOLL_WRITE;
return 0;
}
第三次查找
在上一次处理查找失败的过程中FOLL_WRITE被去掉了,所以这一次的follow_page_mask会成功返回之前分配的page。到这里写时复制过程就算完成了。
madvise(MADV_DONTNEED)
madvise系统调用的作用是给系统对于内存使用的一些建议,MADV_DONTNEED参数告诉系统未来不访问该内存了,内核可以释放内存页了。内核函数madvise_dontneed中会移除指定范围内的用户空间page。
static long madvise_dontneed(struct vm_area_struct *vma,
struct vm_area_struct **prev,
unsigned long start, unsigned long end)
{
...
zap_page_range(vma, start, end - start, NULL);
return 0;
}

void zap_page_range(struct vm_area_struct *vma, unsigned long start,
unsigned long size, struct zap_details *details)
{
...
for ( ; vma && vma->vm_start vm_next)
unmap_single_vma(&tlb, vma, start, end, details);
...
}
产生竞态条件
我们再来梳理一下写时复制的过程中调页的过程:
1. 第一次follow_page_mask(FOLL_WRITE),因为page不在内存中,进行pagefault处理。
2. 第二次follow_page_mask(FOLL_WRITE),因为page没有写权限,并去掉FOLL_WRITE。
3. 第三次follow_page_mask(无FOLL_WRITE),成功。
\__get_user_pages函数中每次查找page前会先调用cond_resched()线程调度一下,这样就引入了竞态条件的可能性。在第二次分配COW页成功后,FOLL_WRITE标记已经去掉,如果此时,另一个线程把page释放了,那么第三次由于page不在内存中,又会进行调页处理,由于不带FOLL_WRITE标记,不会进行COW操作,此时get_user_pages得到的page带\__PAGE_DIRTY,竞态条件就是这样产生的,流程如下:
1. 第一次follow_page_mask(FOLL_WRITE),page不在内存中,进行pagefault处理。
2. 第二次follow_page_mask(FOLL_WRITE),page没有写权限,并去掉FOLL_WRITE。
3. 另一个线程释放上一步分配的COW页
4. 第三次follow_page_mask(无FOLL_WRITE),page不在内存中,进行pagefault处理。
5. 第四次follow_page_mask(无FOLL_WRITE),成功返回page,但没有使用COW机制。
0x03 漏洞利用
https://github.com/dirtycow/dirtycow.github.io/blob/master/dirtyc0w.c
这个是利用/proc/self/mem来修改只读文件的exploit
https://github.com/dirtycow/dirtycow.github.io/blob/master/pokemon.c
这个是利用ptrace(PTRACE_POKETEXT)来修改只读文件的exploit
https://github.com/timwr/CVE-2016-5195
这个是Andriod系统Root的exploit
0x04 漏洞修复
该漏洞patch的链接:[https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619]。现在不再是把FOLL_WRITE标记去掉,而是添加了一个FOLL_COW标志来表示获取一个COW分配的页。即使是竞态条件破坏了一次完整的获取页的过程,但是因为FOLL_WRITE标志还在,所以会重头开始分配一个COW页,从而保证该过程的完整性。
diff --git a/include/linux/mm.h b/include/linux/mm.h
index e9caec6..ed85879 100644
--- a/include/linux/mm.h

分页阅读: 1 2 3 4 5
【声明】:8090安全小组门户(https://www.8090-sec.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们:邮箱hack@ddos.kim,我们会在最短的时间内进行处理。