2026-08-21
Every kernel thread has a fixed-size stack — 16KB on x86-64 (THREAD_SIZE). Until Linux 4.9 (2016), that stack was carved out of the direct-mapped kernel region: a contiguous slab of physical pages, allocated by the page allocator, with the thread_info struct sitting at the bottom. If your kernel code recursed too deep or blew the stack with a giant on-stack buffer, the write silently smashed whatever happened to be adjacent in physical memory — often another task's thread_info, sometimes a slab cache header. The kernel would keep running, corrupted, until something dereferenced the wreckage minutes or hours later. Debugging was miserable.
CONFIG_VMAP_STACK changed this. Kernel stacks are now allocated in vmalloc space, where the kernel can arrange virtual-to-physical mappings freely. The allocator maps four 4KB physical pages into a virtually contiguous 20KB region — but only the middle 16KB is actually mapped. The 4KB immediately below the stack is a guard page: a virtual address with no PTE. Overflow one byte past the stack limit and you hit an unmapped page — instant #PF, immediate oops with a full backtrace pointing at the exact function that overflowed.
The cost. Vmalloc addresses require populating a fresh page table walk on first use (they're not in the direct map), and freeing them requires a TLB shootdown across all CPUs. Every fork() and thread exit now pays this. The kernel added a per-CPU cache of freed stacks (default 2 per CPU) to amortize this — reuse a cached stack and you skip the vmalloc round trip entirely.
Concrete example. An XFS bug around 2017 recursed through xfs_alloc_ag_vextent deep enough to overflow the stack. On the old allocator, it corrupted a neighboring task_struct and the machine crashed in the scheduler an hour later with no useful trace. On vmap stacks, the same bug produced an immediate oops naming the XFS function that overflowed. Fix landed in days instead of weeks.
Rule of thumb. The x86-64 kernel stack is 16KB. Subtract ~1KB for interrupt frames and the pt_regs at the top, another ~500 bytes for typical entry-path frames. You have maybe 14KB of usable depth. A single function with a char buf[8192] on the stack has already spent more than half of it — never put buffers over ~1KB on the kernel stack, use kmalloc().
