Rust 因其雄伟的安全性能而备受爱好,尤其是在内存安全和线程安全方面。干系词,这是否意味着独一使用 Rust,就一定能幸免编写出不安全的代码呢?事实并非如斯。在某些场景下,蛊惑者不得不使用 unsafe Rust 来完成任务,这也带来了潜在的安全隐患。那么,如安在这些不可幸免的情况下,最猛过程地裁汰风险,确保代码的可靠性呢?
特斯拉工程师 Colin Breck 针对此问题撰文,回首了三种有用的实践步调,但愿能对蛊惑者有所裨益。
原文:https://blog.colinbreck.com/making-unsafe-rust-a-little-safer-tools-for-verifying-unsafe-code/
作家 | Colin Breck 责编 | 苏宓
出品 | CSDN(ID:CSDNnews)
Rust 之是以能成为一种流行的系统编程话语,其中一个原因是它具有出色的性能,同期不错在编译时摈斥内存和并发失误,而这些失误在其他具有雷同性能特色的话语(如 C 和 C++)中很难幸免。不外,蛊惑者不错通过编写 unsafe Rust 代码来绕过这些编译时查验。尽管绝大无数法子员不应编写不安全的 Rust 代码,但一些库出于性能需求、以及径直操作内存或硬件,八成与其他库和系统调用集成的地方,使用了不安全的 Rust 代码。
接下来,本文将探讨考据不安全 Rust 代码的用具,包括从 C 或 C++ 编写的库中调用的不安全代码。现下我念念要久了这一主题,主要塞方亦然念念为运营技艺(OT)和漏洞基础设施编写安全且可靠的软件。
内存检测用具 Sanitizers
Sanitizers 是一种启动时用具,成心用来检测法子启动中的问题,比如内存损坏、内存裸露或线程之间的数据竞争。它的责任旨趣是在编译代码时自动插入查验机制,匡助考据法子的行径是否夙昔。在使用 Sanitizers 时天然它会引入内存和性能支拨,但经常仅用于测试环境中。进攻的是,与编译器不同,Sanitizer 只可检测在启动时施行被实行的代码旅途中的失误——这不错通过测试或径直启动法子来杀青。
当我第一次得知 Rust 搭救用于查找失误的 Sanitizers 时,我感到很讶异。因为过往,我比拟练习如安在 C 和 C++ 中通过 Clang 和 LLVM 编译器使用 Sanitizers。由于 Rust 的编译器 rustc 亦然基于 LLVM 构建的,它雷同不错使用这些 Sanitizers。
内存越界拜访/缓冲区溢出
看一下底下的法子:
fn bad_address(i: i32) -> i32 {\nlet xs: [i32; 4] = [0, 1, 2, 3];\nxs[i as usize]\n}\n\n\nfn main() {\nlet v = bad_address(4);\nprintln!(\"Value at offset: {}\", v);\n}
当我使用 RUST_BACKTRACE=1 cargo run --release 启动法子时,Rust 的界限查验检测到了失误,法子会 panic(崩溃):
thread 'main' panicked at src/main.rs:3:5:\nindex out of bounds: the len is 4 but the index is 4\nstack backtrace:\n0: _rust_begin_unwind\n1: core::panicking::panic_fmt\n2: core::panicking::panic_bounds_check\n3: sanitizers::main
法子被隔断,这种情况可能是蛊惑者极不肯看到以至是难以继承的,尤其当该软件对漏洞基础设施的启动至关进攻时,可能会激发其他安全问题。干系词,启动时查验可确保法子永恒不会实行导致未界说行径的不安全代码。
当前筹商一种情况——要是该函数在一个 unsafe 代码块中使用指针索引数组会发生什么:
fn bad_address(i: i32) -> i32 {\nlet xs: [i32; 4] = [0, 1, 2, 3];\nunsafe { *xs.as_ptr().offset(i as isize) }\n}\n\nfn main() {\nlet v = bad_address(4);\nprintln!(\"Value at offset: {}\", v);\n}
在不安全代码中,Rust 编译器不再提供内存和线程安全的保险。法子员需要我方确保不安全代码是恰当规章的,况且不会导致未界说行径。当我启动这段代码时,即使法子读取了数组界限外的内存,也不会触发 panic。
Value at offset: 24576
Rust 的 AddressSanitizer 不错赞理查验代码中对堆栈和堆的越界拜访。它的旨趣是,AddressSanitizer 通过在内存分拨之间插入一些“红区”(red-zones),这些区域不可被拜访,同期使用影子内存(shadow memory)跟踪内存是否被造孽读写。要是法子拜访了不该碰的内存,AddressSanitizer 就会报错。需要防御的是,这个用具只可在 Rust 的 nightly 版块中使用,不可用在郑重版上。但别追念,nightly 和郑重版用具链不错同期安设,不会相互影响。要安设 nightly 用具链,你不错这么操作:
rustup install nightly
然后启动 AddressSanitizer 启动法子:
export RUSTFLAGS=-Zsanitizer=address\ncargo +nightly run
法子会因为越界拜访而崩溃,并生成注主义失误文告:
=================================================================\n==96148==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x00016dce67b0 at pc 0x00010211bf70 bp 0x00016dce6770 sp 0x00016dce6768\nREAD of size 4 at 0x00016dce67b0 thread T0\n#0 0x00010211bf6c in array_out_of_bounds_unsafe::bad_address::h9a9dae85f9ad5feb array_out_of_bounds_unsafe.rs:3\n#1 0x00010211c170 in array_out_of_bounds_unsafe::main::hc84cbff8319e0a2b array_out_of_bounds_unsafe.rs:7\n#2 0x00010211bd40 in core::ops::function::FnOnce::call_once::hc75a52fb9134d583 function.rs:250\n#3 0x00010211bd8c in std::sys::backtrace::__rust_begin_short_backtrace::h9c09c1d17c8393c3 backtrace.rs:152\n#4 0x00010211b888 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h3a3a442dfff79e34 rt.rs:195\n#5 0x000102135230 in std::rt::lang_start_internal::hc996363c321dd410+0x440 (array_out_of_bounds_unsafe:arm64+0x10001d230)\n#6 0x00010211b6c0 in std::rt::lang_start::hae3ff67dcefd99eb rt.rs:194\n#7 0x00010211c2e0 in main+0x20 (array_out_of_bounds_unsafe:arm64+0x1000042e0)\n#8 0x00019d87e0dc ()\n#9 0xf4687ffffffffffc ()\n\n\nAddress 0x00016dce67b0 is located in stack of thread T0 at offset 48 in frame\n#0 0x00010211bdbc in array_out_of_bounds_unsafe::bad_address::h9a9dae85f9ad5feb array_out_of_bounds_unsafe.rs:1\n\n\nThis frame has 1 object(s):\n[32, 48) 'xs' (line 2) <== Memory access at offset 48 overflows this variable\nHINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork\n(longjmp and C++ exceptions *are* supported)\nSUMMARY: AddressSanitizer: stack-buffer-overflow array_out_of_bounds_unsafe.rs:3 in array_out_of_bounds_unsafe::bad_address::h9a9dae85f9ad5feb\nShadow bytes around the buggy address:\n0x00016dce6500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n0x00016dce6580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n0x00016dce6600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n0x00016dce6680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n0x00016dce6700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n=>0x00016dce6780: f1 f1 f1 f1 00 00[f3]f3 00 00 00 00 00 00 00 00\n0x00016dce6800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n0x00016dce6880: 00 00 00 00 f1 f1 f1 f1 f8 f8 f2 f2 f8 f8 f8 f8\n0x00016dce6900: f8 f8 f2 f2 f2 f2 04 f3 00 00 00 00 00 00 00 00\n0x00016dce6980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n0x00016dce6a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\nShadow byte legend (one shadow byte represents 8 application bytes):\nAddressable: 00\nPartially addressable: 01 02 03 04 05 06 07\nHeap left redzone: fa\nFreed heap region: fd\nStack left redzone: f1\nStack mid redzone: f2\nStack right redzone: f3\nStack after return: f5\nStack use after scope: f8\nGlobal redzone: f9\nGlobal init order: f6\nPoisoned by user: f7\nContainer overflow: fc\nArray cookie: ac\nIntra object redzone: bb\nASan internal: fe\nLeft alloca redzone: ca\nRight alloca redzone: cb\n==96148==ABORTING
上述这一示例是在 debug 步地下启动的。要是在 release 步地下启动,由于编译器优化,可能无法识别到该失误。因此,在 release 构建中使用 Sanitizer 时,务必禁用编译器优化:
export RUSTFLAGS=\"-C opt-level=0 -Zsanitizer=address\"\ncargo +nightly run --release
值得一提的是,AddressSanitizer 不是每次齐能发现内存越界的问题。在前边的例子中,法子的发扬取决于我拜访数组时用的索引值:法子可能夙昔启动,也可能因为拜访了未知地址而报 SEGV 失误,八成因为堆栈溢出径直崩溃。
数据竞争
为了完好意思接头 Sanitizer(检测用具)2024欧洲杯官网- 欢迎您&,我还念念分享另一个示例,接头一下在不安全 Rust 代码中出现的失误不错通过 Sanitizer 检测到的步调。再来看一下以下代码,该代码从不同线程中的不安全代码拜访分享的可变变量:
fn main() {\nstatic mut A: usize = 0;\n\n\nlet t = std::thread::spawn(