Show HN: 我构建了一个用于安全运行不安全代码的 Rust crate
mem-isolate
: 安全运行不安全代码
mem-isolate
通过 fork()
运行你的函数,等待结果,并返回它。
这使你的代码可以访问调用前内存和状态的精确副本,但保证该函数不会以任何方式影响父进程的内存占用。
它强制函数成为 内存纯函数 (在内存方面是纯的),即使它们不是。
use mem_isolate::execute_in_isolated_process;
// No heap, stack, or program memory out here...
let result = mem_isolate::execute_in_isolated_process(|| {
// ...Can be affected by anything in here
unsafe {
gnarly_cpp_bindings::potentially_leaking_function();
unstable_ffi::segfault_prone_function();
heap_fragmenting_operation();
something_that_panics_in_a_way_you_could_recover_from();
}
});
示例用例:
- 运行具有已知内存泄漏的代码
- 运行会碎片化堆的代码
- 运行
unsafe
代码 - 运行你的代码慢 1ms ( har har 😉,请参阅局限性)
注意:由于大量使用 POSIX 系统调用,此 crate 仅支持类 Unix 操作系统(例如,Linux、macOS、BSD)。 目前没有计划支持 Windows 和 wasm。 请参阅 examples/ 获取更多用法,尤其是基本错误处理示例。
工作原理
POSIX 系统使用 fork()
系统调用创建一个新的子进程,该子进程是父进程的副本。 在现代系统上,即使父进程在调用时使用大量内存,这也很便宜(~1ms)。 这是因为操作系统使用写时复制内存技术来避免复制父进程的整个内存。 在调用 fork()
时,父进程和子进程都共享内存中的相同物理页面。 只有当其中一个修改页面时,它才会被复制到新位置。
mem-isolate
使用此实现细节作为一个巧妙的技巧,为 callable
函数提供一个临时且隔离的内存空间。 你可以将此隔离视为在调用 execute_in_isolated_process()
时拍摄了程序内存的快照,一旦用户提供的 callable
函数执行完毕,该快照就会被恢复。
当调用 execute_in_isolated_process()
时,该进程将:
- 创建一个
pipe()
用于进程间通信,位于调用它的进程(“父进程”)和将要创建以隔离和运行你的callable
的新子进程之间 fork()
一个新的子进程- 在子进程中执行用户提供的
callable
,并通过管道将其结果返回给父进程 - 使用
waitpid()
等待子进程完成 - 将结果返回给父进程
我们称这个技巧为“fork and free”模式。 这非常巧妙。 🫰
局限性
- 仅适用于 POSIX 系统(Linux、macOS、BSD)
- 从
callable
函数返回的数据必须序列化到子进程和从子进程序列化(使用serde
),这对于大型数据而言可能代价高昂。 - 除了序列化/反序列化成本之外,与直接调用
callable
相比,execute_in_isolated_process()
引入了大约 ~1ms 的运行时开销。
在性能关键型系统中,这些开销可能不容忽视。 但是,对于许多用例来说,这是对 mem-isolate
提供的内存安全性和快照行为的一种可承受的权衡。
基准测试
在一个简单的基准测试中,原始函数调用约为 ~1.5ns,fork()
+ 等待约为 ~1.7ms,而 execute_in_isolated_process()
约为 1.9ms。 相比之下,这非常慢,但对于许多内存安全至关重要的用例来说,这是可以容忍的。
cargo bench
Finished `bench` profile [optimized] target(s) in 0.07s
Running unittests src/lib.rs (target/release/deps/mem_isolate-d96fcfa5f2fd31c0)
running 3 tests
test tests::simple_example ... ignored
test tests::test_static_memory_mutation_with_isolation ... ignored
test tests::test_static_memory_mutation_without_isolation ... ignored
test result: ok. 0 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/benchmarks.rs (target/release/deps/benchmarks-25c74db99f107a73)
Overhead/direct_function_call
time: [1.4347 ns 1.4357 ns 1.4370 ns]
change: [-1.4983% +0.6412% +3.4486%] (p = 0.55 > 0.05)
No change in performance detected.
Found 11 outliers among 100 measurements (11.00%)
4 (4.00%) high mild
7 (7.00%) high severe
Overhead/fork_alone time: [1.6893 ms 1.6975 ms 1.7062 ms]
change: [+1.3025% +3.8968% +5.7914%] (p = 0.01 < 0.05)
Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
Overhead/execute_in_isolated_process
time: [1.8769 ms 1.9007 ms 1.9226 ms]
change: [-7.6229% -5.7657% -3.7073%] (p = 0.00 < 0.05)
Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) low severe
所有基准测试都在配备 AMD Ryzen 5 PRO 7540U CPU @ 3.2Ghz 最大时钟速度和 32 GB 内存的 ThinkPad T14 Gen 4 AMD (14″) 笔记本电脑上运行。使用的操作系统是带有 Linux 内核 6.12 的 Debian 13。
许可证
mem-isolate
在以下任一协议下获得双重许可:
你可以选择其一。
贡献
除非你明确说明,否则你有意提交以包含在该作品中的任何贡献均应获得上述双重许可,且没有任何其他条款或条件。