Loading
0

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

{
do {
retry:
cond_resched(); /* 进程调度 */
...
page = follow_page_mask(vma, start, foll_flags, &page_mask); /* 查找虚拟地址的page */
if (!page) {
ret = faultin_page(tsk, vma, start, &foll_flags, nonblocking); /* 处理失败的查找 */
switch (ret) {
case 0:
goto retry;
}
}
if (page)
加入page数组
} while (nr_pages);
}
该函数通过follow_page_mask去查找虚拟地址对应的page,如果找不到就进入faultin_page处理。这里可能会重复几次,直到找到page或发生错误为止。另外由于每次循环会先调用cond_resched()进行线程调度,所以才会出现多线程的竞态条件的可能。
第一次查找页
follow_page_mask
该函数用来通过进程虚拟地址沿着pgd、gud、gmd、pte一路查找page。因为是第一次访问映射的内存区域,此时页表是空的,返回NULL,然后外层函数进入faultin_page过程去调页。
struct page *follow_page_mask(
struct vm_area_struct *vma, /* [IN] 虚拟地址所在的vma */
unsigned long address, /* [IN] 待查找的虚拟地址 */
unsigned int flags, /* [IN] 标记 */
unsigned int *page_mask /* [OUT] 返回页大小 */
)
{
...
return no_page_table(vma, flags);
...
}

static struct page *no_page_table(struct vm_area_struct *vma,
unsigned int flags)
{
if ((flags & FOLL_DUMP) && (!vma->vm_ops || !vma->vm_ops->fault))
return ERR_PTR(-EFAULT);
return NULL;
}
faultin_page
该函数完成follow_page_mask找不到page的处理。第一次查找时页还不在内存中,首先设置FAULT_FLAG_WRITE标记,然后沿着handle_mm_fault -> \__handle_mm_fault -> handle_pte_fault -> do_fault -> do_cow_fault分配页。
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;

if (*flags & FOLL_WRITE)
fault_flags |= FAULT_FLAG_WRITE; /* 标记失败的原因 WRITE */
...
ret = handle_mm_fault(mm, vma, address, fault_flags); /* 第一次分配page并返回 0 */
...
return 0;

}

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)
{
if (!pte_present(entry))
if (pte_none(entry))
return do_fault(mm, vma, address, pte, pmd, flags, entry); /* page不在内存中,调页 */
}

static int do_fault(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd,
unsigned int flags, pte_t orig_pte)
{
if (!(vma->vm_flags & VM_SHARED)) /* VM_PRIVATE模式,使用写时复制(COW)分配页 */
return do_cow_fault(mm, vma, address, pmd, pgoff, flags,
orig_pte);
}

static int do_cow_fault(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pmd_t *pmd,
pgoff_t pgoff, unsigned int flags, pte_t orig_pte)
{
new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, address); /* 分配一个page */

ret = __do_fault(vma, address, pgoff, flags, new_page, &fault_page,

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