xorl %eax, %eax

CVE-2007-6434: Bypassing mmap_min_addr

with one comment

I really like bugs that don't get much attention but are still important for the average attacker. This is one of them. It was discovered by Eric Paris and published on 4 December of 2007. This issue affects Linux kernel up to 2.6.24-rc4 release, configured with SECURITY_DEFAULT_MMAP_MIN_ADDR option which is enabled by default on the latest kernels. First of all, a few words about this option/feature. Since 1994 it is known that NULL pointer dereferences can be exploitable conditions by using the completely valid address (void *)0x0 which is the NULL on most popular operating systems such as Linux, *BSD, Windows, Solaris etc. Recently, Linux kernel developers decided that they should limit the mmap(2)'able pages from the userland to avoid mappings of the NULL for exploitation purposes. This is why the above option was made. You can manipulate it either directly from /proc/sys/vm/mmap_min_addr or using sysctl(8) utility as option vm.mmap_min_addr. When it is set to 0 there is no limitation on pages requested by the user space processes but on newer releases it is set by default to 64 which stands for 64KB. That is, 64,000 Bytes from the first page (which is 0x0) cannot be mapped from user space processes. This should normally limit the user from mapping lower addresses that are restricted from MMAP_MIN_ADDR.

This bug (CVE-2007-6434) provides a way to bypass this restriction. Let's see how... The vulnerablity starts at do_brk() internal function. This function is called when more memory is needed on the heap segment for the user space or when larger chunks (that are not available on the heap's bins) of memory need to be provided by the kernel. Thus, it can be reached indirectly with calls to malloc(3) or similar dynamic memory management routines or almost directly with mmap(2) system call. The following code is taken from 2.6.23 (mm/mmap.c) Linux kernel release:

1922 unsigned long do_brk(unsigned long addr, unsigned long len)
1923 {
1924         struct mm_struct * mm = current->mm;

A quick overview diagram of the calls that do_brk() performs is this:

  PAGE_ALIGN(len);
  arch_mmap_check(addr, len, flags);
  verify_mm_writelocked(mm);
  // Perform the appropriate allocation
  return addr;

So.. actually there is NO check for the restricted addresses on do_brk(). Using a large chunk request through mmap(2) for anonymous mapping (MAP_ANONYMOUS option) an attacker can reach this function and if the requested start address (first argument of mmap(2)) is available, or maybe forced with MAP_FIXED option, attacker can map a restricted by mmap_min_addr address. This was patched simply but adding this line:

if (is_hugepage_only_range(mm, addr, len))
  return -EINVAL;

+ error = security_file_mmap(0, 0, 0, 0, addr, 1);
+ if (error)
+   return error;
+

flags = VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;

Where this additional function is an inline routine located at include/linux/security.h and is used to check the security options of the requsted page simply as a wrapper to file_mmap() function pointer like this:

1822 static inline int security_file_mmap (struct file *file, unsigned long reqprot,
1823                                      unsigned long prot,
1824                                      unsigned long flags,
1825                                      unsigned long addr,
1826                                      unsigned long addr_only)
1827 {
1828        return security_ops->file_mmap (file, reqprot, prot, flags, addr,
1829                                        addr_only);
1830 }

You can prove the above assumption on a system with mmap_min_addr enabled and kernel up to 2.6.24-rc4 if you allocate so much space on the heap that kernel will call do_brk() and return addresses that would be normally be restricted by the above option or if you expand the stack so much that kernel will have to expand heap as well on lower addresses and thus you'll get the same effect where lower heap addresses will be accessible to the user space.

Written by xorl

January 20, 2009 at 00:43

Posted in bugs, linux

One Response

Subscribe to comments with RSS.

  1. Another trick to bypass it is there.

    Julien

    July 10, 2009 at 11:22


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