FForge
← Labsdebug-01Runtime debugging●○○○

Null pointer dereference

debug/01_null_deref.c4 steps
0/4 steps
evidence to leave with
gdb backtrace naming the faulting line; ASan report classifying the access
walk it, one step at a time
run this
$ clang -g -O0 01_null_deref.c -o bin/01_null_deref
$ ./bin/01_null_deref ; echo "exit=$?"
what it printed on the machine that wrote this lab
about to read through a null pointer...
Segmentation fault (core dumped)
exit=139
(We never reach "x = ..." — it died on the read.)
run this
$ gdb -q -batch -ex 'set debuginfod enabled off' \
what it printed on the machine that wrote this lab
      -ex run -ex bt -ex 'print p' -ex 'info locals' \
      ./bin/01_null_deref

Program received signal SIGSEGV, Segmentation fault.
0x000000000040049c in main () at 01_null_deref.c:104
104     int x = *p;        // <-- THE BUG
#0  0x000000000040049c in main () at 01_null_deref.c:104
$1 = (int *) 0x0
p = 0x0
x = 0

How to read each command:
  run         -> runs the program inside gdb; it stops the instant the
                 signal is delivered, ON the faulting instruction.
  bt          -> backtrace (call stack). Here just frame #0 = main, and
                 it names the exact file:line (01_null_deref.c:104).
  print p     -> shows p == 0x0. The smoking gun: we read *through* 0.
  info locals -> all locals in the frame; confirms p=0x0 (x not set yet).
(`set debuginfod enabled off` suppresses gdb's interactive prompt asking
 whether to download missing debuginfo from the Fedora server — see
 README "debuginfod".)
run this
$ valgrind -q ./bin/01_null_deref
what it printed on the machine that wrote this lab
==46685== Invalid read of size 4
==46685==    at 0x40049C: main (01_null_deref.c:104)
==46685==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==46685== Process terminating with default action of signal 11 (SIGSEGV)
==46685==  Access not within mapped region at address 0x0
How to read it:
  "Invalid read of size 4"  -> a 4-byte (int) load that touched memory
                               it should not have.
  "Address 0x0 is not ..."  -> 0x0 isn't stack/heap/recently-freed: it is
                               simply unmapped. => NULL deref.

ASan variant (faster; also dumps registers):
run this
$ clang -g -O0 -fsanitize=address 01_null_deref.c -o bin/01_null_deref.asan
$ ./bin/01_null_deref.asan
what it printed on the machine that wrote this lab
==46704==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
==46704==The signal is caused by a READ memory access.
==46704==Hint: address points to the zero page.
    #0 0x4e9ea1 in main .../01_null_deref.c:104:13
"Hint: address points to the zero page" == NULL (or near-NULL) deref.
run this
$ clang -g -O0 -S -emit-llvm 01_null_deref.c -o 01.ll
$ opt -passes=verify 01.ll -disable-output     # -> "IR verified OK"
what it printed on the machine that wrote this lab
  Relevant IR inside main:
      store ptr null, ptr %2, align 8      ; p = NULL
      %5 = load ptr, ptr %2, align 8       ; tmp = p   (== null)
      %6 = load i32, ptr %5, align 4       ; *** = *p   <-- FAULTS HERE
  `%6 = load i32, ptr %5` is the dereference: load 4 bytes from the pointer
  we just loaded (null). opt -passes=verify reports the IR is *well formed*
  — verification checks STRUCTURE, not that a pointer is valid at runtime.
  (META-LESSON: a verifier is not a bug finder.)

ROOT CAUSE vs CRASH SITE:
  Here they nearly coincide, but the crash SITE is the load (`*p`) while the
  root CAUSE is the earlier assignment `int *p = NULL;`. The load merely
  exposes the bad VALUE set earlier. In real code the NULL usually comes
  from a function that returned NULL (malloc/fopen/lookup) far from the
  deref — walk the backtrace and `print` the pointer to find WHERE it
  became NULL.
category

segfault

difficulty

1 ------------------------------------------------------------

what's wrong

`p` is set to NULL (`int *p = NULL;`) and then dereferenced (`int x = *p;`). Reading the int at virtual address 0 is forbidden — the kernel never maps page 0 — so the CPU raises a fault. (Buggy line: `int x = *p;`.)

symptom

"Segmentation fault (core dumped)", shell exit code 139 (139 = 128 + signal 11 = 128 + SIGSEGV). Deterministic: address 0 is *never* mapped, so it crashes every run.

the fix

Give p real storage, or guard the deref: int v = 42; int *p = &v; // point at a real object or if (p) { int x = *p; ... } // never deref without a check

take it further

Change `int x = *p;` to `*p = 5;` (a WRITE). Re-run under gdb/valgrind: it now says "Invalid write" / "WRITE memory access". Same root cause, different access kind. ============================================================

read the source · debug/01_null_deref.c
#include <stdio.h> int main(void) { int *p = NULL; /* root cause: p points at address 0 */ printf("about to read through a null pointer...\n"); *p=5; int x = *p; /* <-- THE BUG: dereference of NULL */ printf("x = %d\n", x); 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.