OSDN Git Service

[PATCH] sysctl to prevent normal processes from mapping NULL
authorWilly Tarreau <w@1wt.eu>
Mon, 13 Aug 2007 08:50:08 +0000 (10:50 +0200)
committerWilly Tarreau <w@1wt.eu>
Sat, 8 Sep 2007 17:41:48 +0000 (19:41 +0200)
After a patch proposal from Solar Designer, and discussions with
Alan Cox and Chris Wright, I modeled this patch which permits to
restrict mapping of lower virtual addresses to CAP_RAW_IO capable
processes only.

This makes it harder for processes to try to exploit NULL pointer
dereference bugs in the kernel.

In order to ease transition from 2.4 to 2.6, the patch has also
been inspired by Eric Paris's patch now present in 2.6, which
adds the sys/vm/mmap_min_addr sysctl by which the lowest mappable
address is set. This sysctl defaults to zero, meaning NULL is
allowed by default.

According to test ran by Solar Designer, both Xorg and Wine run
correctly as a normal user with the restriction enabled. There
should be very few regressions when enabling it, and it is
recommended to enable it on servers after the obviously needed
validation.

Alan points that some rare programs use a trick consisting in
mapping this page in order to reduce the number of NULL checks
in linked lists for instance.

Signed-off-by: Willy Tarreau <w@1wt.eu>
include/linux/mm.h
include/linux/sysctl.h
kernel/sysctl.c
mm/mmap.c
mm/mremap.c

index c9ad6d7..a9bafcb 100644 (file)
@@ -117,6 +117,7 @@ struct vm_area_struct {
 /* read ahead limits */
 extern int vm_min_readahead;
 extern int vm_max_readahead;
+extern unsigned long mmap_min_addr;
 
 /*
  * mapping from the currently active vm_flags protection bits (the
@@ -652,6 +653,11 @@ static inline int expand_stack(struct vm_area_struct * vma, unsigned long addres
         * page_table_lock lock to serialize against concurrent expand_stacks.
         */
        address &= PAGE_MASK;
+
+       /* ensure a non-privileged process is not trying to mmap lower pages */
+       if (address < mmap_min_addr && !capable(CAP_SYS_RAWIO))
+               return -EPERM;
+
        spin_lock(&vma->vm_mm->page_table_lock);
 
        /* already expanded while we were spinning? */
index 23c7ae4..34a1f13 100644 (file)
@@ -159,6 +159,7 @@ enum
        VM_LAPTOP_MODE=21,      /* kernel in laptop flush mode */
        VM_BLOCK_DUMP=22,       /* dump fs activity to log */
        VM_ANON_LRU=23,         /* immediatly insert anon pages in the vm page lru */
+       VM_MMAP_MIN_ADDR=24,    /* prevent mapping of low addresses by mmap() */
 };
 
 
index 3d54347..d36c362 100644 (file)
@@ -317,6 +317,8 @@ static ctl_table vm_table[] = {
         &laptop_mode, sizeof(int), 0644, NULL, &proc_dointvec},
        {VM_BLOCK_DUMP, "block_dump",
         &block_dump, sizeof(int), 0644, NULL, &proc_dointvec},
+       {VM_MMAP_MIN_ADDR, "mmap_min_addr",
+        &mmap_min_addr, sizeof(unsigned long), 0644, NULL, &proc_doulongvec_minmax},
        {0}
 };
 
index dc296f3..d35ff3e 100644 (file)
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -46,6 +46,8 @@ pgprot_t protection_map[16] = {
 };
 
 int sysctl_overcommit_memory;
+unsigned long mmap_min_addr;           /* defaults to 0 = no protection */
+
 int max_map_count = DEFAULT_MAX_MAP_COUNT;
 
 /* Check that a process has enough memory to allocate a
@@ -654,13 +656,25 @@ unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned
                        return -ENOMEM;
                if (addr & ~PAGE_MASK)
                        return -EINVAL;
+
+               /* Ensure a non-privileged process is not trying to map
+                * lower pages.
+                */
+               if (addr < mmap_min_addr && !capable(CAP_SYS_RAWIO))
+                       return -EPERM;
+
                return addr;
        }
 
        if (file && file->f_op && file->f_op->get_unmapped_area)
-               return file->f_op->get_unmapped_area(file, addr, len, pgoff, flags);
+               addr = file->f_op->get_unmapped_area(file, addr, len, pgoff, flags);
+       else
+               addr = arch_get_unmapped_area(file, addr, len, pgoff, flags);
+
+       if (addr < mmap_min_addr && !capable(CAP_SYS_RAWIO))
+               return -ENOMEM;
 
-       return arch_get_unmapped_area(file, addr, len, pgoff, flags);
+       return addr;
 }
 
 /* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
index 416dd4b..3bda909 100644 (file)
@@ -273,6 +273,12 @@ unsigned long do_mremap(unsigned long addr,
                if ((addr <= new_addr) && (addr+old_len) > new_addr)
                        goto out;
 
+               /* Ensure a non-privileged process is not trying to map
+                * lower pages.
+                */
+               if (new_addr < mmap_min_addr && !capable(CAP_SYS_RAWIO))
+                       return -EPERM;
+
                ret = do_munmap(current->mm, new_addr, new_len);
                if (ret && new_len)
                        goto out;