xorl %eax, %eax

CVE-2010-4169: Linux kernel perf_event_mmap() Use After Free

leave a comment »

This vulnerability was disclosed by Eugene Teo of Red Hat and you can read the buggy code at mm/mprotect.c where the mprotect(2) system call’s code resides.

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(&current->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.

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.

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);
}

To fix this, the perf_event_mmap() call was removed from mprotect(2) and placed in the ‘success’ code of mprotect_fixup().

Written by xorl

December 1, 2010 at 08:21

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s