In the x86-64 System V ABI it is specified that the space behind the $rsp - 128
is the so-called red zone which is not touched by any signal handlers. On my machine
$ ulimit -s
8192
I expected there is only 2 pages in the stack. So I wrote the following program to test till which size red zone can expand:
PAGE_SIZE equ 0x1000
SYS_exit equ 0x3C
section .text
global _start
_start:
lea rcx, [rsp - 0x1f * PAGE_SIZE]
mov rax, rsp
loop:
sub rax, PAGE_SIZE
mov qword [rax], -1
cmp rax, rcx
jne loop
mov rax, SYS_exit
mov rdi, 0x20
So I expected the program always fails. But the program sometimes fails with SEGV
, sometimes finishes fine.
The behavior is exactly as what MAP_GROWSDOWN
documents:
This flag is used for stacks. It indicates to the kernel virtual memory system that the mapping should extend downward in memory. The return address is one page lower than the memory area that is actually created in the process's virtual address space. Touching an address in the "guard" page below the mapping will cause the mapping to grow by a page. This growth can be repeated until the mapping grows to within a page of the high end of the next lower mapping, at which point touching the "guard" page will result in a
SIGSEGV
signal.
As discussed in this question mappings created with MAP_GROWSDOWN
and PROT_GROWSDOWN
does not grow that way:
volatile char *mapped_ptr = mmap(NULL, 4096,
PROT_READ | PROT_WRITE | PROT_GROWSDOWN,
MAP_GROWSDOWN | MAP_ANONYMOUS | MAP_PRIVATE,
-1, 0);
mapped_ptr[4095] = 'a'; //OK!
mapped_ptr[0] = 'b'; //OK!
mapped_ptr[-1] = 'c'; //SEGV
QUESTION: Combining the reasoning above is it true that the only mapping that uses MAP_GROWSDOWN
is the main thread's [stack]
mapping ?
None of this has anything to do with the red-zone because you aren't moving RSP. Memory protection works with page granularity, but the red-zone is always only 128 bytes below RSP that's safe to read/write as well as safe from async clobber.
No, nothing uses MAP_GROWSDOWN
unless you use it manually. The main thread's stack uses a non-broken mechanism that doesn't let other mmap
calls randomly steal its growth space. See my answer on Analyzing memory mapping of a process with pmap. [stack]
The sometimes-success of your asm code is an exact duplicate of Why does this code crash with address randomization on? - you're touching memory up to 124 KiB below RSP, so the initial allocation of 132 KiB happens to be enough sometimes, depending on ASLR and how much space args + env takes on the stack.
Why is MAP_GROWSDOWN mapping does not grow? is the interesting part: MAP_GROWSDOWN may not work with a 1-page mapping. But again, this has nothing to do with stacks. The man page saying "This flag is used for stacks." is 100% wrong. That was the intent when adding the feature, but the design isn't actually usable so the implementation may be buggy even vs. the documentation.