上一篇的 Miri 能解释执行但很慢,也不能调 C 函数。Sanitizer 换了一条路:编译时在原生代码里插入检查,运行得快得多。这篇把常见的内存错误和数据竞争各跑一遍,看它们的报告长什么样、哪些在本机能用、代价多大。
源码:
1 | fn main() {
2 | let v = vec![1, 2, 3];
3 | let p = v.as_ptr();
4 | drop(v);
5 | println!("{}", unsafe { *p });
6 | }
-Zsanitizer=address 编译运行:退出码 -6
=================================================================
==92812==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000970 at pc 0x00010206d61c bp 0x00016dd92270 sp 0x00016dd92268
READ of size 4 at 0x602000000970 thread T0
#0 u164_uaf::main::h4690bcd4eeb5eab8 u164_uaf.rs:5
#1 core::ops::function::FnOnce::call_once::h81b718fb6564e259 function.rs:250
#2 std::sys::backtrace::__rust_begin_short_backtrace::h4808e6b10d96054f backtrace.rs:166
0x602000000970 is located 0 bytes inside of 12-byte region [0x602000000970,0x60200000097c)
freed by thread T0 here:
#0 free+0x70
#1 _$LT$alloc..raw_vec..RawVec$LT$T$C$A$GT$$u20$as$u20$core..ops..drop..Drop$GT$::drop::h9b52e4e4c87f2eea mod.rs:443
#2 core::ptr::drop_glue$LT$alloc..raw_vec..RawVec$LT$i32$GT$$GT$::hfbdd768f8728e56a mod.rs:848
previously allocated by thread T0 here:
#0 malloc+0x6c
#1 alloc::alloc::alloc::h4d231932ce95d638 alloc.rs:158
#2 alloc::alloc::Global::alloc_impl_runtime::hec25b63c3797b59e alloc.rs:335
SUMMARY: AddressSanitizer: heap-use-after-free u164_uaf.rs:5 in u164_uaf::main::h4690bcd4eeb5eab8
==92812==ABORTING对比解释执行的 Miri 见Miri 检查未定义行为。
源码:
1 | use std::hint::black_box;
2 | fn main() {
3 | let v = vec![10u32, 20, 30];
4 | let i = black_box(3);
5 | let x = unsafe { *v.as_ptr().add(i) };
6 | println!("{}", x);
7 | }
-Zsanitizer=address 编译运行:退出码 -6
=================================================================
==92843==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000097c at pc 0x000102095630 bp 0x00016dd6a2b0 sp 0x00016dd6a2a8
READ of size 4 at 0x60200000097c thread T0
#0 u164_oob::main::h68f0db86d30f4ac0 u164_oob.rs:5
#1 core::ops::function::FnOnce::call_once::h985e0e6e4afcdd18 function.rs:250
#2 std::sys::backtrace::__rust_begin_short_backtrace::h7748453a8d8cbf95 backtrace.rs:166
0x60200000097c is located 0 bytes after 12-byte region [0x602000000970,0x60200000097c)
allocated by thread T0 here:
#0 malloc+0x6c
#1 alloc::alloc::alloc::hd44f808ba37a7cc0 alloc.rs:158
#2 alloc::alloc::Global::alloc_impl_runtime::hdd866a2a3943b54e alloc.rs:335
SUMMARY: AddressSanitizer: heap-buffer-overflow u164_oob.rs:5 in u164_oob::main::h68f0db86d30f4ac0
==92843==ABORTING
源码:
1 | use std::hint::black_box;
2 | fn main() {
3 | let v = vec![10u32, 20, 30];
4 | let i = black_box(3);
5 | println!("{}", v[i]);
6 | }
-Zsanitizer=address 编译运行:退出码 101
thread 'main' (7610322) panicked at <实验目录>/src/u164_safe_oob.rs:5:21:
index out of bounds: the len is 3 but the index is 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 源码:
1 | use std::hint::black_box;
2 | fn main() {
3 | let a = [1u8, 2, 3, 4];
4 | let p = a.as_ptr();
5 | let i = black_box(4);
6 | println!("{}", unsafe { *p.add(i) });
7 | }
-Zsanitizer=address 编译运行:退出码 -6
=================================================================
==92913==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x00016d6523d4 at pc 0x0001027accc8 bp 0x00016d652370 sp 0x00016d652368
READ of size 1 at 0x00016d6523d4 thread T0
#0 u164_stack::main::h0ee85ba49f3c565c u164_stack.rs:6
#1 core::ops::function::FnOnce::call_once::h4c56f55464e056c3 function.rs:250
#2 std::sys::backtrace::__rust_begin_short_backtrace::hda93cf726b48ae27 backtrace.rs:166
Address 0x00016d6523d4 is located in stack of thread T0 at offset 84 in frame
#0 u164_stack::main::h0ee85ba49f3c565c u164_stack.rs:2
This frame has 3 object(s):
[32, 48) 'args' (line 6)
[64, 65) '_10' (line 6)
[80, 84) 'a' (line 3) <== Memory access at offset 84 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
…(共 16 行)
源码:
1 | fn main() {
2 | let b = Box::new(5u32);
3 | let p = Box::into_raw(b);
4 | unsafe { drop(Box::from_raw(p)); drop(Box::from_raw(p)); }
5 | }
-Zsanitizer=address 编译运行:退出码 -6
=================================================================
==92953==ERROR: AddressSanitizer: attempting double-free on 0x602000000970 in thread T0:
#0 free+0x70
#1 alloc::alloc::dealloc_nonnull::hd83b3ba2b3e2d01c alloc.rs:205
#2 alloc::alloc::Global::deallocate_impl_runtime::h9ea61a1be044d64f alloc.rs:354
0x602000000970 is located 0 bytes inside of 4-byte region [0x602000000970,0x602000000974)
freed by thread T0 here:
#0 free+0x70
#1 alloc::alloc::dealloc_nonnull::hd83b3ba2b3e2d01c alloc.rs:205
#2 alloc::alloc::Global::deallocate_impl_runtime::h9ea61a1be044d64f alloc.rs:354
previously allocated by thread T0 here:
#0 malloc+0x6c
#1 alloc::alloc::alloc::h67cc3907dedf19bb alloc.rs:158
#2 alloc::alloc::Global::alloc_impl_runtime::h0161344b0a1b8663 alloc.rs:335
SUMMARY: AddressSanitizer: double-free (librustc-nightly_rt.asan.dylib:arm64+0x3ef54) in free+0x70
==92953==ABORTING 源码:
1 | fn main() {
2 | let b = Box::new([1u8; 16]);
3 | std::mem::forget(b);
4 | println!("done");
5 | }
-Zsanitizer=address 编译运行:退出码 1
done
=================================================================
==92982==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 16 byte(s) in 1 object(s) allocated from:
#0 malloc+0x6c
#1 alloc::alloc::alloc::h5c1c31a4651401fe alloc.rs:158
#2 alloc::alloc::Global::alloc_impl_runtime::h8df4232c0085002b alloc.rs:335
SUMMARY: AddressSanitizer: 16 byte(s) leaked in 1 allocation(s).
-Zsanitizer=leak 编译: error: leak sanitizer is not supported for this target
-Zsanitizer=memory 编译: error: memory sanitizer is not supported for this target 源码:
1 | use std::hint::black_box;
2 | unsafe extern "C" { fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8; }
3 | fn main() {
4 | let mut dst = vec![0u8; 8];
5 | let src = [7u8; 16];
6 | unsafe { memcpy(dst.as_mut_ptr(), src.as_ptr(), black_box(16)) };
7 | println!("{}", dst[0]);
8 | }
-Zsanitizer=address 编译运行:退出码 -6
=================================================================
==93022==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000978 at pc 0x0001007ed1dc bp 0x00016fc062f0 sp 0x00016fc05aa0
WRITE of size 16 at 0x602000000978 thread T0
#0 memcpy+0x48c
#1 u164_ffi::main::h789552d2ef368346 u164_ffi.rs:6
#2 core::ops::function::FnOnce::call_once::hcfe6ab0c5df18b36 function.rs:250
0x602000000978 is located 0 bytes after 8-byte region [0x602000000970,0x602000000978)
allocated by thread T0 here:
#0 calloc+0x74
#1 _RNvMs8_NtCs3OnNwH6WCZa_5alloc7raw_vecNtB5_11RawVecInner15try_allocate_inCs6isvwMtXNgT_5gimli+0x60
#2 alloc::raw_vec::RawVecInner$LT$A$GT$::with_capacity_zeroed_in::h5e2911326f496cac mod.rs:610
SUMMARY: AddressSanitizer: heap-buffer-overflow (librustc-nightly_rt.asan.dylib:arm64+0x3d1d8) in memcpy+0x48c
==93022==ABORTING 源码:
1 | use std::thread;
2 | struct P(*mut u32);
3 | unsafe impl Send for P {}
4 | fn main() {
5 | let mut x = 0u32;
6 | let p = P(&mut x as *mut u32);
7 | let h = thread::spawn(move || {
8 | let p = p;
9 | for _ in 0..1000 { unsafe { *p.0 += 1 }; }
10 | });
11 | for _ in 0..1000 { unsafe { *(&mut x as *mut u32) += 1 }; }
12 | h.join().unwrap();
13 | println!("x = {}", x);
14 | }
只给自己的 crate 加 -Zsanitizer=thread(不重编标准库):退出码 101
error: mixing `-Zsanitizer` will cause an ABI mismatch in crate `ts`
|
= help: the `-Zsanitizer` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely
= note: `-Zsanitizer=thread` in this crate is incompatible with `-Zsanitizer` being unset in dependency `std`
= help: unset `-Zsanitizer` in this crate or set `-Zsanitizer=thread` in `std`
= help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=sanitizer` to silence this error
…(共 134 行)
加 -Zbuild-std(含重编标准库,共 12 秒):退出码 -6
==================
WARNING: ThreadSanitizer: data race (pid=93031)
Read of size 4 at 0x00016d7598bc by thread T1:
#0 ts::main::_$u7b$$u7b$closure$u7d$$u7d$::hdf5edab4845b39f7 main.rs:9
#1 std::sys::backtrace::__rust_begin_short_backtrace::h156f8089f55476ae backtrace.rs:166
#2 std::thread::lifecycle::spawn_unchecked::_$u7b$$u7b$closure$u7d$$u7d$::_$u7b$$u7b$closure$u7d$$u7d$::h53f13b1c4a46dbcc lifecycle.rs:70
Previous write of size 4 at 0x00016d7598bc by main thread:
#0 ts::main::hf31edbd70aaa0657 main.rs:11
#1 core::ops::function::FnOnce::call_once::h56d34f4555891b70 function.rs:250
#2 std::sys::backtrace::__rust_begin_short_backtrace::h65b730afc025650e backtrace.rs:166
Location is stack of main thread.
Thread T1 (tid=7611885, running) created by main thread at:
#0 pthread_create <null>
#1 std::sys::thread::unix::Thread::new::ha849696e57eca4aa <null>
#2 std::thread::lifecycle::spawn_unchecked::h4ad50f56bd654a92 lifecycle.rs:116
SUMMARY: ThreadSanitizer: data race main.rs:9 in ts::main::_$u7b$$u7b$closure$u7d$$u7d$::hdf5edab4845b39f7
==================
==================
WARNING: ThreadSanitizer: data race (pid=93031)
Write of size 4 at 0x00016d7598bc by thread T1:
#0 ts::main::_$u7b$$u7b$closure$u7d$$u7d$::hdf5edab4845b39f7 main.rs:9
#1 std::sys::backtrace::__rust_begin_short_backtrace::h156f8089f55476ae backtrace.rs:166
#2 std::thread::lifecycle::spawn_unchecked::_$u7b$$u7b$closure$u7d$$u7d$::_$u7b$$u7b$closure$u7d$$u7d$::h53f13b1c4a46dbcc lifecycle.rs:70
Previous write of size 4 at 0x00016d7598bc by main thread:
#0 ts::main::hf31edbd70aaa0657 main.rs:11
#1 core::ops::function::FnOnce::call_once::h56d34f4555891b70 function.rs:250
#2 std::sys::backtrace::__rust_begin_short_backtrace::h65b730afc025650e backtrace.rs:166
Location is stack of main thread.
Thread T1 (tid=7611885, running) created by main thread at:
#0 pthread_create <null>
#1 std::sys::thread::unix::Thread::new::ha849696e57eca4aa <null>
#2 std::thread::lifecycle::spawn_unchecked::h4ad50f56bd654a92 lifecycle.rs:116
SUMMARY: ThreadSanitizer: data race main.rs:9 in ts::main::_$u7b$$u7b$closure$u7d$$u7d$::hdf5edab4845b39f7
==================
ThreadSanitizer: reported 2 warnings
x = 2000数据竞争在安全代码里是编译错误,见数据竞争为什么编译不过;这里为了触发它用了 unsafe 的裸指针。
源码:
1 | use std::hint::black_box;
2 | use std::time::Instant;
3 | fn main() {
4 | let n = 20_000_000usize;
5 | let v: Vec<u32> = (0..n as u32).collect();
6 | let t = Instant::now();
7 | let mut s = 0u64;
8 | for i in 0..v.len() { s += unsafe { *v.as_ptr().add(black_box(i)) } as u64; }
9 | black_box(s);
10 | let a = t.elapsed().as_secs_f64() * 1000.0;
11 | let t = Instant::now();
12 | for i in 0..2_000_000u32 { let b = Box::new([i; 4]); black_box(&b); }
13 | let b = t.elapsed().as_secs_f64() * 1000.0;
14 | println!("{:.2} {:.2}", a, b);
15 | }
遍历 2000 万元素 (ms) 200 万次 Box::new (ms) 峰值内存 (MiB) 可执行文件 (KiB)
原生 -O 6.1 21.1 78 500
ASan -O 14.5 196.5 180 502模糊测试通常与 sanitizer 一起运行,见Fuzz 测试。