diff options
author | Akira Takeuchi <takeuchi.akr@jp.panasonic.com> | 2013-11-12 18:08:21 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-12-04 13:56:39 -0500 |
commit | 3cbafaa72d3afc4a4177f0d76178cd26a525589b (patch) | |
tree | 68c0918c8d37ccb9e19c1ecc1660bab4939192e5 /mm | |
parent | df3e475e43ae5e2112a464335c28368092d53edd (diff) |
mm: ensure get_unmapped_area() returns higher address than mmap_min_addr
commit 2afc745f3e3079ab16c826be4860da2529054dd2 upstream.
This patch fixes the problem that get_unmapped_area() can return illegal
address and result in failing mmap(2) etc.
In case that the address higher than PAGE_SIZE is set to
/proc/sys/vm/mmap_min_addr, the address lower than mmap_min_addr can be
returned by get_unmapped_area(), even if you do not pass any virtual
address hint (i.e. the second argument).
This is because the current get_unmapped_area() code does not take into
account mmap_min_addr.
This leads to two actual problems as follows:
1. mmap(2) can fail with EPERM on the process without CAP_SYS_RAWIO,
although any illegal parameter is not passed.
2. The bottom-up search path after the top-down search might not work in
arch_get_unmapped_area_topdown().
Note: The first and third chunk of my patch, which changes "len" check,
are for more precise check using mmap_min_addr, and not for solving the
above problem.
[How to reproduce]
--- test.c -------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/errno.h>
int main(int argc, char *argv[])
{
void *ret = NULL, *last_map;
size_t pagesize = sysconf(_SC_PAGESIZE);
do {
last_map = ret;
ret = mmap(0, pagesize, PROT_NONE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
// printf("ret=%p\n", ret);
} while (ret != MAP_FAILED);
if (errno != ENOMEM) {
printf("ERR: unexpected errno: %d (last map=%p)\n",
errno, last_map);
}
return 0;
}
---------------------------------------------------------------
$ gcc -m32 -o test test.c
$ sudo sysctl -w vm.mmap_min_addr=65536
vm.mmap_min_addr = 65536
$ ./test (run as non-priviledge user)
ERR: unexpected errno: 1 (last map=0x10000)
Signed-off-by: Akira Takeuchi <takeuchi.akr@jp.panasonic.com>
Signed-off-by: Kiyoshi Owada <owada.kiyoshi@jp.panasonic.com>
Reviewed-by: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'mm')
-rw-r--r-- | mm/mmap.c | 10 |
1 files changed, 5 insertions, 5 deletions
@@ -1853,7 +1853,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, | |||
1853 | struct vm_area_struct *vma; | 1853 | struct vm_area_struct *vma; |
1854 | struct vm_unmapped_area_info info; | 1854 | struct vm_unmapped_area_info info; |
1855 | 1855 | ||
1856 | if (len > TASK_SIZE) | 1856 | if (len > TASK_SIZE - mmap_min_addr) |
1857 | return -ENOMEM; | 1857 | return -ENOMEM; |
1858 | 1858 | ||
1859 | if (flags & MAP_FIXED) | 1859 | if (flags & MAP_FIXED) |
@@ -1862,7 +1862,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, | |||
1862 | if (addr) { | 1862 | if (addr) { |
1863 | addr = PAGE_ALIGN(addr); | 1863 | addr = PAGE_ALIGN(addr); |
1864 | vma = find_vma(mm, addr); | 1864 | vma = find_vma(mm, addr); |
1865 | if (TASK_SIZE - len >= addr && | 1865 | if (TASK_SIZE - len >= addr && addr >= mmap_min_addr && |
1866 | (!vma || addr + len <= vma->vm_start)) | 1866 | (!vma || addr + len <= vma->vm_start)) |
1867 | return addr; | 1867 | return addr; |
1868 | } | 1868 | } |
@@ -1901,7 +1901,7 @@ arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0, | |||
1901 | struct vm_unmapped_area_info info; | 1901 | struct vm_unmapped_area_info info; |
1902 | 1902 | ||
1903 | /* requested length too big for entire address space */ | 1903 | /* requested length too big for entire address space */ |
1904 | if (len > TASK_SIZE) | 1904 | if (len > TASK_SIZE - mmap_min_addr) |
1905 | return -ENOMEM; | 1905 | return -ENOMEM; |
1906 | 1906 | ||
1907 | if (flags & MAP_FIXED) | 1907 | if (flags & MAP_FIXED) |
@@ -1911,14 +1911,14 @@ arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0, | |||
1911 | if (addr) { | 1911 | if (addr) { |
1912 | addr = PAGE_ALIGN(addr); | 1912 | addr = PAGE_ALIGN(addr); |
1913 | vma = find_vma(mm, addr); | 1913 | vma = find_vma(mm, addr); |
1914 | if (TASK_SIZE - len >= addr && | 1914 | if (TASK_SIZE - len >= addr && addr >= mmap_min_addr && |
1915 | (!vma || addr + len <= vma->vm_start)) | 1915 | (!vma || addr + len <= vma->vm_start)) |
1916 | return addr; | 1916 | return addr; |
1917 | } | 1917 | } |
1918 | 1918 | ||
1919 | info.flags = VM_UNMAPPED_AREA_TOPDOWN; | 1919 | info.flags = VM_UNMAPPED_AREA_TOPDOWN; |
1920 | info.length = len; | 1920 | info.length = len; |
1921 | info.low_limit = PAGE_SIZE; | 1921 | info.low_limit = max(PAGE_SIZE, mmap_min_addr); |
1922 | info.high_limit = mm->mmap_base; | 1922 | info.high_limit = mm->mmap_base; |
1923 | info.align_mask = 0; | 1923 | info.align_mask = 0; |
1924 | addr = vm_unmapped_area(&info); | 1924 | addr = vm_unmapped_area(&info); |