跳到主要内容

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?

答案

  1. FFI:调用 C 函数
  2. 性能:在证明安全后跳过边界检查(get_unchecked
  3. 底层数据结构:自引用结构、侵入式链表
  4. 硬件操作:内存映射 IO

原则:unsafe 是最后手段,先尝试安全的替代方案。使用 unsafe 时必须封装在安全的 API 后面。

相关链接