Skip to content

Commit bd7ea32

Browse files
committed
Support msan; Android/Linux: unpoison output of getrandom syscall.
See the added comment for details.
1 parent 8686806 commit bd7ea32

File tree

10 files changed

+184
-4
lines changed

10 files changed

+184
-4
lines changed

.github/workflows/tests.yml

+22
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,28 @@ jobs:
6666
- if: ${{ matrix.toolchain == 'nightly' }}
6767
run: cargo test --benches
6868

69+
sanitizer-tests:
70+
name: Run tests under Memory Sanitizer.
71+
runs-on: ${{ matrix.os }}
72+
strategy:
73+
matrix:
74+
os: [ubuntu-22.04]
75+
target: [
76+
x86_64-unknown-linux-gnu,
77+
]
78+
toolchain: [nightly]
79+
steps:
80+
- uses: actions/checkout@v3
81+
- uses: dtolnay/rust-toolchain@master
82+
with:
83+
targets: ${{ matrix.target }}
84+
toolchain: ${{ matrix.toolchain }}
85+
- uses: Swatinem/rust-cache@v2
86+
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize
87+
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize,std
88+
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize,linux_disable_fallback
89+
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize,custom
90+
6991
linux-tests:
7092
name: Linux Test
7193
runs-on: ubuntu-22.04

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ rustc-dep-of-std = [
5252
"libc/rustc-dep-of-std",
5353
"wasi/rustc-dep-of-std",
5454
]
55+
# Enable support for sanitizers; Requires Rust feature `cfg_sanitize`.
56+
unstable-sanitize = []
5557
# Unstable/test-only feature to run wasm-bindgen tests in a browser
5658
test-in-browser = []
5759

src/custom.rs

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
9696
// compatibility with implementations that rely on that (e.g. Rust
9797
// implementations that construct a `&mut [u8]` slice from `dest` and
9898
// `len`).
99+
// XXX: Because we do this, memory sanitizer isn't able to detect when
100+
// `__getrandom_custom` fails to fill `dest`, but we can't poison `dest`
101+
// here either, for the same reason we have to fill it in the first place.
99102
let dest = uninit_slice_fill_zero(dest);
100103
let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) };
101104
match NonZeroU32::new(ret) {

src/lib.rs

+16
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@
3434
//!
3535
//! Pull Requests that add support for new targets to `getrandom` are always welcome.
3636
//!
37+
//! ## Memory Sanitizer (msan) support
38+
//!
39+
//! The `unstable-sanitize` feature adds Memory Sanitizer support. You must use
40+
//! Rust Nightly, e.g.
41+
//! ```sh
42+
//! RUSTFLAGS="-Zsanitizer=memory" \
43+
//! cargo +nightly test \
44+
//! -Zbuild-std --target=x86_64-unknown-linux-gnu --features=unstable-sanitize
45+
//! ```
46+
//! It is assumed that libstd/libc have had their APis instrumented to support
47+
//! sanitizers, so we only provide special support on Linux when using the
48+
//! `getrandom` syscall.
49+
//!
3750
//! ## Unsupported targets
3851
//!
3952
//! By default, `getrandom` will not compile on unsupported targets, but certain
@@ -208,6 +221,7 @@
208221
#![no_std]
209222
#![warn(rust_2018_idioms, unused_lifetimes, missing_docs)]
210223
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
224+
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]
211225

212226
#[macro_use]
213227
extern crate cfg_if;
@@ -300,9 +314,11 @@ cfg_if! {
300314
mod use_file;
301315
mod lazy;
302316
mod linux_android;
317+
mod util_syscall_linux;
303318
#[path = "linux_android_with_fallback.rs"] mod imp;
304319
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
305320
mod util_libc;
321+
mod util_syscall_linux;
306322
#[path = "linux_android.rs"] mod imp;
307323
} else if #[cfg(target_os = "solaris")] {
308324
mod util_libc;

src/linux_android.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Implementation for Linux / Android without `/dev/urandom` fallback
2-
use crate::{util_libc, Error};
2+
use crate::{util_libc, util_syscall_linux, Error};
33
use core::mem::MaybeUninit;
44

55
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
@@ -8,12 +8,23 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
88

99
// Also used by linux_android_with_fallback to check if the syscall is available.
1010
pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
11-
unsafe {
11+
util_syscall_linux::pre_write_range(buf.as_mut_ptr(), buf.len());
12+
let res = unsafe {
1213
libc::syscall(
1314
libc::SYS_getrandom,
1415
buf.as_mut_ptr().cast::<core::ffi::c_void>(),
1516
buf.len(),
1617
0,
17-
) as libc::ssize_t
18-
}
18+
)
19+
} as libc::ssize_t;
20+
if let Ok(written) = usize::try_from(res) {
21+
// XXX: LLVM has support to do this automatically if/when libc is
22+
// compiled with it, but glibc that ships in typical Linux distros
23+
// doesn't. Assume Android's Bionic is similar. `-Zsanitizer=memory`
24+
// is not compatible with `+crt-static` according to rustc.
25+
unsafe {
26+
util_syscall_linux::post_write_range(buf.as_mut_ptr(), written);
27+
}
28+
};
29+
res
1930
}

src/util_syscall_linux.rs

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Support for raw system calls on Linux.
2+
//
3+
// # Sanitizers
4+
//
5+
// Currently only Memory Sanitizer is actively supported.
6+
//
7+
// TODO: Support address sanitizer, in particular in `pre_write_range`.
8+
//
9+
// ## Memory Sanitizer
10+
//
11+
// See https://github.com/llvm/llvm-project/commit/ac9ee01fcbfac745aaedca0393a8e1c8a33acd8d:
12+
// LLVM uses:
13+
// ```c
14+
// COMMON_INTERCEPTOR_ENTER(ctx, getrandom, buf, buflen, flags);
15+
// SSIZE_T n = REAL(getrandom)(buf, buflen, flags);
16+
// if (n > 0) {
17+
// COMMON_INTERCEPTOR_WRITE_RANGE(ctx, buf, n);
18+
// }
19+
// ```
20+
// and:
21+
// ```c
22+
// #define PRE_SYSCALL(name) \
23+
// SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_syscall_pre_impl_##name
24+
// #define PRE_WRITE(p, s) COMMON_SYSCALL_PRE_WRITE_RANGE(p, s)
25+
// #define POST_WRITE(p, s) COMMON_SYSCALL_POST_WRITE_RANGE(p, s)
26+
// PRE_SYSCALL(getrandom)(void *buf, uptr count, long flags) {
27+
// if (buf) {
28+
// PRE_WRITE(buf, count);
29+
// }
30+
// }
31+
//
32+
// POST_SYSCALL(getrandom)(long res, void *buf, uptr count, long flags) {
33+
// if (res > 0 && buf) {
34+
// POST_WRITE(buf, res);
35+
// }
36+
// }
37+
// ```
38+
39+
use core::mem::MaybeUninit;
40+
41+
// MSAN defines:
42+
//
43+
// ```c
44+
// #define COMMON_INTERCEPTOR_ENTER(ctx, func, ...) \
45+
// if (msan_init_is_running) \
46+
// return REAL(func)(__VA_ARGS__); \
47+
// ENSURE_MSAN_INITED(); \
48+
// MSanInterceptorContext msan_ctx = {IsInInterceptorScope()}; \
49+
// ctx = (void *)&msan_ctx; \
50+
// (void)ctx; \
51+
// InterceptorScope interceptor_scope; \
52+
// __msan_unpoison(__errno_location(), sizeof(int));
53+
// ```
54+
//
55+
// * We assume that memory sanitizer will not use the this crate during the
56+
// initialization of msan, so we don't have to worry about
57+
// `msan_init_is_running`.
58+
// * We assume that rustc/LLVM initializes MSAN before executing any Rust code,
59+
// so we don't need to call `ENSURE_MSAN_INITED`.
60+
// * Notice that `COMMON_INTERCEPTOR_WRITE_RANGE` doesn't use `ctx`, which
61+
// means it is oblivious to `IsInInterceptorScope()`, so we don't have to
62+
// call it. More generally, we don't have to worry about interceptor scopes
63+
// because we are not an interceptor.
64+
// * We don't read from `__errno_location()` so we don't need to unpoison it.
65+
//
66+
// Consequently, MSAN's `COMMON_INTERCEPTOR_ENTER` is a no-op.
67+
//
68+
// MSAN defines:
69+
// ```c
70+
// #define COMMON_SYSCALL_PRE_WRITE_RANGE(p, s) \
71+
// do { \
72+
// } while (false)
73+
// ```
74+
// So MSAN's PRE_SYSCALL hook is also a no-op.
75+
//
76+
// Consequently, we have nothing to do before invoking the syscall unless/until
77+
// we support other sanitizers like ASAN.
78+
#[allow(unused_variables)]
79+
pub fn pre_write_range(_ptr: *mut MaybeUninit<u8>, _size: usize) {}
80+
81+
// MSNA defines:
82+
// ```c
83+
// #define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \
84+
// __msan_unpoison(ptr, size)
85+
// ```
86+
// and:
87+
// ```c
88+
// #define COMMON_SYSCALL_POST_WRITE_RANGE(p, s) __msan_unpoison(p, s)
89+
// ```
90+
#[allow(unused_variables)]
91+
pub unsafe fn post_write_range(ptr: *mut MaybeUninit<u8>, size: usize) {
92+
#[cfg(feature = "unstable-sanitize")]
93+
{
94+
#[cfg(sanitize = "memory")]
95+
{
96+
use core::ffi::c_void;
97+
extern "C" {
98+
// void __msan_unpoison(const volatile void *a, size_t size);
99+
fn __msan_unpoison(a: *mut c_void, size: usize);
100+
}
101+
__msan_unpoison(ptr.cast::<c_void>(), size)
102+
}
103+
}
104+
}

tests/common/mod.rs

+19
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ fn test_huge() {
109109
fn test_huge_uninit() {
110110
let mut huge = [MaybeUninit::uninit(); 100_000];
111111
getrandom_uninit_impl(&mut huge).unwrap();
112+
check_initialized(&huge);
113+
}
114+
115+
#[allow(unused_variables)]
116+
fn check_initialized(buf: &[MaybeUninit<u8>]) {
117+
#[cfg(feature = "unstable-sanitize")]
118+
{
119+
#[cfg(sanitize = "memory")]
120+
{
121+
use core::ffi::c_void;
122+
extern "C" {
123+
// void __msan_check_mem_is_initialized(const volatile void *x, size_t size);
124+
fn __msan_check_mem_is_initialized(x: *const c_void, size: usize);
125+
}
126+
unsafe {
127+
__msan_check_mem_is_initialized(buf.as_ptr().cast::<c_void>(), buf.len());
128+
}
129+
}
130+
}
112131
}
113132

114133
// On WASM, the thread API always fails/panics

tests/custom.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
feature = "custom",
66
not(feature = "js")
77
))]
8+
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]
89

910
use wasm_bindgen_test::wasm_bindgen_test as test;
1011

tests/normal.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
feature = "custom",
66
not(feature = "js")
77
)))]
8+
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]
89

910
// Use the normal getrandom implementation on this architecture.
1011
use getrandom::{getrandom as getrandom_impl, getrandom_uninit as getrandom_uninit_impl};

tests/rdrand.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// We only test the RDRAND-based RNG source on supported architectures.
22
#![cfg(any(target_arch = "x86_64", target_arch = "x86"))]
3+
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]
34

45
// rdrand.rs expects to be part of the getrandom main crate, so we need these
56
// additional imports to get rdrand.rs to compile.

0 commit comments

Comments
 (0)