漏洞链接
https://nvd.nist.gov/vuln/detail/CVE-2010-4169
bugzilla链接
https://bugzilla.redhat.com/show_bug.cgi?id=651671
patch链接
其他分析
https://xorl.wordpress.com/2010/12/01/cve-2010-4169-linux-kernel-perf_event_mmap-use-after-free/
漏洞分析
漏洞成因
参考其他链接:
在kernel的mm/mprotect.c中存在use-after-free漏洞。
SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
unsigned long, prot)
{
unsigned long vm_flags, nstart, end, tmp, reqprot;
struct vm_area_struct *vma, *prev;
int error = -EINVAL;
...
vma = find_vma_prev(current->mm, start, &prev);
...
for (nstart = start ; ; ) {
...
if (tmp > end)
tmp = end;
error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
if (error)
goto out;
perf_event_mmap(vma);
nstart = tmp;
...
out:
up_write(¤t->mm->mmap_sem);
return error;
}
After obtaining a pointer to the VMA that will be processed, it will enter a loop that will execute the actual mprotect(2) code. Inside that loop you can see a call to mprotect_fixup() which is a routine located in the same source code file that could lead to the following code path.(在访问VMA之后,会进入一个循环,在这个循环中有函数mprotect_fixup(),下面是mprotect_fixup()的定义:)
int
mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
unsigned long start, unsigned long end, unsigned long newflags)
{
struct mm_struct *mm = vma->vm_mm;
unsigned long oldflags = vma->vm_flags;
...
/*
* First try to merge with previous and/or next vma.
*/
pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
*pprev = vma_merge(mm, *pprev, start, end, newflags,
vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma));
if (*pprev) {
vma = *pprev;
goto success;
}
...
fail:
vm_unacct_memory(charged);
return error;
}
So, the call to vma_page() leads to vma_adjust() that could free the passed VMA in order to merge previous and possibly next VMAs. However, the following call of perf_event_mmap() will attempt to access the freed VMA pointer as we can find at kernel/perf_event.c file.(vma_adjust()会free VMA,但是perf_event_mmap()会试图去访问freed VMA pointer)
void perf_event_mmap(struct vm_area_struct *vma)
{
struct perf_mmap_event mmap_event;
if (!atomic_read(&nr_mmap_events))
return;
mmap_event = (struct perf_mmap_event){
.vma = vma,
/* .file_name */
/* .file_size */
.event_id = {
.header = {
.type = PERF_RECORD_MMAP,
.misc = PERF_RECORD_MISC_USER,
/* .size */
},
/* .pid */
/* .tid */
.start = vma->vm_start,
.len = vma->vm_end - vma->vm_start,
.pgoff = (u64)vma->vm_pgoff << PAGE_SHIFT,
},
};
perf_event_mmap_event(&mmap_event);
}
所以会发生use-after-free。
patch
To fix this, the perf_event_mmap() call was removed from mprotect(2) and placed in the ‘success’ code of mprotect_fixup().
将访问VMA的函数从SYSCALL_DEFINE3中移除,放在mprotect_fixup()的“success”里面。
所以是avoid use,使得use不再发生。