pointers-memory · intermediate · ~25 min

Detect a buffer overflow with a canary

The mechanism behind GCC's -fstack-protector.

Challenge

Implement a stack-canary check by hand: a buffer is bracketed by two "canary" words holding a known magic value. If either word changes, something has overrun the buffer.

The struct and API:

typedef struct { uint32_t canary_lo; char buf[16]; uint32_t canary_hi; } guarded_t;
void guarded_init(guarded_t *g);
int  guarded_check(const guarded_t *g);   /* 1 if both canaries intact, 0 otherwise */

Task

Implement two functions (no main — the grader calls them):

  • guarded_init sets both canary_lo and canary_hi to the magic value 0xDEADBEEFu (and zeroes buf).
  • guarded_check returns 1 if both canaries still equal 0xDEADBEEFu, otherwise 0.

Input

A pointer to a guarded_t. (The grader simulates corruption by writing to a canary field directly — no real buffer overflow occurs, so this is safe under -fstack-protector.)

Output

guarded_check returns 1 (both canaries intact) or 0 (either has changed). guarded_init returns nothing.

Example

guarded_t g; guarded_init(&g);
guarded_check(&g)        ->   1
g.canary_hi = 0xCAFEBABE;
guarded_check(&g)        ->   0

Edge cases

  • Both canaries intact: return 1.
  • Either canary changed (high or low): return 0.

Rules

  • No allocations; just set / compare fields. Check both canaries, using the u suffix so the literal matches the uint32_t field.

Why this matters

Stack canaries are the cheap, ubiquitous defense against buffer overflows — every modern compiler emits them. Implementing the concept yourself in pure C makes the mechanism obvious.

Input format

A pointer to a guarded_t.

Output format

guarded_check returns 1 (intact) or 0 (corrupted); guarded_init returns nothing.

Constraints

No allocations; just set / compare fields. Check both canaries against 0xDEADBEEFu.

Starter code

#include <stdint.h>
#ifndef GUARDED_T_DEFINED
#define GUARDED_T_DEFINED
typedef struct { uint32_t canary_lo; char buf[16]; uint32_t canary_hi; } guarded_t;
#endif
void guarded_init(guarded_t *g);
int  guarded_check(const guarded_t *g);

Common mistakes

Setting the canary to 0 (any uninitialized memory looks like a corrupted canary); checking only one canary (real overflows can come from either side via under/over-runs); using == against the wrong magic word.

Edge cases to handle

Both canaries intact — return 1. Either canary changed — return 0.

Complexity

O(1).

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.