Rust 使用 secret_string 存储敏感信息如何避免内存被 swap 交换

文章导读
Rust 标准库没有内置防止 Swap 的 secret_string 类型,必须结合 mlock 系统调用或专用安全 crate 手动锁定内存页。适用场景为处理密码、私钥等高敏感数据,风险边界在于锁定内存会消耗物理 RAM 且可能导致进程因资源不足被杀。
📋 目录
  1. 快速处理思路
  2. 为什么会这样
  3. 分步处理
  4. 怎么验证是否生效
  5. 常见坑
  6. 常见问题
  7. 参考来源
A A

Rust 标准库没有内置防止 Swap 的 secret_string 类型,必须结合 mlock 系统调用或专用安全 crate 手动锁定内存页。适用场景为处理密码、私钥等高敏感数据,风险边界在于锁定内存会消耗物理 RAM 且可能导致进程因资源不足被杀。

先说结论:防止 Swap 的核心是调用 mlock 锁定物理页,而非依赖 Rust 所有权机制。

  • 先判断:确认数据是否值得消耗物理内存锁定
  • 优先做:使用 libc::mlock 或安全 crate 锁定分配区域
  • 再验证:检查/proc/self/status 确认 VmLck 变化

快速处理思路

通过 libc 调用 mlock 锁定存放敏感数据的堆内存区域,并在 Drop trait 中确保解锁和清零。

use libc::{mlock, munlock};
// 分配内存后立即锁定
unsafe {
    mlock(ptr as *const libc::c_void, len);
}
// 释放前解锁
unsafe {
    munlock(ptr as *const libc::c_void, len);
}

为什么会这样

Linux 内核在内存不足时会将不活跃的内存页移动到磁盘交换空间 (Swap),导致敏感数据持久化残留。

mlock 系统调用告诉内核不要让指定范围的内存页被交换到磁盘,确保数据仅存在于物理 RAM 中。Rust 的所有权系统管理生命周期,但不干预内核的页交换策略,因此必须显式调用系统 API。

分步处理

1. 引入依赖:在 Cargo.toml 添加 libc 或专用安全 crate。

2. 分配内存:使用 Vec<u8> 或 Box<[u8]> 分配足够空间。

3. 锁定内存:调用 mlock 锁定指针指向的区域,检查返回值是否为 0。

4. 实现 Drop:在结构体析构时先清零数据,再调用 munlock 解锁。

5. 错误处理:mlock 失败时应立即清零并终止敏感操作,避免数据暴露在未锁定内存中。

Rust 使用 secret_string 存储敏感信息如何避免内存被 swap 交换

怎么验证是否生效

运行程序后,在终端执行 cat /proc/self/status | grep VmLck,观察 VmLck 值是否随敏感数据分配而增加。

若 VmLck 大小为 0 kB,说明锁定未生效;若数值增加且接近锁定字节数,说明内存已被锁定。

常见坑

1. 资源限制:默认用户锁定内存限制可能过小,需用 ulimit -l 调整,否则 mlock 返回 ENOMEM。

2. 过度锁定:锁定过多内存会导致系统整体性能下降,甚至触发 OOM Killer 杀死进程。

3. 忘记清零:mlock 仅防止交换,不防止内存读取,释放前必须手动清零敏感数据。

常见问题

Rust 的 String 类型会自动防止 Swap 吗

不会。String 只是堆分配内存,内核仍可能将其交换到磁盘,必须手动锁定。

mlock 锁定后数据就绝对安全了吗

不是。mlock 仅防止磁盘交换,无法防御物理内存攻击或进程内未授权读取,需结合加密和清零。

参考来源

别再让敏感数据溜进 Swap 了:手把手教你用 mlock/mlockall 保护内存中的密码和密钥