unsafe 代码审计
问题
如何审计和评估 Rust 中的 unsafe 代码?
答案
unsafe 允许的 5 种操作
| 操作 | 风险 |
|---|---|
| 解引用裸指针 | 空指针、悬垂指针 |
| 调用 unsafe 函数 | 依赖调用者保证前置条件 |
| 访问/修改可变静态变量 | 数据竞争 |
| 实现 unsafe trait | 必须满足 trait 的安全不变量 |
| 访问 union 的字段 | 类型混淆 |
审计清单
// ✅ 好的 unsafe:最小化范围 + 安全注释
/// 从字节切片创建字符串,调用者保证是合法 UTF-8
///
/// # Safety
/// `bytes` 必须是合法的 UTF-8 编码
pub unsafe fn from_utf8_unchecked(bytes: &[u8]) -> &str {
// SAFETY: 调用者保证 bytes 是合法 UTF-8
unsafe { std::str::from_utf8_unchecked(bytes) }
}
// ❌ 坏的 unsafe:大范围 unsafe 块,无注释
unsafe {
// 几十行代码...
// 不知道哪一行是 unsafe 操作
// 没有 SAFETY 注释
}
审计要点
| 检查项 | 问题 |
|---|---|
| 裸指针来源 | 指针是否有效?是否可能悬垂? |
| 生命周期 | unsafe 是否绕过了生命周期检查? |
| 线程安全 | 是否正确实现了 Send/Sync? |
| 不变量 | 安全代码能否破坏 unsafe 建立的不变量? |
| SAFETY 注释 | 每个 unsafe 块是否有清晰的安全理由? |
| 范围最小化 | unsafe 块是否尽可能小? |
工具辅助
# cargo-geiger:统计 unsafe 使用
cargo install cargo-geiger
cargo geiger
# 输出每个 crate 的 unsafe 统计
# 🔒 = 无 unsafe,☢️ = 有 unsafe
# miri:运行时检测 UB
cargo +nightly miri test
常见面试问题
Q1: 什么时候必须用 unsafe?
答案:
- FFI:调用 C 函数
- 性能:在证明安全后跳过边界检查(
get_unchecked) - 底层数据结构:自引用结构、侵入式链表
- 硬件操作:内存映射 IO
原则:unsafe 是最后手段,先尝试安全的替代方案。使用 unsafe 时必须封装在安全的 API 后面。