Lab 01 — Debugging Dojo: Find the Planted Bug

Phase: 10 — Reliability & Debugging | Difficulty: ⭐⭐⭐⭐☆ | Time: 3–5 hours Language: C11 | Hardware: none | Needs: a C compiler with ASan; optional gdb/valgrind

Concept primer: ../WARMUP.md Ch. 1–4, ../HITCHHIKERS-GUIDE.md §1–§4.

Run

make          # normal build — buggy may APPEAR to work (a latent bug)
make asan     # AddressSanitizer build — points at the exact bug
make valgrind # alternative detector
make fixed    # the corrected version, clean under ASan

0. The mission

Practice the method, not the answer: a deliberate memory-safety bug lives in buggy.c. Find it with tools (ASan/gdb/valgrind), explain it, fix it, and write the RCA (RCA.md). The point is that a normal build may run fine — latent memory bugs are why you need sanitizers, and why "it works on my machine" is not "it's correct."

1. The exercise (don't peek at fixed.c first)

  1. make && ./buggy — observe. It may print plausibly or crash, depending on your libc. That ambiguity is the lesson.
  2. make asan — AddressSanitizer reports heap-use-after-free at print_all (buggy.c:45) with two stack traces: where the memory was freed and where it was used. That's the localization tools give you for free.
  3. Confirm with gdb ./buggy (run, backtrace) and/or valgrind.
  4. Find the root cause in remove_reading (it frees a node without unlinking it → the previous node's ->next dangles → UAF on the next traversal, and a double-free at cleanup).
  5. Fix it (unlink before free; a sentinel handles head-removal — see fixed.c), and verify make fixed is clean under ASan.
  6. Write RCA.md — the writeup is the deliverable. Drive the 5 Whys to the systemic cause (no ASan in CI, no list-mutation tests) and list owned action items.

2. Why this matters

This is the JD's #1 skill in miniature: a real, latent, close-to-hardware bug found by method and tools, not luck — and turned into a permanent fix via a blameless RCA. Memory-safety bugs are the top cause of CVEs and the hardest production crashes (they crash far from their cause); ASan/TSan in CI (Phase 11) is how you stop them.

3. Extensions

  1. Plant a race in a threaded version and find it with TSan (-fsanitize=thread).
  2. Debug a hung process: add a blocking read on a pipe that never gets data; find it with strace -p.
  3. Core-dump analysis: ulimit -c unlimited, crash it, then gdb ./buggy core and backtrace.
  4. git bisect: make a repo with the bug introduced in a middle commit and bisect to it.

4. What this lab proves

You debug with method and the right tool, you fix root causes (not symptoms), and you close the loop with a blameless RCA + preventive action items. "Tell me about a hard bug you root-caused" becomes a real story (STAR — Phase 12), and "you suspect memory corruption — what tools?" becomes a demonstration.