--- /dev/null
+From: Andrea Arcangeli <andrea@suse.de>
+Subject: avoid silent stack overflow over the heap
+Patch-mainline: no
+References: bnc#44807 bnc#211997
+
+x
+
+Signed-off-by: Andrea Arcangeli <andrea@suse.de>
+Updated-by: Jeff Mahoney <jeffm@suse.com>
+---
+
+ arch/alpha/mm/fault.c | 2 -
+ arch/arm/mm/fault.c | 2 -
+ arch/cris/mm/fault.c | 2 -
+ arch/frv/mm/fault.c | 2 -
+ arch/ia64/mm/fault.c | 2 -
+ arch/m32r/mm/fault.c | 2 -
+ arch/m68k/mm/fault.c | 2 -
+ arch/mips/mm/fault.c | 2 -
+ arch/parisc/mm/fault.c | 2 -
+ arch/powerpc/mm/fault.c | 5 ++-
+ arch/powerpc/platforms/cell/spu_fault.c | 2 -
+ arch/s390/mm/fault.c | 2 -
+ arch/sh/mm/fault_32.c | 2 -
+ arch/sh/mm/tlbflush_64.c | 2 -
+ arch/sparc/mm/fault.c | 4 +--
+ arch/sparc64/mm/fault.c | 2 -
+ arch/um/kernel/trap.c | 9 ++++--
+ arch/x86/kernel/sys_x86_64.c | 10 ++++++-
+ arch/x86/mm/fault.c | 10 ++++++-
+ arch/xtensa/mm/fault.c | 2 -
+ include/linux/mm.h | 5 +++
+ kernel/sysctl.c | 8 ++++++
+ mm/mmap.c | 42 +++++++++++++++++++++++++-------
+ 23 files changed, 88 insertions(+), 35 deletions(-)
+
+--- a/arch/alpha/mm/fault.c
++++ b/arch/alpha/mm/fault.c
+@@ -123,7 +123,7 @@ do_page_fault(unsigned long address, uns
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto bad_area;
+
+ /* Ok, we have a good vm_area for this memory access, so
+--- a/arch/arm/mm/fault.c
++++ b/arch/arm/mm/fault.c
+@@ -233,7 +233,7 @@ out_of_memory:
+ goto survive;
+
+ check_stack:
+- if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))
++ if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr, NULL))
+ goto good_area;
+ out:
+ return fault;
+--- a/arch/cris/mm/fault.c
++++ b/arch/cris/mm/fault.c
+@@ -133,7 +133,7 @@ do_page_fault(unsigned long address, str
+ if (address + PAGE_SIZE < rdusp())
+ goto bad_area;
+ }
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto bad_area;
+
+ /*
+--- a/arch/frv/mm/fault.c
++++ b/arch/frv/mm/fault.c
+@@ -121,7 +121,7 @@ asmlinkage void do_page_fault(int datamm
+ }
+ }
+
+- if (expand_stack(vma, ear0))
++ if (expand_stack(vma, ear0, NULL))
+ goto bad_area;
+
+ /*
+--- a/arch/ia64/mm/fault.c
++++ b/arch/ia64/mm/fault.c
+@@ -185,7 +185,7 @@ ia64_do_page_fault (unsigned long addres
+ if (REGION_NUMBER(address) != REGION_NUMBER(vma->vm_start)
+ || REGION_OFFSET(address) >= RGN_MAP_LIMIT)
+ goto bad_area;
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL /* FIXME? */))
+ goto bad_area;
+ } else {
+ vma = prev_vma;
+--- a/arch/m32r/mm/fault.c
++++ b/arch/m32r/mm/fault.c
+@@ -159,7 +159,7 @@ asmlinkage void do_page_fault(struct pt_
+ goto bad_area;
+ }
+
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto bad_area;
+ /*
+ * Ok, we have a good vm_area for this memory access, so
+--- a/arch/m68k/mm/fault.c
++++ b/arch/m68k/mm/fault.c
+@@ -121,7 +121,7 @@ int do_page_fault(struct pt_regs *regs,
+ if (address + 256 < rdusp())
+ goto map_err;
+ }
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto map_err;
+
+ /*
+--- a/arch/mips/mm/fault.c
++++ b/arch/mips/mm/fault.c
+@@ -80,7 +80,7 @@ asmlinkage void do_page_fault(struct pt_
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto bad_area;
+ /*
+ * Ok, we have a good vm_area for this memory access, so
+--- a/arch/parisc/mm/fault.c
++++ b/arch/parisc/mm/fault.c
+@@ -196,7 +196,7 @@ good_area:
+
+ check_expansion:
+ vma = prev_vma;
+- if (vma && (expand_stack(vma, address) == 0))
++ if (vma && (expand_stack(vma, address, NULL) == 0))
+ goto good_area;
+
+ /*
+--- a/arch/powerpc/mm/fault.c
++++ b/arch/powerpc/mm/fault.c
+@@ -116,7 +116,7 @@ static int store_updates_sp(struct pt_re
+ int __kprobes do_page_fault(struct pt_regs *regs, unsigned long address,
+ unsigned long error_code)
+ {
+- struct vm_area_struct * vma;
++ struct vm_area_struct * vma, * prev_vma;
+ struct mm_struct *mm = current->mm;
+ siginfo_t info;
+ int code = SEGV_MAPERR;
+@@ -230,7 +230,8 @@ int __kprobes do_page_fault(struct pt_re
+ && (!user_mode(regs) || !store_updates_sp(regs)))
+ goto bad_area;
+ }
+- if (expand_stack(vma, address))
++ find_vma_prev(mm, address, &prev_vma);
++ if (expand_stack(vma, address, prev_vma))
+ goto bad_area;
+
+ good_area:
+--- a/arch/powerpc/platforms/cell/spu_fault.c
++++ b/arch/powerpc/platforms/cell/spu_fault.c
+@@ -59,7 +59,7 @@ int spu_handle_mm_fault(struct mm_struct
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if (expand_stack(vma, ea))
++ if (expand_stack(vma, ea, NULL))
+ goto bad_area;
+ good_area:
+ is_write = dsisr & MFC_DSISR_ACCESS_PUT;
+--- a/arch/s390/mm/fault.c
++++ b/arch/s390/mm/fault.c
+@@ -350,7 +350,7 @@ do_exception(struct pt_regs *regs, unsig
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL /* FIXME? */))
+ goto bad_area;
+ /*
+ * Ok, we have a good vm_area for this memory access, so
+--- a/arch/sh/mm/fault_32.c
++++ b/arch/sh/mm/fault_32.c
+@@ -108,7 +108,7 @@ asmlinkage void __kprobes do_page_fault(
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto bad_area;
+ /*
+ * Ok, we have a good vm_area for this memory access, so
+--- a/arch/sh/mm/tlbflush_64.c
++++ b/arch/sh/mm/tlbflush_64.c
+@@ -153,7 +153,7 @@ asmlinkage void do_page_fault(struct pt_
+ #endif
+ goto bad_area;
+ }
+- if (expand_stack(vma, address)) {
++ if (expand_stack(vma, address, NULL)) {
+ #ifdef DEBUG_FAULT
+ print_task(tsk);
+ printk("%s:%d fault, address is 0x%08x PC %016Lx textaccess %d writeaccess %d\n",
+--- a/arch/sparc/mm/fault.c
++++ b/arch/sparc/mm/fault.c
+@@ -219,7 +219,7 @@ asmlinkage void do_sparc_fault(struct pt
+ goto good_area;
+ if(!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if(expand_stack(vma, address))
++ if(expand_stack(vma, address, NULL))
+ goto bad_area;
+ /*
+ * Ok, we have a good vm_area for this memory access, so
+@@ -472,7 +472,7 @@ static void force_user_fault(unsigned lo
+ goto good_area;
+ if(!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if(expand_stack(vma, address))
++ if(expand_stack(vma, address, NULL))
+ goto bad_area;
+ good_area:
+ info.si_code = SEGV_ACCERR;
+--- a/arch/sparc64/mm/fault.c
++++ b/arch/sparc64/mm/fault.c
+@@ -367,7 +367,7 @@ continue_fault:
+ goto bad_area;
+ }
+ }
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto bad_area;
+ /*
+ * Ok, we have a good vm_area for this memory access, so
+--- a/arch/um/kernel/trap.c
++++ b/arch/um/kernel/trap.c
+@@ -24,7 +24,7 @@ int handle_page_fault(unsigned long addr
+ int is_write, int is_user, int *code_out)
+ {
+ struct mm_struct *mm = current->mm;
+- struct vm_area_struct *vma;
++ struct vm_area_struct *vma, *prev_vma;
+ pgd_t *pgd;
+ pud_t *pud;
+ pmd_t *pmd;
+@@ -50,8 +50,11 @@ int handle_page_fault(unsigned long addr
+ goto out;
+ else if (is_user && !ARCH_IS_STACKGROW(address))
+ goto out;
+- else if (expand_stack(vma, address))
+- goto out;
++ else {
++ find_vma_prev(mm, address, &prev_vma);
++ if(expand_stack(vma, address, prev_vma))
++ goto out;
++ }
+
+ good_area:
+ *code_out = SEGV_ACCERR;
+--- a/arch/x86/kernel/sys_x86_64.c
++++ b/arch/x86/kernel/sys_x86_64.c
+@@ -106,6 +106,7 @@ arch_get_unmapped_area(struct file *filp
+
+ full_search:
+ for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
++ unsigned long __heap_stack_gap;
+ /* At this point: (!vma || addr < vma->vm_end). */
+ if (end - len < addr) {
+ /*
+@@ -119,7 +120,14 @@ full_search:
+ }
+ return -ENOMEM;
+ }
+- if (!vma || addr + len <= vma->vm_start) {
++ if (!vma)
++ goto got_it;
++ __heap_stack_gap = 0;
++ if (vma->vm_flags & VM_GROWSDOWN)
++ __heap_stack_gap = min(end-(addr+len),
++ (unsigned long) heap_stack_gap << PAGE_SHIFT);
++ if (addr + len + __heap_stack_gap <= vma->vm_start) {
++ got_it:
+ /*
+ * Remember the place where we stopped the search:
+ */
+--- a/arch/x86/mm/fault.c
++++ b/arch/x86/mm/fault.c
+@@ -585,7 +585,7 @@ void __kprobes do_page_fault(struct pt_r
+ {
+ struct task_struct *tsk;
+ struct mm_struct *mm;
+- struct vm_area_struct *vma;
++ struct vm_area_struct *vma, *prev_vma;
+ unsigned long address;
+ int write, si_code;
+ int fault;
+@@ -719,7 +719,13 @@ again:
+ if (address + 65536 + 32 * sizeof(unsigned long) < regs->sp)
+ goto bad_area;
+ }
+- if (expand_stack(vma, address))
++ /*
++ * find_vma_prev is just a bit slower, because it cannot
++ * use the mmap_cache, so we run it only in the growsdown
++ * slow path and we leave find_vma in the fast path.
++ */
++ find_vma_prev(current->mm, address, &prev_vma);
++ if (expand_stack(vma, address, prev_vma))
+ goto bad_area;
+ /*
+ * Ok, we have a good vm_area for this memory access, so
+--- a/arch/xtensa/mm/fault.c
++++ b/arch/xtensa/mm/fault.c
+@@ -80,7 +80,7 @@ void do_page_fault(struct pt_regs *regs)
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+- if (expand_stack(vma, address))
++ if (expand_stack(vma, address, NULL))
+ goto bad_area;
+
+ /* Ok, we have a good vm_area for this memory access, so
+--- a/include/linux/mm.h
++++ b/include/linux/mm.h
+@@ -1208,7 +1208,10 @@ void page_cache_async_readahead(struct a
+ unsigned long max_sane_readahead(unsigned long nr);
+
+ /* Do stack extension */
+-extern int expand_stack(struct vm_area_struct *vma, unsigned long address);
++#define EXPAND_STACK_HAS_3_ARGS
++extern int heap_stack_gap;
++extern int expand_stack(struct vm_area_struct * vma, unsigned long address,
++ struct vm_area_struct * prev_vma);
+ #ifdef CONFIG_IA64
+ extern int expand_upwards(struct vm_area_struct *vma, unsigned long address);
+ #endif
+--- a/kernel/sysctl.c
++++ b/kernel/sysctl.c
+@@ -1208,6 +1208,14 @@ static struct ctl_table vm_table[] = {
+ .extra2 = &one,
+ },
+ #endif
++ {
++ .ctl_name = CTL_UNNUMBERED,
++ .procname = "heap-stack-gap",
++ .data = &heap_stack_gap,
++ .maxlen = sizeof(int),
++ .mode = 0644,
++ .proc_handler = &proc_dointvec,
++ },
+ /*
+ * NOTE: do not add new entries to this table unless you have read
+ * Documentation/sysctl/ctl_unnumbered.txt
+--- a/mm/mmap.c
++++ b/mm/mmap.c
+@@ -85,6 +85,7 @@ int sysctl_overcommit_memory = OVERCOMMI
+ int sysctl_overcommit_ratio = 50; /* default is 50% */
+ int sysctl_max_map_count __read_mostly = DEFAULT_MAX_MAP_COUNT;
+ atomic_long_t vm_committed_space = ATOMIC_LONG_INIT(0);
++int heap_stack_gap = 1;
+
+ /*
+ * Check that a process has enough memory to allocate a new virtual
+@@ -1291,6 +1292,7 @@ arch_get_unmapped_area(struct file *filp
+ full_search:
+ for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
+ /* At this point: (!vma || addr < vma->vm_end). */
++ unsigned long __heap_stack_gap;
+ if (TASK_SIZE - len < addr) {
+ /*
+ * Start a new search - just in case we missed
+@@ -1304,7 +1306,14 @@ full_search:
+ }
+ return -ENOMEM;
+ }
+- if (!vma || addr + len <= vma->vm_start) {
++ if (!vma)
++ goto got_it;
++ __heap_stack_gap = 0;
++ if (vma->vm_flags & VM_GROWSDOWN)
++ __heap_stack_gap = min(TASK_SIZE-(addr+len),
++ (unsigned long) heap_stack_gap << PAGE_SHIFT);
++ if (addr + len + __heap_stack_gap <= vma->vm_start) {
++ got_it:
+ /*
+ * Remember the place where we stopped the search:
+ */
+@@ -1633,11 +1642,9 @@ int expand_upwards(struct vm_area_struct
+ }
+ #endif /* CONFIG_STACK_GROWSUP || CONFIG_IA64 */
+
+-/*
+- * vma is the first one with address < vma->vm_start. Have to extend vma.
+- */
+ static inline int expand_downwards(struct vm_area_struct *vma,
+- unsigned long address)
++ unsigned long address,
++ struct vm_area_struct *prev_vma)
+ {
+ int error;
+
+@@ -1665,6 +1672,13 @@ static inline int expand_downwards(struc
+ if (address < vma->vm_start) {
+ unsigned long size, grow;
+
++ error = -ENOMEM;
++ if (prev_vma) {
++ unsigned long __heap_stack_gap = min(TASK_SIZE-prev_vma->vm_end,
++ (unsigned long) heap_stack_gap << PAGE_SHIFT);
++ if (unlikely(prev_vma->vm_end + __heap_stack_gap > address))
++ goto out_unlock;
++ }
+ size = vma->vm_end - address;
+ grow = (vma->vm_start - address) >> PAGE_SHIFT;
+
+@@ -1674,6 +1688,7 @@ static inline int expand_downwards(struc
+ vma->vm_pgoff -= grow;
+ }
+ }
++ out_unlock:
+ anon_vma_unlock(vma);
+ return error;
+ }
+@@ -1684,8 +1699,16 @@ int expand_stack_downwards(struct vm_are
+ }
+
+ #ifdef CONFIG_STACK_GROWSUP
+-int expand_stack(struct vm_area_struct *vma, unsigned long address)
++int expand_stack(struct vm_area_struct * vma, unsigned long address,
++ struct vm_area_struct * prev_vma)
+ {
++ /*
++ * If you re-use the heap-stack-gap for a growsup stack you
++ * should implement the feature for growsup too and remove
++ * this WARN_ON.
++ */
++ WARN_ON(prev_vma);
++
+ return expand_upwards(vma, address);
+ }
+
+@@ -1698,7 +1721,7 @@ find_extend_vma(struct mm_struct *mm, un
+ vma = find_vma_prev(mm, addr, &prev);
+ if (vma && (vma->vm_start <= addr))
+ return vma;
+- if (!prev || expand_stack(prev, addr))
++ if (!prev || expand_stack(prev, addr, NULL))
+ return NULL;
+ if (prev->vm_flags & VM_LOCKED)
+ make_pages_present(addr, prev->vm_end);
+@@ -1713,7 +1736,7 @@ int expand_stack(struct vm_area_struct *
+ struct vm_area_struct *
+ find_extend_vma(struct mm_struct * mm, unsigned long addr)
+ {
+- struct vm_area_struct * vma;
++ struct vm_area_struct * vma, * prev_vma;
+ unsigned long start;
+
+ addr &= PAGE_MASK;
+@@ -1725,7 +1748,8 @@ find_extend_vma(struct mm_struct * mm, u
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ return NULL;
+ start = vma->vm_start;
+- if (expand_stack(vma, addr))
++ find_vma_prev(mm, addr, &prev_vma);
++ if (expand_stack(vma, addr, prev_vma))
+ return NULL;
+ if (vma->vm_flags & VM_LOCKED)
+ make_pages_present(addr, start);