Linux Stack Auto-Growth and the Guard Gap
The Linux main-thread stack can grow toward lower virtual addresses when a program touches memory below its current VMA. This experiment maps pages around that boundary to see when growth succeeds.
The test attempts to grow the stack by writing one byte below its current VMA:
1 | volatile char* below_stack = |
When that address is unmapped, the resulting page fault can make Linux extend a downward-growing stack VMA by one page.
Linux also enforces stack_guard_gap, which defaults to 256 pages. This gap is
not a reserved or pre-mapped region. Linux applies it while choosing mmap
addresses and while expanding a stack. The kernel boot option
stack_guard_gap= can change its size.
The experiment uses MAP_FIXED_NOREPLACE so each mapping is attempted at an
exact address without replacing an existing VMA. Each case runs in its own
child process because two cases intentionally cause SIGSEGV. A child created
by fork() inherits the same virtual address layout, while its stack growth and
new mappings remain private to that child.
§Six cases
| Case | New mapping | Location | Result of growth attempt |
|---|---|---|---|
| 1 | Read/write | Immediately above stack.end |
Grows |
| 2 | Read/write | At stack.start |
mmap fails with EEXIST; stack grows |
| 3 | Read/write | Inside the guard gap | SIGSEGV |
| 4 | PROT_NONE |
Inside the guard gap | Grows |
| 5 | PROT_NONE |
Immediately below stack.start |
SIGSEGV |
| 6 | Read/write | Below the guard gap | Grows |
The cases run independently. The diagram places their mapping targets on one
address-space axis. P is one page, and G is the guard-gap size.
1 | higher addresses |
Cases 3 and 4 differ only in memory protection. Linux checks whether the VMA
below an expanding stack is accessible before enforcing the guard-gap
distance. The relevant part of expand_downwards() is:
1 | if (!(prev->vm_flags & VM_GROWSDOWN) && |
A read/write VMA is accessible, so case 3 fails the expansion check. A
PROT_NONE VMA is not accessible, so case 4 bypasses that check. Unmapped
space and the PROT_NONE range both provide inaccessible guard space at that
distance.
Case 5 reaches a different path. The faulting address is already covered by
the PROT_NONE VMA immediately below the stack. Linux handles it as a
protection fault in that VMA rather than as a request to extend the stack, so
the child receives SIGSEGV.
§The program
1 |
|
§Observed output
1 | stack: [0x7fffc163a000, 0x7fffc165c000], size: 136 KiB |
The guard gap protects the stack from growing too close to accessible memory.
It does not require all 256 pages to remain unmapped. Linux permits an
inaccessible PROT_NONE VMA within that distance, then stops growth when the
stack reaches the VMA itself.