内存安全与常见漏洞
约 70% 的严重漏洞源于内存安全问题(Microsoft、Google Chrome 的多年统计一致)。本章讲栈溢出、释放后使用、整数溢出的原理,缓解措施与绕过(ROP),以及内存安全语言作为根本解法(→ 类型系统、流水线、冒险与乱序执行)。
栈溢出与返回地址覆盖
Section titled “栈溢出与返回地址覆盖”栈帧(→ 指令集架构)里返回地址与缓冲区相邻——经典的缓冲区溢出:
void vuln(const char *input) { char buf[64]; strcpy(buf, input); /* 不检查长度:input > 64 字节时 覆盖栈上相邻的返回地址 */}攻击:输入里塞入恶意机器码(shellcode)+ 恰好的填充 + 覆盖返回地址指向 shellcode——函数返回时跳去执行攻击者代码。**"控制指令指针"**是一切控制流劫持的原型:覆盖返回地址、函数指针、虚表指针(C++ 的 vtable 劫持)。
释放后使用(UAF)与堆漏洞
Section titled “释放后使用(UAF)与堆漏洞”释放后使用(use-after-free):释放后的内存被再次分配给新对象,旧指针还在用——类型混淆:把新对象当旧对象读写。
- UAF 是浏览器漏洞的主力(复杂对象图 + 大量回调);同类:双重释放(double free)、未初始化使用;
- 堆漏洞的通用形态:堆元数据(chunk 头)被破坏 → 后续分配被操纵 → 任意写。
整数溢出(→ 信息的表示)为内存漏洞铺路:
if (len < sizeof(buf)) { ... } /* len 是 int:负数绕过检查 */malloc(len); /* len = -1 → 巨大的无符号数 */先溢出(检查被绕过)再溢出(分配过小)——组合拳:整数溢出通常不直接执行代码,而是让后续的长度检查失效。
缓解措施与绕过
Section titled “缓解措施与绕过”每层缓解提高攻击成本(不消除漏洞):
| 缓解 | 做法 | 绕过 |
|---|---|---|
| 栈保护(canary) | 栈帧放金丝雀值,返回前校验 | 泄漏/重写 canary;不保护函数指针 |
| DEP/NX | 数据页不可执行 | ROP(见下) |
| ASLR | 栈/堆/库地址随机化 | 信息泄漏(读指针)+ 喷射 |
| CFI | 间接跳转校验合法目标 | 粒度内的跳转 |
**ROP(return-oriented programming)**是 DEP 时代的标准绕过:不注入代码,重用已有代码——把程序里以 ret 结尾的指令片段(gadget)串成链条:控制栈上的一串"返回地址",每个 gadget 做一小步(设置寄存器、调系统调用)。DEP 防注入防不了重用——缓解措施的军备竞赛由此展开(CFI、CET 收紧重用)。
缓解措施提高攻击成本但不消除漏洞:每层缓解把"一次攻击"变成"泄漏 + 绕过 + 重用"的多步链条——成本提高,可行仍在(见侧信道,见下)。
侧信道:Spectre 类
Section titled “侧信道:Spectre 类”**侧信道(side channel)**绕过所有传统隔离:不攻破边界,测量时间/缓存/功耗的副效应:
- Spectre:推测执行的缓存副作用(→ 流水线、冒险与乱序执行)——诱导推测执行越界读,用缓存命中与否编码秘密;
- Meltdown:乱序执行的越界读取在异常前留下缓存痕迹;
- Rowhammer:反复刷新相邻内存行导致位翻转。
侧信道(Spectre)绕过了所有传统隔离:同核的进程、容器、甚至同超线程的进程都可能被测量——缓解(边界检查屏障、禁用推测)付性能税;根本解法在硬件与新语言。
内存安全语言:根本解法
Section titled “内存安全语言:根本解法”根本解法是消除漏洞的类别:
- Rust:所有权 + 借用检查在编译期消除 UAF、越界、数据竞争(→ 类型系统)——Rust 的所有权在编译期消除整类漏洞;
- 托管语言(Java/Go/Python):GC + 边界检查——运行时保证内存安全,代价是 GC 停顿与性能;
- C/C++ 的现实:存量代码巨大,迁移是长期工程——渐进策略:新代码用安全语言(Android/Chrome 的 Rust 渗入数据:内存安全漏洞占比显著下降),旧代码上 sanitizers(ASan/UAF、UBSan)+ fuzzing(→ 测试)。
约 70% 的严重漏洞源于内存安全——这条曲线唯一显著的下降来自"新代码不再用 C/C++",不是来自更多缓解。
- 栈溢出的原型是控制指令指针(覆盖返回地址/函数指针/虚表)
- UAF 是浏览器漏洞的主力:释放后复用造成类型混淆;整数溢出让长度检查失效
- 缓解措施提高攻击成本但不消除漏洞:canary/DEP/ASLR/CFI 各有绕过;ROP 重用已有代码绕过 DEP
- 侧信道(Spectre)绕过了所有传统隔离:测量推测执行的缓存副效应
- Rust 的所有权在编译期消除整类漏洞;根本解法是内存安全语言,缓解是军备竞赛