FForge
← Labsdebug-14Runtime debugging●●○○

Off-by-one heap over-read

debug/14_off_by_one.c3 steps
0/3 steps
evidence to leave with
Invalid read 1 byte past the block
walk it, one step at a time
run this
$ clang -g -O0 14_off_by_one.c -o bin/14_off_by_one
$ ./bin/14_off_by_one ; echo "exit=$?"
what it printed on the machine that wrote this lab
sum = 10
exit=0
^ 0+1+2+3+4 == 10, and a[5] happened to be 0 this run, so the wrong answer
  LOOKS right. A different layout would give a different sum. Silent bug.

No fault, so `gdb run` exits 0. To SEE the off-by-one, step the loop and watch `i` cross n:

run this
$ gdb -q -batch -ex 'set debuginfod enabled off' \
what it printed on the machine that wrote this lab
      -ex 'break 84' -ex run -ex 'print n' -ex 'print i' \
      -ex 'print a[i]' -ex continue ./bin/14_off_by_one
Breakpoint 1, main () at 14_off_by_one.c:84
$1 = 5             # n
$2 = 0             # i on first hit
$3 = 0             # a[0]
... (set a breakpoint condition `break 84 if i==n` to stop exactly on
     the out-of-bounds iteration, then `print a[i]` shows garbage) ...
(break FILE:LINE, then print / continue, and conditional breakpoints
 `break ... if COND` are how you corner an off-by-one without a crash.)
run this
$ valgrind -q ./bin/14_off_by_one
what it printed on the machine that wrote this lab
  ==NNNNN== Invalid read of size 4
  ==NNNNN==    at 0x........: main (14_off_by_one.c:85)
  ==NNNNN==  Address 0x........ is 0 bytes after a block of size 20 alloc'd
  ==NNNNN==    at 0x........: malloc (vg_replace_malloc.c:...)
  ==NNNNN==    by 0x........: main (14_off_by_one.c:80)
  How to read it:
    "Invalid read of size 4 ... 0 bytes after a block of size 20" -> a[5] is
       immediately past a 20-byte (5-int) block => read one element too far.
  CONTRAST WITH BUG #04: there the array was on the STACK and valgrind saw
  NOTHING (no stack red zones). Here it's on the HEAP, which valgrind DOES
  guard, so memcheck catches it. Same off-by-one, different memory => the
  tool that works differs. (ASan catches BOTH stack and heap.)
run this
$ clang -g -O0 -fsanitize=address 14_off_by_one.c -o bin/14_off_by_one.asan
$ ./bin/14_off_by_one.asan
what it printed on the machine that wrote this lab
  ==NNNNN==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x....
  READ of size 4 at 0x.... thread T0
      #0 0x... in main .../14_off_by_one.c:85
  0x.... is located 0 bytes after 20-byte region ... allocated by ... main:80

ROOT CAUSE vs CRASH SITE:
  No crash. The bad READ is at line 85 (sum += a[i]); the ROOT CAUSE is the
  loop bound at line 84 (`<=` should be `<`). The fix is the comparison,
  not the read.
category

heap error / buffer over-read

difficulty

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

what's wrong

The array has n=5 elements (valid 0..4) but the sum loop uses `i <= n`, so it reads a[5] — one past the end. Classic off-by-one: `<=` where it should be `<`. Buggy line: `for (int i = 0; i <= n; i++)`.

symptom

SILENT: a[5] is whatever lies just past the block, so `sum` comes out wrong but the program exits 0. The answer is garbage that can change run to run. (Here it's a HEAP array, which matters for tooling — see STEP 3 vs bug #04.)

the fix

Use a strict bound: `for (int i = 0; i < n; i++)`. Off-by-one bugs almost always come down to `<=` vs `<` or `n` vs `n-1` — audit loop bounds first.

take it further

Change `i <= n` to `i < n`: valgrind and ASan go silent, sum is reliably 10. Then move the array to the stack (`int a[5];`) and keep `i <= n`: valgrind goes quiet again (bug #04's lesson) but ASan still fires. ============================================================

read the source · debug/14_off_by_one.c
#include <stdlib.h> #include <stdio.h> int main(void) { int n = 5; int *a = malloc(n * sizeof(int)); /* 5 ints: valid a[0..4] */ for (int i = 0; i < n; i++) a[i] = i; int sum = 0; for (int i = 0; i <= n; i++) /* <-- THE BUG: i<=n reads a[5] */ sum += a[i]; /* OOB read when i==n */ printf("sum = %d\n", sum); free(a); 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.