xorl %eax, %eax

Linux kernel NOMMU fput() NULL Pointer Dereference

leave a comment »

This post is about a neat bug from 2.6.32-rc6’s ChangeLog file, specifically, this one. It was discovered and reported by David Howells of Red Hat and it was fixed in 2.6.32-rc6 release of the Linux kernel. Here is some code from 2.6.31’s mm/nommu.c file.

/*
 * handle mapping creation for uClinux
 */
unsigned long do_mmap_pgoff(struct file *file,
                            unsigned long addr,
                            unsigned long len,
                            unsigned long prot,
                            unsigned long flags,
                            unsigned long pgoff)
{
        struct vm_area_struct *vma;
        struct vm_region *region;
        struct rb_node *rb;
        unsigned long capabilities, vm_flags, result;
        int ret;
       ...
error:
        fput(region->vm_file);
        kmem_cache_free(vm_region_jar, region);
        fput(vma->vm_file);
        if (vma->vm_flags & VM_EXECUTABLE)
                removed_exe_file_vma(vma->vm_mm);
        kmem_cache_free(vm_area_cachep, vma);
        kleave(" = %d", ret);
        return ret;
       ...
}
EXPORT_SYMBOL(do_mmap_pgoff);

If the allocation of ‘region->vm_file’ area fails in do_mmap_pgoff() routine in a no-MMU case (such as uClinux), pointer ‘region->vm_file’ would be NULL and the error handling code under ‘error’ label will attempt to access/dereference it directly when calling fput() function. As you might already know, is a simple wrapper around __fput() which can be found at fs/file_table.c like this:

void fput(struct file *file)
{
        if (atomic_long_dec_and_test(&file->f_count))
                __fput(file);
}

Basically, it’s just using an atomic operation to decrement the reference counter of that file and invoke __fput() to release that file pointer. D. Howells also provided a trigger PoC code that causes instant kernel OOPS. The code is quite simple…

int main() { static long long a[1024 * 1024 * 20] = { 0 }; return a;}

What it does is requesting a huge amount of memory in order to trigger the ‘error’ code execution and consequently, the NULL pointer dereference during the attempt to decrement the reference counter in fput(). As you might have been expecting, the patch was of course…

 error:
-	fput(region->vm_file);
+	if (region->vm_file)
+		fput(region->vm_file);
 	kmem_cache_free(vm_region_jar, region);
-	fput(vma->vm_file);
+	if (vma->vm_file)
+		fput(vma->vm_file);
 	if (vma->vm_flags & VM_EXECUTABLE)
 		removed_exe_file_vma(vma->vm_mm);

Quite obvious NULL pointer checks.

Written by xorl

November 5, 2009 at 22:13

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