Loading
0

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

do_set_pte(vma, address, new_page, pte, true, true); /* 设置new_page的PTE */
}

static int __do_fault(struct vm_area_struct *vma, unsigned long address,
pgoff_t pgoff, unsigned int flags,
struct page *cow_page, struct page **page,
void **entry)
{
ret = vma->vm_ops->fault(vma, &vmf);
}

void do_set_pte(struct vm_area_struct *vma, unsigned long address,
struct page *page, pte_t *pte, bool write, bool anon)
{
pte_t entry;

flush_icache_page(vma, page);
entry = mk_pte(page, vma->vm_page_prot);
if (write)
entry = maybe_mkwrite(pte_mkdirty(entry), vma); /* 带_RW_DIRTY,不带_PAGE_RW */
if (anon) { /* anon = 1 */
page_add_new_anon_rmap(page, vma, address, false);
} else {
inc_mm_counter_fast(vma->vm_mm, mm_counter_file(page));
page_add_file_rmap(page);
}

set_pte_at(vma->vm_mm, address, pte, entry);
}

static inline pte_t maybe_mkwrite(pte_t pte, struct vm_area_struct *vma)
{
if (likely(vma->vm_flags & VM_WRITE)) /* 因为是只读的,所以pte不带_PAGE_RW标记 */
pte = pte_mkwrite(pte);
return pte;
}
到这里第一次查询和pagefault处理结束,已经在内存中分配好了,该页是只读的匿名页。
第二次查找
在这次的查找中因为flags带有FOLL_WRITE标记,而page是只读的,此时follow_page_mask返回NULL,进入faultin_page。
struct page *follow_page_mask(...)
{
return follow_page_pte(vma, address, pmd, flags);
}

static struct page *follow_page_pte(...)
{
if ((flags & FOLL_WRITE) && !pte_write(pte)) { /* 查找可写的页,但是该页是只读的 */
pte_unmap_unlock(ptep, ptl);
return NULL;
}
}
在处理faultin_page过程中,我们沿着函数调用路径faultin_page -> handle_mm_fault -> \__handle_mm_fault -> handle_pte_fault一路找来,在handle_pte_fault中因为没有写访问权限,会进入do_wp_page函数中:
static int handle_pte_fault(...)
{
if (flags & FAULT_FLAG_WRITE) /* faultin_page函数开头设置了该标志 */
if (!pte_write(entry))
return do_wp_page(mm, vma, address, pte, pmd, ptl, entry);
}
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)
{
old_page = vm_normal_page(vma, address, orig_pte); /* 得到之前分配的只读页,该页是匿名的页 */

if (PageAnon(old_page) && !PageKsm(old_page)) {
int total_mapcount;
if (reuse_swap_page(old_page, &total_mapcount)) { /* old_page只有自己的进程在使用,直接使用就行了,不用再复制了 */
if (total_mapcount == 1) {
/*
* 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);
}

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