Commit 3a459756 authored by Kirill Korotaev's avatar Kirill Korotaev Committed by Linus Torvalds

[PATCH] IA64,sparc: local DoS with corrupted ELFs

This prevents cross-region mappings on IA64 and SPARC which could lead
to system crash.  They were correctly trapped for normal mmap() calls,
but not for the kernel internal calls generated by executable loading.

This code just moves the architecture-specific cross-region checks into
an arch-specific "arch_mmap_check()" macro, and defines that for the
architectures that needed it (ia64, sparc and sparc64).

Architectures that don't have any special requirements can just ignore
the new cross-region check, since the mmap() code will just notice on
its own when the macro isn't defined.
Signed-off-by: default avatarPavel Emelianov <xemul@openvz.org>
Signed-off-by: default avatarKirill Korotaev <dev@openvz.org>
Acked-by: default avatarDavid Miller <davem@davemloft.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
[ Cleaned up to not affect architectures that don't need it ]
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 10387e5e
...@@ -163,10 +163,25 @@ sys_pipe (void) ...@@ -163,10 +163,25 @@ sys_pipe (void)
return retval; return retval;
} }
int ia64_mmap_check(unsigned long addr, unsigned long len,
unsigned long flags)
{
unsigned long roff;
/*
* Don't permit mappings into unmapped space, the virtual page table
* of a region, or across a region boundary. Note: RGN_MAP_LIMIT is
* equal to 2^n-PAGE_SIZE (for some integer n <= 61) and len > 0.
*/
roff = REGION_OFFSET(addr);
if ((len > RGN_MAP_LIMIT) || (roff > (RGN_MAP_LIMIT - len)))
return -EINVAL;
return 0;
}
static inline unsigned long static inline unsigned long
do_mmap2 (unsigned long addr, unsigned long len, int prot, int flags, int fd, unsigned long pgoff) do_mmap2 (unsigned long addr, unsigned long len, int prot, int flags, int fd, unsigned long pgoff)
{ {
unsigned long roff;
struct file *file = NULL; struct file *file = NULL;
flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
...@@ -188,17 +203,6 @@ do_mmap2 (unsigned long addr, unsigned long len, int prot, int flags, int fd, un ...@@ -188,17 +203,6 @@ do_mmap2 (unsigned long addr, unsigned long len, int prot, int flags, int fd, un
goto out; goto out;
} }
/*
* Don't permit mappings into unmapped space, the virtual page table of a region,
* or across a region boundary. Note: RGN_MAP_LIMIT is equal to 2^n-PAGE_SIZE
* (for some integer n <= 61) and len > 0.
*/
roff = REGION_OFFSET(addr);
if ((len > RGN_MAP_LIMIT) || (roff > (RGN_MAP_LIMIT - len))) {
addr = -EINVAL;
goto out;
}
down_write(&current->mm->mmap_sem); down_write(&current->mm->mmap_sem);
addr = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); addr = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);
up_write(&current->mm->mmap_sem); up_write(&current->mm->mmap_sem);
......
...@@ -219,6 +219,21 @@ asmlinkage int sys_ipc (uint call, int first, int second, int third, void __user ...@@ -219,6 +219,21 @@ asmlinkage int sys_ipc (uint call, int first, int second, int third, void __user
return err; return err;
} }
int sparc_mmap_check(unsigned long addr, unsigned long len, unsigned long flags)
{
if (ARCH_SUN4C_SUN4 &&
(len > 0x20000000 ||
((flags & MAP_FIXED) &&
addr < 0xe0000000 && addr + len > 0x20000000)))
return -EINVAL;
/* See asm-sparc/uaccess.h */
if (len > TASK_SIZE - PAGE_SIZE || addr + len > TASK_SIZE - PAGE_SIZE)
return -EINVAL;
return 0;
}
/* Linux version of mmap */ /* Linux version of mmap */
static unsigned long do_mmap2(unsigned long addr, unsigned long len, static unsigned long do_mmap2(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags, unsigned long fd, unsigned long prot, unsigned long flags, unsigned long fd,
...@@ -233,25 +248,13 @@ static unsigned long do_mmap2(unsigned long addr, unsigned long len, ...@@ -233,25 +248,13 @@ static unsigned long do_mmap2(unsigned long addr, unsigned long len,
goto out; goto out;
} }
retval = -EINVAL;
len = PAGE_ALIGN(len); len = PAGE_ALIGN(len);
if (ARCH_SUN4C_SUN4 &&
(len > 0x20000000 ||
((flags & MAP_FIXED) &&
addr < 0xe0000000 && addr + len > 0x20000000)))
goto out_putf;
/* See asm-sparc/uaccess.h */
if (len > TASK_SIZE - PAGE_SIZE || addr + len > TASK_SIZE - PAGE_SIZE)
goto out_putf;
flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
down_write(&current->mm->mmap_sem); down_write(&current->mm->mmap_sem);
retval = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); retval = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);
up_write(&current->mm->mmap_sem); up_write(&current->mm->mmap_sem);
out_putf:
if (file) if (file)
fput(file); fput(file);
out: out:
......
...@@ -548,6 +548,26 @@ asmlinkage long sparc64_personality(unsigned long personality) ...@@ -548,6 +548,26 @@ asmlinkage long sparc64_personality(unsigned long personality)
return ret; return ret;
} }
int sparc64_mmap_check(unsigned long addr, unsigned long len,
unsigned long flags)
{
if (test_thread_flag(TIF_32BIT)) {
if (len >= STACK_TOP32)
return -EINVAL;
if ((flags & MAP_FIXED) && addr > STACK_TOP32 - len)
return -EINVAL;
} else {
if (len >= VA_EXCLUDE_START)
return -EINVAL;
if ((flags & MAP_FIXED) && invalid_64bit_range(addr, len))
return -EINVAL;
}
return 0;
}
/* Linux version of mmap */ /* Linux version of mmap */
asmlinkage unsigned long sys_mmap(unsigned long addr, unsigned long len, asmlinkage unsigned long sys_mmap(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags, unsigned long fd, unsigned long prot, unsigned long flags, unsigned long fd,
...@@ -563,27 +583,11 @@ asmlinkage unsigned long sys_mmap(unsigned long addr, unsigned long len, ...@@ -563,27 +583,11 @@ asmlinkage unsigned long sys_mmap(unsigned long addr, unsigned long len,
} }
flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
len = PAGE_ALIGN(len); len = PAGE_ALIGN(len);
retval = -EINVAL;
if (test_thread_flag(TIF_32BIT)) {
if (len >= STACK_TOP32)
goto out_putf;
if ((flags & MAP_FIXED) && addr > STACK_TOP32 - len)
goto out_putf;
} else {
if (len >= VA_EXCLUDE_START)
goto out_putf;
if ((flags & MAP_FIXED) && invalid_64bit_range(addr, len))
goto out_putf;
}
down_write(&current->mm->mmap_sem); down_write(&current->mm->mmap_sem);
retval = do_mmap(file, addr, len, prot, flags, off); retval = do_mmap(file, addr, len, prot, flags, off);
up_write(&current->mm->mmap_sem); up_write(&current->mm->mmap_sem);
out_putf:
if (file) if (file)
fput(file); fput(file);
out: out:
......
...@@ -22,4 +22,12 @@ ...@@ -22,4 +22,12 @@
#define MCL_CURRENT 1 /* lock all current mappings */ #define MCL_CURRENT 1 /* lock all current mappings */
#define MCL_FUTURE 2 /* lock all future mappings */ #define MCL_FUTURE 2 /* lock all future mappings */
#ifdef __KERNEL__
#ifndef __ASSEMBLY__
#define arch_mmap_check ia64_mmap_check
int ia64_mmap_check(unsigned long addr, unsigned long len,
unsigned long flags);
#endif
#endif
#endif /* _ASM_IA64_MMAN_H */ #endif /* _ASM_IA64_MMAN_H */
...@@ -35,4 +35,12 @@ ...@@ -35,4 +35,12 @@
#define MADV_FREE 0x5 /* (Solaris) contents can be freed */ #define MADV_FREE 0x5 /* (Solaris) contents can be freed */
#ifdef __KERNEL__
#ifndef __ASSEMBLY__
#define arch_mmap_check sparc_mmap_check
int sparc_mmap_check(unsigned long addr, unsigned long len,
unsigned long flags);
#endif
#endif
#endif /* __SPARC_MMAN_H__ */ #endif /* __SPARC_MMAN_H__ */
...@@ -35,4 +35,12 @@ ...@@ -35,4 +35,12 @@
#define MADV_FREE 0x5 /* (Solaris) contents can be freed */ #define MADV_FREE 0x5 /* (Solaris) contents can be freed */
#ifdef __KERNEL__
#ifndef __ASSEMBLY__
#define arch_mmap_check sparc64_mmap_check
int sparc64_mmap_check(unsigned long addr, unsigned long len,
unsigned long flags);
#endif
#endif
#endif /* __SPARC64_MMAN_H__ */ #endif /* __SPARC64_MMAN_H__ */
...@@ -30,6 +30,10 @@ ...@@ -30,6 +30,10 @@
#include <asm/cacheflush.h> #include <asm/cacheflush.h>
#include <asm/tlb.h> #include <asm/tlb.h>
#ifndef arch_mmap_check
#define arch_mmap_check(addr, len, flags) (0)
#endif
static void unmap_region(struct mm_struct *mm, static void unmap_region(struct mm_struct *mm,
struct vm_area_struct *vma, struct vm_area_struct *prev, struct vm_area_struct *vma, struct vm_area_struct *prev,
unsigned long start, unsigned long end); unsigned long start, unsigned long end);
...@@ -913,6 +917,10 @@ unsigned long do_mmap_pgoff(struct file * file, unsigned long addr, ...@@ -913,6 +917,10 @@ unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,
if (!len) if (!len)
return -EINVAL; return -EINVAL;
error = arch_mmap_check(addr, len, flags);
if (error)
return error;
/* Careful about overflows.. */ /* Careful about overflows.. */
len = PAGE_ALIGN(len); len = PAGE_ALIGN(len);
if (!len || len > TASK_SIZE) if (!len || len > TASK_SIZE)
...@@ -1859,6 +1867,7 @@ unsigned long do_brk(unsigned long addr, unsigned long len) ...@@ -1859,6 +1867,7 @@ unsigned long do_brk(unsigned long addr, unsigned long len)
unsigned long flags; unsigned long flags;
struct rb_node ** rb_link, * rb_parent; struct rb_node ** rb_link, * rb_parent;
pgoff_t pgoff = addr >> PAGE_SHIFT; pgoff_t pgoff = addr >> PAGE_SHIFT;
int error;
len = PAGE_ALIGN(len); len = PAGE_ALIGN(len);
if (!len) if (!len)
...@@ -1867,6 +1876,12 @@ unsigned long do_brk(unsigned long addr, unsigned long len) ...@@ -1867,6 +1876,12 @@ unsigned long do_brk(unsigned long addr, unsigned long len)
if ((addr + len) > TASK_SIZE || (addr + len) < addr) if ((addr + len) > TASK_SIZE || (addr + len) < addr)
return -EINVAL; return -EINVAL;
flags = VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;
error = arch_mmap_check(addr, len, flags);
if (error)
return error;
/* /*
* mlock MCL_FUTURE? * mlock MCL_FUTURE?
*/ */
...@@ -1907,8 +1922,6 @@ unsigned long do_brk(unsigned long addr, unsigned long len) ...@@ -1907,8 +1922,6 @@ unsigned long do_brk(unsigned long addr, unsigned long len)
if (security_vm_enough_memory(len >> PAGE_SHIFT)) if (security_vm_enough_memory(len >> PAGE_SHIFT))
return -ENOMEM; return -ENOMEM;
flags = VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;
/* Can we just expand an old private anonymous mapping? */ /* Can we just expand an old private anonymous mapping? */
if (vma_merge(mm, prev, addr, addr + len, flags, if (vma_merge(mm, prev, addr, addr + len, flags,
NULL, NULL, pgoff, NULL)) NULL, NULL, pgoff, NULL))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment