The oldest trick in memory-corruption exploitation is overflowing a local buffer to overwrite the saved return address, redirecting the CPU into attacker code. The stack canary is the defense that’s been quietly compiled into your binaries for two decades. It doesn’t prevent the overflow — it makes the program notice before it’s too late.

The Stack Layout Being Attacked

On a function call, the stack holds the local buffer, saved registers, and the return address the CPU will jump to when the function returns:

text
   higher addresses
   ┌────────────────────┐
   │  return address    │  ← overwrite this to hijack control
   │  saved frame ptr   │
   │  ...               │
   │  char buf[64]      │  ← overflow starts here, grows upward
   └────────────────────┘
   lower addresses

gets(buf) or an unchecked strcpy writes past buf, marches up through saved registers, and clobbers the return address. On ret, the CPU jumps wherever the attacker pointed.

The Canary

The compiler inserts a secret random value — the canary — between the local buffers and the return address. It’s written on entry and checked right before return:

text
   ┌────────────────────┐
   │  return address    │
   │  canary  (random)  │  ← any overflow reaching the return addr
   │  char buf[64]      │     must first overwrite this
   └────────────────────┘

A contiguous overflow that wants to reach the return address must trample the canary on the way. Before returning, the function compares the stored canary to the known-good value:

text
   __stack_chk_fail() if  stack_canary != fs:0x28

Mismatch → the program aborts immediately instead of returning into corrupted control flow. You’ve seen the result: *** stack smashing detected ***.

Why It’s Cheap and Where It Fails

The canary value lives in a per-thread location (the TLS, fs:0x28 on x86-64) and is randomized per process, so an attacker can’t just hardcode it. The runtime cost is a couple of instructions per protected function — the compiler only adds it to functions with stack buffers (-fstack-protector-strong).

It is not a cure. Attackers bypass canaries by:

  • Leaking the canary via a separate info-leak bug, then writing it back intact.
  • Overwriting non-contiguously — e.g. a bad array index that skips the canary and hits the return address directly.
  • Targeting other pointers (function pointers, GOT entries) that don’t sit behind the canary.

That’s why canaries ship alongside ASLR, NX/DEP, and CFI — defense in depth, not a single wall.

Takeaways

  • A stack canary is a random value placed between local buffers and the return address, checked before every protected return.
  • A linear overflow can’t reach the return address without destroying the canary first, so the program aborts instead of being hijacked.
  • It’s defeated by info leaks and non-linear writes — which is why it’s one layer among ASLR, NX, and CFI, not the whole defense.