C++ Use-After-Free (UAF): Causes, ASan, and Ownership Rules
이 글의 핵심
UAF causes, AddressSanitizer, ownership with smart pointers, and practical gdb workflows.
What is use-after-free?
Using memory after it has been freed or after the object’s lifetime ended.
int* ptr = new int(10);
delete ptr;
*ptr = 42; // undefined behavior
Common causes
// 1. Access after delete
int* ptr = new int(10);
delete ptr;
std::cout << *ptr << std::endl;
// 2. Dangling pointer return
int* getPointer() {
int* ptr = new int(10);
delete ptr;
return ptr;
}
// 3. Double free then use
int* ptr = new int(10);
delete ptr;
delete ptr;
*ptr = 42;
// 4. Container invalidation
std::vector<int> vec = {1, 2, 3};
int* ptr = &vec[0];
vec.clear();
*ptr = 42;
Examples
Example 1: Basic UAF
#include <iostream>
void useAfterFree() {
int* ptr = new int(10);
std::cout << *ptr << std::endl;
delete ptr;
std::cout << *ptr << std::endl; // UAF
}
void safeUse() {
int* ptr = new int(10);
std::cout << *ptr << std::endl;
delete ptr;
ptr = nullptr;
if (ptr) {
std::cout << *ptr << std::endl;
}
}
void smartPointer() {
auto ptr = std::make_unique<int>(10);
std::cout << *ptr << std::endl;
}
Example 2: Dangling reference
#include <string>
const std::string& getNameBad() {
std::string name = "Alice";
return name;
}
std::string getNameGood() {
std::string name = "Alice";
return name;
}
Example 3: Iterator / pointer invalidation
#include <vector>
void iteratorInvalidation() {
std::vector<int> vec = {1, 2, 3};
int* ptr = &vec[0];
vec.push_back(4); // may reallocate
std::cout << *ptr << std::endl; // possibly UAF
}
void useIndex() {
std::vector<int> vec = {1, 2, 3};
size_t index = 0;
vec.push_back(4);
std::cout << vec[index] << std::endl;
}
Example 4: Object lifetime
#include <memory>
class Widget {
public:
int value = 42;
~Widget() { }
};
void ownershipTransfer() {
auto ptr = std::make_unique<Widget>();
Widget* raw = ptr.get();
ptr.reset();
std::cout << raw->value << std::endl; // UAF
}
void keepOwnership() {
auto ptr = std::make_unique<Widget>();
std::cout << ptr->value << std::endl;
ptr.reset();
}
void sharedOwnership() {
auto ptr1 = std::make_shared<Widget>();
auto ptr2 = ptr1;
ptr1.reset();
std::cout << ptr2->value << std::endl;
}
Common pitfalls
Pitfall 1: Double free
void doubleFree() {
int* ptr = new int(10);
delete ptr;
delete ptr;
*ptr = 42;
}
void safeFree() {
int* ptr = new int(10);
delete ptr;
ptr = nullptr;
delete ptr;
}
Pitfall 2: Returning freed pointers
std::unique_ptr<int> createSafe() {
return std::make_unique<int>(10);
}
Pitfall 3: Lambda capture
#include <functional>
std::function<int()> createGetterGood() {
int value = 10;
return [value]() { return value; };
}
std::function<int()> createGetterShared() {
auto ptr = std::make_shared<int>(10);
return [ptr]() { return *ptr; };
}
Pitfall 4: Member pointers past container lifetime
class Container {
int* data;
public:
Container() : data(new int(10)) {}
~Container() { delete data; }
int* getData() { return data; }
};
void useContainer() {
int* ptr;
{
Container c;
ptr = c.getData();
}
*ptr = 42; // UAF
}
Detection
g++ -fsanitize=address -g program.cpp
./a.out
valgrind --tool=memcheck ./program
clang-tidy program.cpp
cppcheck --enable=all program.cpp
Mitigation patterns
auto ptr = std::make_unique<int>(10);
delete ptr;
ptr = nullptr;
Debugging tips (MSVC-style example)
#ifdef _DEBUG
void* operator new(size_t size) {
void* ptr = malloc(size);
memset(ptr, 0xCD, size);
return ptr;
}
#endif
Typical UAF root causes
- Raw pointer after explicit free: Dereferencing after
delete/free; settingnullptronly blocks some mistakes—reuse of the same address can still hide logic bugs. - Ownership vs borrow confusion: Returning interior pointers that outlive the owning container or object.
- Iterator/pointer invalidation: Reallocation in
vector, mutatingstring, etc. - Async callbacks: Lambdas capturing stack addresses or raw pointers to destroyed objects.
- Double free: Corrupts heap metadata; later operations become UAF or crashes.
Catching UAF early with AddressSanitizer
With ASan, most UAFs are reported immediately with allocation stack, free stack, and access site.
g++ -std=c++17 -O1 -g -fsanitize=address -fno-omit-frame-pointer uaf.cpp -o uaf
ASAN_OPTIONS=abort_on_error=1:detect_stack_use_after_return=1 ./uaf
detect_stack_use_after_return=1catches more stack lifetime bugs (higher overhead).- Reports include shadow memory details—compare the nearest
delete/freewith the failing access.
Valgrind Memcheck also catches UAF but is slower; CI often uses ASan builds.
Smart pointers and ownership
std::unique_ptr: Single owner—do not use the old raw pointer after transfer; considerstd::exchangeto make intent obvious.std::shared_ptr/std::weak_ptr: Break cycles withweak_ptrfor caches and graphs.- Observers: For non-owning pointers, adopt a consistent convention (e.g. non-owning
T*with documented lifetime) and follow C++ Core Guidelines on ownership.
Rule of thumb: If only one place calls delete, UAF from forgotten pairing drops sharply—keep new/delete at library boundaries and use RAII internally.
Real-world patterns
- Event loops / timers: If the object is destroyed before the callback, guard with
weak_ptror unregister in the destructor. - C APIs: One place owns
free; copying or double-closing handles causes UAF—wrap in RAII and control move/copy. string_viewlifetime: A view must not outlive the owningstd::string; store astringif you need persistence.
Workflow: gdb + ASan
- Read the ASan ERROR line and source location first.
- In gdb,
bt full; if lines look wrong due to inlining, try-O0or disable optimization for that TU. - Trace pointer lifetime: search for alloc/free pairs for the same logical object.
- Minimize the repro and add a regression test.
Without ASan, platform debug heaps or allocation hooks help but are costly—prefer an ASan configuration in daily dev.
FAQ
Q1: When does UAF happen?
A: After free, dangling pointers, invalidation.
Q2: Detection?
A: ASan, Valgrind, static analysis.
Q3: Prevention?
A: Smart pointers, clear ownership, RAII.
Q4: Symptoms?
A: Crashes, flaky behavior, corruption.
Q5: Is nullptr check enough?
A: Partially—smart pointers and lifetime discipline are stronger.
Q6: Resources?
A: Effective C++, ASan docs, CWE-416.
See also
- C++ heap corruption
- C++ memory leak
- C++ buffer overflow
Practical tips
Debugging
- Warnings first
- Minimal repro
Performance
- Profile before optimizing
Code review
- Team conventions
Checklist
Before coding
- Right fix?
- Maintainable?
- Performance OK?
While coding
- Warnings?
- Edge cases?
- Errors?
At review
- Clear?
- Tests?
- Docs?
Keywords
C++, use-after-free, memory, safety, AddressSanitizer, debugging.
Related posts
- C++ buffer overflow
- C++ heap corruption
- C++ memory leak
- C++ sanitizers
- C++ Valgrind