Stack Canaries are BTFOing your unsafe C

October 7, 2025 // 4 min read

cover

You wrote some C. It compiles. You run it, it segfaults. GDB says it crashed in a function you definitely wrote correctly. Then you notice it: stack smashing detected. The canary got you.

What even is a stack canary

When the compiler builds your function, it quietly slips a random value onto the stack between your local variables and the saved return address. Before the function returns, it checks that the value is still there. If it's been clobbered — by a buffer overflow, bad pointer arithmetic, whatever — it kills the process rather than let you return to god-knows-where.

It's named after the old mining practice of bringing a canary into a coal mine to detect carbon monoxide. The canary dies first. Same idea.

The value is picked at program startup from AT_RANDOM (a random 16-byte value the kernel drops into the process's auxiliary vector), so it's different every run and an attacker can't hardcode it.

What the generated code looks like

Compile any function with a local buffer without -fno-stack-protector and look at the disassembly:

void greet(char *name) {
    char buf[64];
    strcpy(buf, name);
    printf("Hello, %s\n", buf);
}
; prologue
mov    rax, QWORD PTR fs:0x28   ; load canary from TLS
mov    QWORD PTR [rbp-0x8], rax ; put it on the stack
 
; ... function body ...
 
; epilogue
mov    rax, QWORD PTR [rbp-0x8] ; read it back
xor    rax, QWORD PTR fs:0x28   ; XOR with original
jne    __stack_chk_fail          ; if != 0, we're dead

The canary lives in thread-local storage at fs:0x28 on x86-64 (or gs:0x14 on x86). The XOR trick means even a zeroed canary fails the check — you can't just overflow with nulls.

Why your overflow gets caught

A classic stack overflow overwrites memory linearly — you fill your buffer, keep going, and hit the canary before you reach the saved return address. The epilogue check fires, __stack_chk_fail is called, and the process prints *** stack smashing detected *** and aborts.

[  local buf  ][  canary  ][  saved rbp  ][  return addr  ]
      ^
      overflow starts here, hits canary before return addr

Bypassing it

A few techniques exist, none of them clean:

Leak the canary first. If you have an arbitrary read before you have an arbitrary write — a format string bug, for example — you can read fs:0x28 directly, then include the correct value in your overflow. The check passes.

// format string leaks stack values including the canary
printf(user_controlled_input);

Overwrite without crossing the canary. Some overflows are non-linear. If you control a pointer and can write to an arbitrary address, you skip the canary entirely. Heap exploits don't have canaries.

Brute force (only on forking servers). If the process forks rather than re-executes for each connection, the canary is the same value across forks. You can probe it one byte at a time — 256 guesses per byte, 2048 attempts total for a 64-bit canary. Apache's prefork MPM was historically vulnerable to this.

-fno-stack-protector. If whoever built the binary turned off canaries, they're just gone. Check with checksec:

checksec --file=./binary

Disabling it yourself

If you're writing exploit development tooling, doing CTF challenges, or just debugging without the noise:

# per-compilation
gcc -fno-stack-protector -o vuln vuln.c
 
# check what protections a binary has
checksec --file=./vuln

Output from checksec on a protected binary:

STACK CANARY: Canary found
RELRO:        Full RELRO
NX:           NX enabled
PIE:          PIE enabled

On one without:

STACK CANARY: No canary found
RELRO:        Partial RELRO
NX:           NX enabled
PIE:          No PIE

The limits

Canaries don't protect against:

  • Heap overflows — no canary on the heap
  • Off-by-one overwrites — if you only clobber the saved rbp and not the canary, you can still redirect execution
  • Use-after-free — entirely different class of bug
  • Format string bugs — these can leak the canary, making it useless

They're one layer. Combined with ASLR, NX, and PIE they make exploitation meaningfully harder. Alone, a determined attacker with a leak primitive will work around them.

tl;dr

Stack canaries are a compiler-inserted tripwire between your buffers and your return address. They catch naive linear overflows, they're defeated by leaks or non-linear writes, and you can turn them off with -fno-stack-protector if you have a reason to. Usually you don't.

If your program is getting killed by __stack_chk_fail, you have a buffer overflow. The canary is just the messenger.