| diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c |
| index 735279e..0f9f005 100644 |
| |
| |
| @@ -756,6 +756,16 @@ unsigned long arch_align_stack(unsigned long sp) |
| unsigned long arch_randomize_brk(struct mm_struct *mm) |
| { |
| unsigned long range_end = mm->brk + 0x02000000; |
| - return randomize_range(mm->brk, range_end, 0) ? : mm->brk; |
| + unsigned long bump = 0; |
| +#ifdef CONFIG_X86_32 |
| + /* in the case of NX emulation, shove the brk segment way out of the |
| + way of the exec randomization area, since it can collide with |
| + future allocations if not. */ |
| + if ( (mm->get_unmapped_exec_area == arch_get_unmapped_exec_area) && |
| + (mm->brk < 0x08000000) ) { |
| + bump = (TASK_SIZE/6); |
| + } |
| +#endif |
| + return bump + (randomize_range(mm->brk, range_end, 0) ? : mm->brk); |
| } |
| |
| diff --git a/arch/x86/mm/mmap.c b/arch/x86/mm/mmap.c |
| index 845df68..d437466 100644 |
| |
| |
| @@ -119,6 +119,12 @@ void arch_pick_mmap_layout(struct mm_struct *mm) |
| } else { |
| mm->mmap_base = mmap_base(); |
| mm->get_unmapped_area = arch_get_unmapped_area_topdown; |
| +#ifdef CONFIG_X86_32 |
| + if (!(current->personality & READ_IMPLIES_EXEC) |
| + && !(__supported_pte_mask & _PAGE_NX) |
| + && mmap_is_ia32()) |
| + mm->get_unmapped_exec_area = arch_get_unmapped_exec_area; |
| +#endif |
| mm->unmap_area = arch_unmap_area_topdown; |
| } |
| } |
| diff --git a/arch/x86/vdso/vdso32-setup.c b/arch/x86/vdso/vdso32-setup.c |
| index 66e6d93..b80cde7 100644 |
| |
| |
| @@ -330,7 +330,7 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) |
| if (compat) |
| addr = VDSO_HIGH_BASE; |
| else { |
| - addr = get_unmapped_area(NULL, 0, PAGE_SIZE, 0, 0); |
| + addr = get_unmapped_area_prot(NULL, 0, PAGE_SIZE, 0, 0, 1); |
| if (IS_ERR_VALUE(addr)) { |
| ret = addr; |
| goto up_fail; |
| diff --git a/include/linux/mm.h b/include/linux/mm.h |
| index b36d08c..91e573d 100644 |
| |
| |
| @@ -1387,7 +1387,13 @@ extern int install_special_mapping(struct mm_struct *mm, |
| unsigned long addr, unsigned long len, |
| unsigned long flags, struct page **pages); |
| |
| -extern unsigned long get_unmapped_area(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); |
| +extern unsigned long get_unmapped_area_prot(struct file *, unsigned long, unsigned long, unsigned long, unsigned long, int); |
| + |
| +static inline unsigned long get_unmapped_area(struct file *file, unsigned long addr, |
| + unsigned long len, unsigned long pgoff, unsigned long flags) |
| +{ |
| + return get_unmapped_area_prot(file, addr, len, pgoff, flags, 0); |
| +} |
| |
| extern unsigned long mmap_region(struct file *file, unsigned long addr, |
| unsigned long len, unsigned long flags, |
| diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h |
| index dad95bd..01a150c 100644 |
| |
| |
| @@ -294,6 +294,9 @@ struct mm_struct { |
| unsigned long (*get_unmapped_area) (struct file *filp, |
| unsigned long addr, unsigned long len, |
| unsigned long pgoff, unsigned long flags); |
| + unsigned long (*get_unmapped_exec_area) (struct file *filp, |
| + unsigned long addr, unsigned long len, |
| + unsigned long pgoff, unsigned long flags); |
| void (*unmap_area) (struct mm_struct *mm, unsigned long addr); |
| #endif |
| unsigned long mmap_base; /* base of mmap area */ |
| diff --git a/include/linux/sched.h b/include/linux/sched.h |
| index f34437e..12fe177 100644 |
| |
| |
| @@ -391,6 +391,10 @@ extern void arch_pick_mmap_layout(struct mm_struct *mm); |
| extern unsigned long |
| arch_get_unmapped_area(struct file *, unsigned long, unsigned long, |
| unsigned long, unsigned long); |
| + |
| +extern unsigned long |
| +arch_get_unmapped_exec_area(struct file *, unsigned long, unsigned long, |
| + unsigned long, unsigned long); |
| extern unsigned long |
| arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, |
| unsigned long len, unsigned long pgoff, |
| diff --git a/mm/mmap.c b/mm/mmap.c |
| index 3edfcdf..076ec09 100644 |
| |
| |
| @@ -31,6 +31,7 @@ |
| #include <linux/audit.h> |
| #include <linux/khugepaged.h> |
| #include <linux/uprobes.h> |
| +#include <linux/random.h> |
| |
| #include <asm/uaccess.h> |
| #include <asm/cacheflush.h> |
| @@ -1011,7 +1012,8 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, |
| /* Obtain the address to map to. we verify (or select) it and ensure |
| * that it represents a valid section of the address space. |
| */ |
| - addr = get_unmapped_area(file, addr, len, pgoff, flags); |
| + addr = get_unmapped_area_prot(file, addr, len, pgoff, flags, |
| + prot & PROT_EXEC); |
| if (addr & ~PAGE_MASK) |
| return addr; |
| |
| @@ -1568,8 +1570,8 @@ void arch_unmap_area_topdown(struct mm_struct *mm, unsigned long addr) |
| } |
| |
| unsigned long |
| -get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, |
| - unsigned long pgoff, unsigned long flags) |
| +get_unmapped_area_prot(struct file *file, unsigned long addr, unsigned long len, |
| + unsigned long pgoff, unsigned long flags, int exec) |
| { |
| unsigned long (*get_area)(struct file *, unsigned long, |
| unsigned long, unsigned long, unsigned long); |
| @@ -1582,7 +1584,11 @@ get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, |
| if (len > TASK_SIZE) |
| return -ENOMEM; |
| |
| - get_area = current->mm->get_unmapped_area; |
| + if (exec && current->mm->get_unmapped_exec_area) |
| + get_area = current->mm->get_unmapped_exec_area; |
| + else |
| + get_area = current->mm->get_unmapped_area; |
| + |
| if (file && file->f_op && file->f_op->get_unmapped_area) |
| get_area = file->f_op->get_unmapped_area; |
| addr = get_area(file, addr, len, pgoff, flags); |
| @@ -1598,8 +1604,83 @@ get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, |
| error = security_mmap_addr(addr); |
| return error ? error : addr; |
| } |
| +EXPORT_SYMBOL(get_unmapped_area_prot); |
| + |
| +static bool should_randomize(void) |
| +{ |
| + return (current->flags & PF_RANDOMIZE) && |
| + !(current->personality & ADDR_NO_RANDOMIZE); |
| +} |
| + |
| +#define SHLIB_BASE 0x00110000 |
| + |
| +unsigned long |
| +arch_get_unmapped_exec_area(struct file *filp, unsigned long addr0, |
| + unsigned long len0, unsigned long pgoff, unsigned long flags) |
| +{ |
| + unsigned long addr = addr0, len = len0; |
| + struct mm_struct *mm = current->mm; |
| + struct vm_area_struct *vma; |
| + unsigned long tmp; |
| + |
| + if (len > TASK_SIZE) |
| + return -ENOMEM; |
| + |
| + if (flags & MAP_FIXED) |
| + return addr; |
| + |
| + if (!addr) |
| + addr = !should_randomize() ? SHLIB_BASE : |
| + randomize_range(SHLIB_BASE, 0x01000000, len); |
| + |
| + if (addr) { |
| + addr = PAGE_ALIGN(addr); |
| + vma = find_vma(mm, addr); |
| + if (TASK_SIZE - len >= addr && |
| + (!vma || addr + len <= vma->vm_start)) |
| + return addr; |
| + } |
| + |
| + addr = SHLIB_BASE; |
| + for (vma = find_vma(mm, addr); ; vma = vma->vm_next) { |
| + /* At this point: (!vma || addr < vma->vm_end). */ |
| + if (TASK_SIZE - len < addr) |
| + return -ENOMEM; |
| + |
| + if (!vma || addr + len <= vma->vm_start) { |
| + /* |
| + * Must not let a PROT_EXEC mapping get into the |
| + * brk area: |
| + */ |
| + if (addr + len > mm->brk) |
| + goto failed; |
| + |
| + /* |
| + * Up until the brk area we randomize addresses |
| + * as much as possible: |
| + */ |
| + if (addr >= 0x01000000 && should_randomize()) { |
| + tmp = randomize_range(0x01000000, |
| + PAGE_ALIGN(max(mm->start_brk, |
| + (unsigned long)0x08000000)), len); |
| + vma = find_vma(mm, tmp); |
| + if (TASK_SIZE - len >= tmp && |
| + (!vma || tmp + len <= vma->vm_start)) |
| + return tmp; |
| + } |
| + /* |
| + * Ok, randomization didnt work out - return |
| + * the result of the linear search: |
| + */ |
| + return addr; |
| + } |
| + addr = vma->vm_end; |
| + } |
| + |
| +failed: |
| + return current->mm->get_unmapped_area(filp, addr0, len0, pgoff, flags); |
| +} |
| |
| -EXPORT_SYMBOL(get_unmapped_area); |
| |
| /* Look up the first VMA which satisfies addr < vm_end, NULL if none. */ |
| struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr) |
| diff --git a/mm/mremap.c b/mm/mremap.c |
| index 21fed20..a3de1ee 100644 |
| |
| |
| @@ -519,10 +519,10 @@ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len, |
| if (vma->vm_flags & VM_MAYSHARE) |
| map_flags |= MAP_SHARED; |
| |
| - new_addr = get_unmapped_area(vma->vm_file, 0, new_len, |
| + new_addr = get_unmapped_area_prot(vma->vm_file, 0, new_len, |
| vma->vm_pgoff + |
| ((addr - vma->vm_start) >> PAGE_SHIFT), |
| - map_flags); |
| + map_flags, vma->vm_flags & VM_EXEC); |
| if (new_addr & ~PAGE_MASK) { |
| ret = new_addr; |
| goto out; |