FForge
← Labsdebug-04Runtime debugging●●○○

Stack array out-of-bounds write

debug/04_stack_oob_write.c3 steps
0/3 steps
evidence to leave with
Plain run silent; canary aborts at return; ASan points at the write; valgrind misses it
walk it, one step at a time
run this
$ clang -g -O0 04_stack_oob_write.c -o bin/04_stack_oob_write
$ ./bin/04_stack_oob_write ; echo "exit=$?"
what it printed on the machine that wrote this lab
buf[0]=0 (looks fine?!)
exit=0
^ Silent corruption. Nothing told you buf[4..8] were written OOB.

A stack canary makes it LOUD (corruption detected at function return):
run this
$ clang -g -O0 -fstack-protector-strong 04_stack_oob_write.c -o bin/04_stack_oob_write.canary
$ ./bin/04_stack_oob_write.canary ; echo "exit=$?"
what it printed on the machine that wrote this lab
*** stack smashing detected ***: terminated
Aborted (core dumped)
exit=134                              # 134 = 128 + 6 (SIGABRT)
run this
$ gdb -q -batch -ex 'set debuginfod enabled off' -ex run -ex bt \
what it printed on the machine that wrote this lab
      ./bin/04_stack_oob_write.canary
*** stack smashing detected ***: terminated
Program received signal SIGABRT, Aborted.
0x00007ffff7e243cc in __pthread_kill_implementation () from /lib64/libc.so.6
#0  0x00007ffff7e243cc in __pthread_kill_implementation () from /lib64/libc.so.6
#1  0x00007ffff7dca15e in raise () from /lib64/libc.so.6
#2  0x00007ffff7db16d0 in abort () from /lib64/libc.so.6
#3  0x00007ffff7db273b in __libc_message_impl.cold () from /lib64/libc.so.6
#4  0x00007ffff7eb0f19 in __fortify_fail () from /lib64/libc.so.6
#5  0x00007ffff7eb22a4 in __stack_chk_fail () from /lib64/libc.so.6
#6  0x00000000004004ed in main () at 04_stack_oob_write.c:113
How to read it:
  bt  -> read it bottom-up. Frame #6 is YOUR code: main at line 113 —
         the closing brace / RETURN. Frames #5..#0 are the canary tripwire
         firing: __stack_chk_fail -> __fortify_fail -> abort -> raise.
  KEY: the canary is checked at function RETURN (line 113), so gdb points
       you at the return, NOT at the overflowing write (line 111). The
       canary tells you "something smashed the stack", not WHERE. (See
       ROOT CAUSE below — escalate to ASan for the exact write.)
run this
$ valgrind -q ./bin/04_stack_oob_write ; echo "exit=$?"
what it printed on the machine that wrote this lab
  buf[0]=0 (looks fine?!)
  exit=0
  ^ valgrind/memcheck reports NOTHING. memcheck has no red zones around
    STACK arrays, so an overflow that stays within the mapped stack page is
    invisible to it. META-LESSON: "valgrind clean" != "no bug".
run this
$ clang -g -O0 -fsanitize=address 04_stack_oob_write.c -o bin/04_stack_oob_write.asan
$ ./bin/04_stack_oob_write.asan ; echo "exit=$?"
what it printed on the machine that wrote this lab
  ==NNNNN==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x.... at pc 0x....
  WRITE of size 4 at 0x.... thread T0
      #0 0x... in main .../04_stack_oob_write.c:111:16
  Address 0x.... is located in stack of thread T0 at offset 48 in frame
      #0 0x... in main .../04_stack_oob_write.c:108
    This frame has 1 object(s):
      [32, 48) 'buf' (line 109) <== Memory access at offset 48 overflows this variable
  ... Shadow bytes around the buggy address: ...
  =>0x............: f1 f1 f1 f1 00 00[f3]f3 00 00 ...
  exit=1
  How to read ASan:
    "stack-buffer-overflow" + "WRITE of size 4" -> a 4-byte (int) write past
       an array on the stack, at .../04_stack_oob_write.c:111 column 16.
    "[32,48) 'buf' ... offset 48 overflows" -> buf occupies bytes 32..47 of
       the frame (16 bytes = 4 ints); the access at offset 48 is one int too
       far. ASan names the VARIABLE and the exact write — far more precise
       than the canary or gdb.
    Shadow legend: f1 = left redzone, 00 = addressable (buf's 16 bytes),
       f3 = right redzone (poisoned guard). The [..] marks the byte hit.
       ASan surrounds every stack array with poisoned redzones; touching one
       is an instant, pinpointed error — which is why ASan catches what
       valgrind misses here, and ~10x faster too.
    (ASan addresses are ASLR-randomized: representative — yours will differ.)

ROOT CAUSE vs CRASH SITE:
  Three different tools point at three different lines, and understanding why
  IS the lesson:
    - Plain build: no crash at all (corruption is silent).
    - Canary build / gdb: aborts at the RETURN (line 113) — that's when the
      smashed canary is *checked*, long after the damage.
    - ASan: the WRITE itself (line 111) — the true root cause.

The corruption happens at line 111; everything downstream is a delayed symptom. Always prefer the tool that flags the WRITE, not the aftermath.

category

stack error / buffer overflow

difficulty

2 ------------------------------------------------------------

what's wrong

`buf` has 4 ints (valid indices 0..3) but the loop runs `i <= 8`, so it writes buf[4]..buf[8] — up to 5 ints PAST the end of a stack array. Buggy line: `buf[i] = i;` for i>=4.

symptom

DANGEROUSLY QUIET. With default flags on this box it does NOT crash and exits 0 — the writes land in adjacent stack padding. (This is the trap: "it ran fine" is not "it's correct".) A stack canary turns it into an abort at RETURN; ASan reports the exact overflowing write.

the fix

Use a correct bound (strictly < the element count): for (int i = 0; i < 4; i++) buf[i] = i; Or size the loop from the array: `for (i=0; i < (int)(sizeof buf/sizeof buf[0]); i++)`.

take it further

Change `i <= 8` to `i <= 4` (write just ONE past the end). Re-run the ASan build: it STILL fires on buf[4] — ASan's redzone catches even a 1-element overflow that the plain build and valgrind sail right past. ============================================================

read the source · debug/04_stack_oob_write.c
#include <stdio.h> int main(void) { int buf[4]; /* valid indices: 0..3 */ for (int i = 0; i <= 8; i++) /* bound is wrong: i reaches 8 */ buf[i] = i; /* <-- THE BUG: OOB stack write for i>=4 */ printf("buf[0]=%d (looks fine?!)\n", buf[0]); return 0; }
◇
Finish the lab: write the evidence note

A lab is done when you can say what it showed on your machine — not when you have read it.