On x86/ARM the stack grows toward lower addresses, but a fixed local buffer is filled from its own low address upward. A function that copies attacker-controlled input into that buffer without checking its length (e.g. strcpy, gets) writes straight past the end of the buffer into whatever sits next in memory, in this order:
buffer[B] → canary[4] → saved frame ptr[8] → return address[8] → caller's stack…
If the write length L exceeds the buffer size B, the overflow is:
overflow = max(0, L − B)
Once overflow reaches far enough, it silently corrupts adjacent variables, then the saved frame pointer, then the return address itself — the exact value the CPU will jump to when the function executes RET. Overwrite it with an attacker-chosen address and execution redirects into attacker-controlled code (classic stack-smashing, CWE-121).
- Stack canary — a random value written directly after the buffer and re-checked immediately before
RET. Any overflow touches the canary first; a mismatch triggers __stack_chk_fail() and the process aborts before the corrupted return address is ever used.
- ASLR — randomizes the base address of the stack on every run. It does not stop the overwrite, but without an information leak the attacker cannot reliably guess where their payload lives, so a hijacked return address is far more likely to crash the process than to execute successfully.
This is the same failure mode behind decades of real-world exploits (Morris Worm 1988, countless CTF "pwn" challenges) and the reason every modern compiler enables stack canaries and ASLR by default.