FForge
← Labsdebug-10Runtime debugging●●○○

Returning a pointer to a local

debug/10_return_local.c4 steps
0/4 steps
evidence to leave with
ASan stack-use-after-return (with detect_stack_use_after_return)
walk it, one step at a time
run this
$ clang -g -O0 10_return_local.c -o bin/10_return_local
what it printed on the machine that wrote this lab
10_return_local.c:79:13: warning: address of stack memory associated
    with local variable 'local' returned [-Wreturn-stack-address]
^ Never ignore -Wreturn-stack-address. Build with -Wall and read warnings.
run this
$ ./bin/10_return_local ; echo "exit=$?"
what it printed on the machine that wrote this lab
*p (first read) = 42
exit=...        # varies: the slot is reused after the printf call
^ It "works" once then returns garbage — a textbook flaky dangling-pointer
  bug. No crash to anchor on.

The plain build doesn't fault, so run the sanitized build under gdb so it STOPS at the bad access and you can inspect frames:

run this
$ clang -g -O0 -fsanitize=address 10_return_local.c -o bin/10_return_local.asan
$ ASAN_OPTIONS=abort_on_error=1 gdb -q -batch \
what it printed on the machine that wrote this lab
     -ex 'set debuginfod enabled off' -ex run -ex bt ./bin/10_return_local.asan
... ASan halts; bt shows main at 10_return_local.c:84 (the deref) ...
run this
$ ./bin/10_return_local.asan
what it printed on the machine that wrote this lab
  ==NNNNN==ERROR: AddressSanitizer: stack-use-after-return on address 0x....
      #0 0x... in main .../10_return_local.c:84
  Address 0x.... is located in stack of thread T0 at offset 32 in frame
      #0 0x... in get_ptr .../10_return_local.c:77
  How to read it:
    "stack-use-after-return"  -> you used a stack slot after its function
       returned. ASan names BOTH ends: the USE in main (line 84) and the
       FRAME it belonged to — get_ptr (line 77). That second name is the
       whole bug. (stack-use-after-return is ON by default in this clang.)
run this
$ valgrind -q ./bin/10_return_local
what it printed on the machine that wrote this lab
  ==NNNNN== Use of uninitialised value of size 8
  ==NNNNN==    at 0x...: _itoa_word (...)      # deep inside printf
  ==NNNNN==    by 0x...: main (10_return_local.c:84)
  META-LESSON: valgrind does NOT say "stack-use-after-return". The dead
  stack slot is still MAPPED memory, so memcheck can't call the address
  invalid; it only notices, indirectly, that the bytes are uninitialised
  once printf consumes them. ASan (with frame red zones) is the right tool
  for dangling-stack bugs; valgrind is weak here.

ROOT CAUSE vs CRASH SITE: The symptom shows up in main (the deref, line 84) or even deep inside printf — far from the cause. The ROOT CAUSE is `return &local;` (line 79): a pointer escaping its object's lifetime. ASan's "in frame get_ptr" line walks you straight back to the guilty function.

category

stack error / dangling pointer

difficulty

3 ------------------------------------------------------------

what's wrong

`get_ptr` returns `&local`, the address of an automatic variable. `local` ceases to exist when `get_ptr` returns, so the caller holds a pointer into a dead stack frame. Buggy line: `return &local;`.

symptom

Usually SILENT and FLAKY. The first deref often still reads 42 (the slot hasn't been reused yet); a later deref reads garbage once another call (e.g. printf) reuses that stack. No crash; exit code varies. The COMPILER even warns at build.

the fix

Don't return the address of a local. Return by value, or allocate storage that outlives the call (heap, or caller-provided buffer): int get_val(void) { int local = 42; return local; } // by value

take it further

Make `local` `static int local = 42;`. The warning and the ASan error both vanish: a static has program lifetime, so &local is valid forever. (Now it's just not reentrant — a different concern.) ============================================================

read the source · debug/10_return_local.c
#include <stdio.h> __attribute__((noinline)) static int *get_ptr(void) { int local = 42; /* lives only for this call */ return &local; /* <-- THE BUG: address of a dead local */ } int main(void) { int *p = get_ptr(); /* p now dangles into a freed frame */ printf("*p (first read) = %d\n", *p); /* deref dangling pointer */ return *p & 0xff; /* read again: slot may be reused now */ }
◇
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.