FForge
← Labsdebug-07Runtime debugging●●○○

Double free

debug/07_double_free.c3 steps
0/3 steps
evidence to leave with
glibc abort backtrace; ASan attempting double-free with both free stacks
walk it, one step at a time
run this
$ clang -g -O0 07_double_free.c -o bin/07_double_free
$ ./bin/07_double_free ; echo "exit=$?"
what it printed on the machine that wrote this lab
first free done
free(): double free detected in tcache 2
Aborted (core dumped)
exit=134
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/07_double_free
free(): double free detected in tcache 2
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  0x00007ffff7e2e665 in malloc_printerr () from /lib64/libc.so.6
#5  0x00007ffff7e33b18 in tcache_double_free_verify () from /lib64/libc.so.6
#6  0x00000000004004d0 in main () at 07_double_free.c:89
How to read it:
  bt bottom-up: frame #6 is YOUR code — main at line 89, the SECOND
  free. Frames above are glibc's detector firing: tcache_double_free_verify
  -> malloc_printerr -> abort -> raise. gdb hands you the offending free.
run this
$ valgrind -q ./bin/07_double_free
what it printed on the machine that wrote this lab
  ==59011== Invalid free() / delete / delete[] / realloc()   (heap addr repr.)
  ==59011==    at 0x4843E43: free (vg_replace_malloc.c:990)
  ==59011==    by 0x4004CF: main (07_double_free.c:89)
  ==59011==  Address 0x4a6a040 is 0 bytes inside a block of size 4 free'd
  ==59011==    at 0x4843E43: free (vg_replace_malloc.c:990)
  ==59011==    by 0x4004B5: main (07_double_free.c:87)
  ==59011==  Block was alloc'd at
  ==59011==    by 0x4004A8: main (07_double_free.c:86)
  How to read it:
    "Invalid free()" at main (...:89)  -> the bad (second) free.
    "Address ... is ... a block ... free'd at main (...:87)" -> it was
       ALREADY freed here. valgrind shows both frees + the original malloc.
  (Note: under valgrind the program does NOT abort the same way — valgrind
   intercepts free, reports, and lets it continue, so you may see exit 0.)
run this
$ clang -g -O0 -fsanitize=address 07_double_free.c -o bin/07_double_free.asan
$ ./bin/07_double_free.asan
what it printed on the machine that wrote this lab
  ==59026==ERROR: AddressSanitizer: attempting double-free on 0x7be25bde0010 in thread T0:
      #0 0x4a593a in free
      #1 0x4e9e7f in main .../07_double_free.c:89:5
  freed by thread T0 here:
      #1 0x4e9e65 in main .../07_double_free.c:87:5
  previously allocated by thread T0 here:
      #1 0x4e9e58 in main .../07_double_free.c:86:14
  ASan says it plainly: "attempting double-free", with the second free
  (89), the first free (87), and the allocation (86).

ROOT CAUSE vs CRASH SITE: The crash is inside glibc's `free`, but your bug is the redundant second `free(p)` at line 89 — the root cause is ownership confusion (two code paths both think they own and must free p).

category

heap error

difficulty

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

what's wrong

`free(p)` is called twice on the same pointer. The second free corrupts the allocator's bookkeeping. Buggy line: the SECOND `free(p);`.

symptom

Modern glibc DETECTS this and aborts: "free(): double free detected in tcache 2", SIGABRT, exit 134 (128 + 6). Deterministic here (glibc tcache check).

the fix

Free exactly once; null the pointer so a second free is a harmless no-op: free(p); p = NULL; // free(NULL) is defined and does nothing

take it further

Insert `p = NULL;` between the two frees. The program now runs clean (free(NULL) is a no-op) — exit 0, no abort. ============================================================

read the source · debug/07_double_free.c
#include <stdlib.h> #include <stdio.h> int main(void) { int *p = malloc(sizeof(int)); /* allocate one int */ free(p); /* first free: legitimate */ printf("first free done\n"); free(p); /* <-- THE BUG: second free of same ptr */ 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.