A05 —— use-after-free:释放后再访问的问题
金句:释放一块内存后继续使用它,就像在一块已经卖掉的地皮上继续建房子——迟早会有人来拆。
什么是 use-after-free
use-after-free(UAF)是指访问已经 free 掉的内存——指针还在,但指向的内容已经被回收并可能被重新分配。
UAF 的典型例子:
char *buf = malloc(100);
strcpy(buf, "hello");
free(buf); // 释放内存
// buf 指针仍然有效(指向原来的地址)
// 但这块内存已经归还给系统
printf("%sn", buf); // 危险!可能读到垃圾数据
strcpy(buf, "world"); // 更危险!可能覆盖其他程序的数据
现代 malloc 的行为:
- free 后,指针不变
- 内存归还 bin,可能被后续 malloc 分配给其他代码
- UAF 会导致数据泄漏或覆写UAF 的危害
UAF 的严重后果:
1. 数据泄漏:
buf = malloc(100);
strcpy(buf, "secret password");
free(buf);
// 另一个程序 malloc,可能分配到同一块内存
other_buf = malloc(100);
// 如果旧的 "secret" 还没被清零,可能被读出
2. 任意代码执行(最严重):
class A { virtual void foo() {} };
A *obj = new A();
delete obj; // 释放对象
// 指针还在,指向已释放的内存
obj->foo(); // 调用虚函数表
// 如果攻击者在释放的内存上布置假的虚函数表
// 可以控制程序执行流程
3. 崩溃:
- 内存可能被其他代码覆写
- 访问时可能触发 crashUAF 的利用(Exploitation)
UAF 的利用步骤(简化):
1. 分配对象 A(包含函数指针/虚表)
obj = malloc(sizeof(A)); // 假设 64 bytes
2. 释放对象 A
free(obj);
// obj 指针仍在,指向已释放的内存
3. 攻击者分配同等大小的块,写入恶意数据
fake_obj = malloc(64);
// 写入假的虚函数表地址
4. 程序使用 obj(不知道已经释放)
obj->virtual_method();
// → 调用 fake_obj 中的假函数表
// → 执行任意代码
现代缓解措施:
- Heap hardening:free 后写入保护字节(canary)
- 安全删除:释放后清零指针
- Heap integrity check:tcache/fastbin 检查 double-free检测 UAF 的工具
1. valgrind --track-origins=yes:
valgrind --track-origins=yes ./program
输出:
==12345== Invalid read of size 8
==12345== at 0x...: use_after_free (prog.c:20)
==12345== by 0x...: main (prog.c:30)
2. AddressSanitizer(ASan):
gcc -fsanitize=address -g program.c
./program
ASan 在每次 access 时检查:
- 内存是否在 quarantine 中(已释放但未重用)
- 是否越界
ASan 的 quarantine:
- free 后,内存不会立即归还 buddy
- 延迟一段时间(re分配更困难)
- 检测 UAF 更容易
3. GDB 的 heap analysis:
GDB 可以设置 watchpoint:
(gdb) watch *0x12345678
(gdb) run
当地址被写入时,触发 watchpoint防止 UAF 的方法
1. 释放后置 NULL:
char *buf = malloc(100);
free(buf);
buf = NULL; // 释放后立即置 NULL
// 使用前检查
if (buf != NULL) {
// 使用 buf
}
问题:代码中所有使用 buf 的地方都要检查
2. 作用域管理(RAII):
std::vector<char> buf(100);
// 作用域结束,自动释放
// 不存在 UAF 问题
3. 智能指针:
auto buf = std::make_unique<char[]>(100);
// 离开作用域,自动 free
// 不存在 UAF 问题
4. 工具:clang 的 -fuse-ld=gold + sanitize:
$ clang -fsanitize=address -g program.c
$ ./program
→ ASan 会在运行时检测所有 UAF
5. 安全的删除接口(自定义):
template<typename T>
void safe_delete(T*& p) {
delete p;
p = nullptr;
}总结
- UAF:use-after-free,访问已 free 的内存
- 危害:数据泄漏(读到别人的数据)/ 任意代码执行(覆写虚函数表)
- 利用:释放对象 → 攻击者分配同大小块写入假数据 → 程序使用原指针 → 执行攻击者代码
- 检测:valgrind / ASan(ASan 有 quarantine 延迟重用,更容易检测)
- 防止:释放后置 NULL / RAII / 智能指针 / safe_delete
下篇预告(A06):C++ 的 new/delete 和 malloc/free 有什么区别?new 的构造和 delete 的析构,以及 operator new 的底层实现。
关注公众号「AI不着急」,回复”资料”获取内存学习路线图。
评论