1 From: Nick Piggin <npiggin@suse.de>
2 Subject: avoid silent stack overflow over the heap
4 References: bnc#44807 bnc#211997
6 This is a rewrite of Andrea Arcangeli's patch, which implements a stack
7 guard feature. That is, it prevents the stack from growing right next
8 to another vma, and prevents other vmas being allocated right next to the
9 stack. This will cause a segfault rather than the stack silently overwriting
10 other memory areas (eg. the heap) in the case that the app has a stack
13 I have rewritten it so as not to require changes to expand_stack prototype,
14 support for growsup stacks, and support for powerpc and ia64.
17 Signed-off-by: Nick Piggin <npiggin@suse.de>
19 arch/ia64/kernel/sys_ia64.c | 11 +++++
20 arch/powerpc/mm/slice.c | 82 +++++++++++++++++++++++++-----------------
21 arch/x86/kernel/sys_x86_64.c | 52 ++++++++++++++++++++------
22 include/linux/mm.h | 1
23 kernel/sysctl.c | 8 ++++
24 mm/mmap.c | 83 ++++++++++++++++++++++++++++++++++++-------
25 6 files changed, 178 insertions(+), 59 deletions(-)
27 --- a/arch/ia64/kernel/sys_ia64.c
28 +++ b/arch/ia64/kernel/sys_ia64.c
29 @@ -59,6 +59,8 @@ arch_get_unmapped_area (struct file *fil
30 start_addr = addr = (addr + align_mask) & ~align_mask;
32 for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
33 + unsigned long guard;
35 /* At this point: (!vma || addr < vma->vm_end). */
36 if (TASK_SIZE - len < addr || RGN_MAP_LIMIT - len < REGION_OFFSET(addr)) {
37 if (start_addr != TASK_UNMAPPED_BASE) {
38 @@ -68,7 +70,14 @@ arch_get_unmapped_area (struct file *fil
42 - if (!vma || addr + len <= vma->vm_start) {
46 + if (vma->vm_flags & VM_GROWSDOWN)
47 + guard = min(TASK_SIZE - (addr + len),
48 + (unsigned long)guard << PAGE_SHIFT);
49 + if (addr + len + guard <= vma->vm_start) {
51 /* Remember the address where we stopped this search: */
52 mm->free_area_cache = addr + len;
54 --- a/arch/powerpc/mm/slice.c
55 +++ b/arch/powerpc/mm/slice.c
56 @@ -94,11 +94,21 @@ static int slice_area_is_free(struct mm_
59 struct vm_area_struct *vma;
60 + unsigned long guard;
62 if ((mm->task_size - len) < addr)
64 vma = find_vma(mm, addr);
65 - return (!vma || (addr + len) <= vma->vm_start);
70 + if (vma->vm_flags & VM_GROWSDOWN)
71 + guard = min(mm->task_size - (addr + len),
72 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
73 + if (addr + len + guard <= vma->vm_start)
78 static int slice_low_has_vma(struct mm_struct *mm, unsigned long slice)
79 @@ -242,8 +252,10 @@ static unsigned long slice_find_area_bot
83 + unsigned long guard;
85 addr = _ALIGN_UP(addr, 1ul << pshift);
86 - if ((TASK_SIZE - len) < addr)
87 + if ((mm->task_size - len) < addr)
89 vma = find_vma(mm, addr);
90 BUG_ON(vma && (addr >= vma->vm_end));
91 @@ -256,7 +268,14 @@ full_search:
92 addr = _ALIGN_UP(addr + 1, 1ul << SLICE_HIGH_SHIFT);
95 - if (!vma || addr + len <= vma->vm_start) {
99 + if (vma->vm_flags & VM_GROWSDOWN)
100 + guard = min(mm->task_size - (addr + len),
101 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
102 + if (addr + len + guard <= vma->vm_start) {
105 * Remember the place where we stopped the search:
107 @@ -264,8 +283,8 @@ full_search:
108 mm->free_area_cache = addr + len;
111 - if (use_cache && (addr + mm->cached_hole_size) < vma->vm_start)
112 - mm->cached_hole_size = vma->vm_start - addr;
113 + if (use_cache && (addr + guard + mm->cached_hole_size) < vma->vm_start)
114 + mm->cached_hole_size = vma->vm_start - (addr + guard);
118 @@ -284,37 +303,23 @@ static unsigned long slice_find_area_top
119 int psize, int use_cache)
121 struct vm_area_struct *vma;
122 - unsigned long addr;
123 + unsigned long start_addr, addr;
124 struct slice_mask mask;
125 int pshift = max_t(int, mmu_psize_defs[psize].shift, PAGE_SHIFT);
127 - /* check if free_area_cache is useful for us */
129 if (len <= mm->cached_hole_size) {
130 + start_addr = addr = mm->mmap_base;
131 mm->cached_hole_size = 0;
132 - mm->free_area_cache = mm->mmap_base;
135 - /* either no address requested or can't fit in requested
138 - addr = mm->free_area_cache;
140 - /* make sure it can fit in the remaining address space */
142 - addr = _ALIGN_DOWN(addr - len, 1ul << pshift);
143 - mask = slice_range_to_mask(addr, len);
144 - if (slice_check_fit(mask, available) &&
145 - slice_area_is_free(mm, addr, len))
146 - /* remember the address as a hint for
149 - return (mm->free_area_cache = addr);
153 + start_addr = addr = mm->free_area_cache;
155 + start_addr = addr = mm->mmap_base;
157 - addr = mm->mmap_base;
160 + unsigned long guard;
162 /* Go down by chunk size */
163 addr = _ALIGN_DOWN(addr - len, 1ul << pshift);
165 @@ -336,7 +341,15 @@ static unsigned long slice_find_area_top
166 * return with success:
168 vma = find_vma(mm, addr);
169 - if (!vma || (addr + len) <= vma->vm_start) {
174 + if (vma->vm_flags & VM_GROWSDOWN)
175 + guard = min(mm->task_size - (addr + len),
176 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
177 + if (addr + len + guard <= vma->vm_start) {
179 /* remember the address as a hint for next time */
181 mm->free_area_cache = addr;
182 @@ -344,11 +357,16 @@ static unsigned long slice_find_area_top
185 /* remember the largest hole we saw so far */
186 - if (use_cache && (addr + mm->cached_hole_size) < vma->vm_start)
187 - mm->cached_hole_size = vma->vm_start - addr;
188 + if (use_cache && (addr + guard + mm->cached_hole_size) < vma->vm_start)
189 + mm->cached_hole_size = vma->vm_start - (addr + guard);
191 /* try just below the current vma->vm_start */
192 - addr = vma->vm_start;
193 + addr = vma->vm_start - guard;
195 + if (start_addr != mm->mmap_base) {
196 + start_addr = addr = mm->mmap_base;
197 + mm->cached_hole_size = 0;
202 --- a/arch/x86/kernel/sys_x86_64.c
203 +++ b/arch/x86/kernel/sys_x86_64.c
204 @@ -106,6 +106,8 @@ arch_get_unmapped_area(struct file *filp
207 for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
208 + unsigned long guard;
210 /* At this point: (!vma || addr < vma->vm_end). */
211 if (end - len < addr) {
213 @@ -119,15 +121,22 @@ full_search:
217 - if (!vma || addr + len <= vma->vm_start) {
221 + if (vma->vm_flags & VM_GROWSDOWN)
222 + guard = min(end - (addr + len),
223 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
224 + if (addr + len + guard <= vma->vm_start) {
227 * Remember the place where we stopped the search:
229 mm->free_area_cache = addr + len;
232 - if (addr + mm->cached_hole_size < vma->vm_start)
233 - mm->cached_hole_size = vma->vm_start - addr;
234 + if (addr + guard + mm->cached_hole_size < vma->vm_start)
235 + mm->cached_hole_size = vma->vm_start - (addr + guard);
239 @@ -174,34 +183,51 @@ arch_get_unmapped_area_topdown(struct fi
241 /* make sure it can fit in the remaining address space */
243 - vma = find_vma(mm, addr-len);
244 - if (!vma || addr <= vma->vm_start)
245 - /* remember the address as a hint for next time */
246 - return (mm->free_area_cache = addr-len);
247 + unsigned long guard;
250 + vma = find_vma(mm, addr);
254 + if (vma->vm_flags & VM_GROWSDOWN)
255 + guard = min(TASK_SIZE - (addr + len),
256 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
257 + if (addr + len + guard <= vma->vm_start)
261 if (mm->mmap_base < len)
264 addr = mm->mmap_base-len;
267 + unsigned long guard;
269 * Lookup failure means no vma is above this address,
270 * else if new region fits below vma->vm_start,
271 * return with success:
273 vma = find_vma(mm, addr);
274 - if (!vma || addr+len <= vma->vm_start)
278 + if (vma->vm_flags & VM_GROWSDOWN)
279 + guard = min(TASK_SIZE - (addr + len),
280 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
281 + if (addr + len + guard <= vma->vm_start) {
283 /* remember the address as a hint for next time */
284 - return (mm->free_area_cache = addr);
285 + mm->free_area_cache = addr;
289 /* remember the largest hole we saw so far */
290 - if (addr + mm->cached_hole_size < vma->vm_start)
291 - mm->cached_hole_size = vma->vm_start - addr;
292 + if (addr + guard + mm->cached_hole_size < vma->vm_start)
293 + mm->cached_hole_size = vma->vm_start - (addr + guard);
295 /* try just below the current vma->vm_start */
296 - addr = vma->vm_start-len;
297 + addr = vma->vm_start - (len + guard);
298 } while (len < vma->vm_start);
301 --- a/include/linux/mm.h
302 +++ b/include/linux/mm.h
303 @@ -1182,6 +1182,7 @@ void page_cache_async_readahead(struct a
304 unsigned long max_sane_readahead(unsigned long nr);
306 /* Do stack extension */
307 +extern int heap_stack_gap;
308 extern int expand_stack(struct vm_area_struct *vma, unsigned long address);
310 extern int expand_upwards(struct vm_area_struct *vma, unsigned long address);
311 --- a/kernel/sysctl.c
312 +++ b/kernel/sysctl.c
313 @@ -1209,6 +1209,14 @@ static struct ctl_table vm_table[] = {
318 + .ctl_name = CTL_UNNUMBERED,
319 + .procname = "heap-stack-gap",
320 + .data = &heap_stack_gap,
321 + .maxlen = sizeof(int),
323 + .proc_handler = &proc_dointvec,
326 * NOTE: do not add new entries to this table unless you have read
327 * Documentation/sysctl/ctl_unnumbered.txt
330 @@ -85,6 +85,7 @@ int sysctl_overcommit_memory = OVERCOMMI
331 int sysctl_overcommit_ratio = 50; /* default is 50% */
332 int sysctl_max_map_count __read_mostly = DEFAULT_MAX_MAP_COUNT;
333 atomic_long_t vm_committed_space = ATOMIC_LONG_INIT(0);
334 +int heap_stack_gap __read_mostly = 1;
337 * Check that a process has enough memory to allocate a new virtual
338 @@ -1290,6 +1291,8 @@ arch_get_unmapped_area(struct file *filp
341 for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
342 + unsigned long guard;
344 /* At this point: (!vma || addr < vma->vm_end). */
345 if (TASK_SIZE - len < addr) {
347 @@ -1304,15 +1307,23 @@ full_search:
351 - if (!vma || addr + len <= vma->vm_start) {
355 + if (vma->vm_flags & VM_GROWSDOWN)
356 + guard = min(TASK_SIZE - (addr + len),
357 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
358 + if (addr + len + guard <= vma->vm_start) {
361 * Remember the place where we stopped the search:
363 mm->free_area_cache = addr + len;
366 - if (addr + mm->cached_hole_size < vma->vm_start)
367 - mm->cached_hole_size = vma->vm_start - addr;
368 + if (addr + guard + mm->cached_hole_size < vma->vm_start)
369 + mm->cached_hole_size = vma->vm_start - (addr + guard);
374 @@ -1370,34 +1381,51 @@ arch_get_unmapped_area_topdown(struct fi
376 /* make sure it can fit in the remaining address space */
378 - vma = find_vma(mm, addr-len);
379 - if (!vma || addr <= vma->vm_start)
380 - /* remember the address as a hint for next time */
381 - return (mm->free_area_cache = addr-len);
382 + unsigned long guard;
385 + vma = find_vma(mm, addr);
389 + if (vma->vm_flags & VM_GROWSDOWN)
390 + guard = min(TASK_SIZE - (addr + len),
391 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
392 + if (addr + len + guard <= vma->vm_start)
396 if (mm->mmap_base < len)
399 addr = mm->mmap_base-len;
402 + unsigned long guard;
404 * Lookup failure means no vma is above this address,
405 * else if new region fits below vma->vm_start,
406 * return with success:
408 vma = find_vma(mm, addr);
409 - if (!vma || addr+len <= vma->vm_start)
413 + if (vma->vm_flags & VM_GROWSDOWN)
414 + guard = min(TASK_SIZE - (addr + len),
415 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
416 + if (addr + len + guard <= vma->vm_start) {
418 /* remember the address as a hint for next time */
419 - return (mm->free_area_cache = addr);
420 + mm->free_area_cache = addr;
424 /* remember the largest hole we saw so far */
425 - if (addr + mm->cached_hole_size < vma->vm_start)
426 - mm->cached_hole_size = vma->vm_start - addr;
427 + if (addr + guard + mm->cached_hole_size < vma->vm_start)
428 + mm->cached_hole_size = vma->vm_start - (addr + guard);
430 /* try just below the current vma->vm_start */
431 - addr = vma->vm_start-len;
432 + addr = vma->vm_start - (len + guard);
433 } while (len < vma->vm_start);
436 @@ -1620,6 +1648,19 @@ int expand_upwards(struct vm_area_struct
437 /* Somebody else might have raced and expanded it already */
438 if (address > vma->vm_end) {
439 unsigned long size, grow;
440 +#ifdef CONFIG_STACK_GROWSUP
441 + unsigned long guard;
442 + struct vm_area_struct *vm_next;
445 + guard = min(TASK_SIZE - address,
446 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
447 + vm_next = find_vma(vma->vm_mm, address + guard);
448 + if (unlikely(vm_next && vm_next != vma)) {
449 + /* stack collision with another vma */
454 size = address - vma->vm_start;
455 grow = (address - vma->vm_end) >> PAGE_SHIFT;
456 @@ -1628,6 +1669,7 @@ int expand_upwards(struct vm_area_struct
458 vma->vm_end = address;
460 +out_unlock: __maybe_unused
461 anon_vma_unlock(vma);
464 @@ -1664,7 +1706,21 @@ static inline int expand_downwards(struc
465 /* Somebody else might have raced and expanded it already */
466 if (address < vma->vm_start) {
467 unsigned long size, grow;
468 + struct vm_area_struct *prev_vma;
470 + find_vma_prev(vma->vm_mm, address, &prev_vma);
474 + unsigned long guard;
476 + guard = min(TASK_SIZE - prev_vma->vm_end,
477 + (unsigned long)heap_stack_gap << PAGE_SHIFT);
478 + if (unlikely(prev_vma->vm_end + guard > address)) {
479 + /* stack collision with another vma */
483 size = vma->vm_end - address;
484 grow = (vma->vm_start - address) >> PAGE_SHIFT;
486 @@ -1674,6 +1730,7 @@ static inline int expand_downwards(struc
487 vma->vm_pgoff -= grow;
491 anon_vma_unlock(vma);