Skip to content

Commit

Permalink
Support msan; Android/Linux: unpoison output of getrandom syscall.
Browse files Browse the repository at this point in the history
See the added comment for details.
  • Loading branch information
briansmith committed Jun 7, 2024
1 parent 8686806 commit e53f6e9
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 4 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ jobs:
- if: ${{ matrix.toolchain == 'nightly' }}
run: cargo test --benches

sanitizer-tests:
name: Sanitizer Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04]
target: [
x86_64-unknown-linux-gnu,
]
toolchain: [nightly]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
components: rust-src
targets: ${{ matrix.target }}
toolchain: ${{ matrix.toolchain }}
- uses: Swatinem/rust-cache@v2
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize,std
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize,linux_disable_fallback
# Doctests fail to link so use `--all-targets` to avoid them.
- run: RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=${{ matrix.target }} --features=unstable-sanitize,custom --all-targets

linux-tests:
name: Linux Test
runs-on: ubuntu-22.04
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ rustc-dep-of-std = [
"libc/rustc-dep-of-std",
"wasi/rustc-dep-of-std",
]
# Enable support for sanitizers; Requires Rust feature `cfg_sanitize`.
unstable-sanitize = []
# Unstable/test-only feature to run wasm-bindgen tests in a browser
test-in-browser = []

Expand Down
3 changes: 3 additions & 0 deletions src/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// compatibility with implementations that rely on that (e.g. Rust
// implementations that construct a `&mut [u8]` slice from `dest` and
// `len`).
// XXX: Because we do this, memory sanitizer isn't able to detect when
// `__getrandom_custom` fails to fill `dest`, but we can't poison `dest`
// here either, for the same reason we have to fill it in the first place.
let dest = uninit_slice_fill_zero(dest);
let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) };
match NonZeroU32::new(ret) {
Expand Down
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@
//!
//! Pull Requests that add support for new targets to `getrandom` are always welcome.
//!
//! ## Memory Sanitizer (msan) support
//!
//! The `unstable-sanitize` feature adds Memory Sanitizer support. You must use
//! Rust Nightly, e.g.
//! ```sh
//! RUSTFLAGS="-Zsanitizer=memory" \
//! cargo +nightly test \
//! -Zbuild-std --target=x86_64-unknown-linux-gnu --features=unstable-sanitize
//! ```
//! It is assumed that libstd/libc have had their APis instrumented to support
//! sanitizers, so we only provide special support on Linux when using the
//! `getrandom` syscall.
//!
//! ## Unsupported targets
//!
//! By default, `getrandom` will not compile on unsupported targets, but certain
Expand Down Expand Up @@ -208,6 +221,7 @@
#![no_std]
#![warn(rust_2018_idioms, unused_lifetimes, missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]

#[macro_use]
extern crate cfg_if;
Expand Down Expand Up @@ -300,9 +314,11 @@ cfg_if! {
mod use_file;
mod lazy;
mod linux_android;
mod util_syscall_linux;
#[path = "linux_android_with_fallback.rs"] mod imp;
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
mod util_libc;
mod util_syscall_linux;
#[path = "linux_android.rs"] mod imp;
} else if #[cfg(target_os = "solaris")] {
mod util_libc;
Expand Down
19 changes: 15 additions & 4 deletions src/linux_android.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Implementation for Linux / Android without `/dev/urandom` fallback
use crate::{util_libc, Error};
use crate::{util_libc, util_syscall_linux, Error};
use core::mem::MaybeUninit;

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

// Also used by linux_android_with_fallback to check if the syscall is available.
pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
unsafe {
util_syscall_linux::pre_write_range(buf.as_mut_ptr(), buf.len());
let res = unsafe {
libc::syscall(
libc::SYS_getrandom,
buf.as_mut_ptr().cast::<core::ffi::c_void>(),
buf.len(),
0,
) as libc::ssize_t
}
)
} as libc::ssize_t;
if let Ok(written) = usize::try_from(res) {
// XXX: LLVM has support to do this automatically if/when libc is
// compiled with it, but glibc that ships in typical Linux distros
// doesn't. Assume Android's Bionic is similar. `-Zsanitizer=memory`
// is not compatible with `+crt-static` according to rustc.
unsafe {
util_syscall_linux::post_write_range(buf.as_mut_ptr(), written);
}
};
res
}
104 changes: 104 additions & 0 deletions src/util_syscall_linux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Support for raw system calls on Linux.
//
// # Sanitizers
//
// Currently only Memory Sanitizer is actively supported.
//
// TODO: Support address sanitizer, in particular in `pre_write_range`.
//
// ## Memory Sanitizer
//
// See https://github.com/llvm/llvm-project/commit/ac9ee01fcbfac745aaedca0393a8e1c8a33acd8d:
// LLVM uses:
// ```c
// COMMON_INTERCEPTOR_ENTER(ctx, getrandom, buf, buflen, flags);
// SSIZE_T n = REAL(getrandom)(buf, buflen, flags);
// if (n > 0) {
// COMMON_INTERCEPTOR_WRITE_RANGE(ctx, buf, n);
// }
// ```
// and:
// ```c
// #define PRE_SYSCALL(name) \
// SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_syscall_pre_impl_##name
// #define PRE_WRITE(p, s) COMMON_SYSCALL_PRE_WRITE_RANGE(p, s)
// #define POST_WRITE(p, s) COMMON_SYSCALL_POST_WRITE_RANGE(p, s)
// PRE_SYSCALL(getrandom)(void *buf, uptr count, long flags) {
// if (buf) {
// PRE_WRITE(buf, count);
// }
// }
//
// POST_SYSCALL(getrandom)(long res, void *buf, uptr count, long flags) {
// if (res > 0 && buf) {
// POST_WRITE(buf, res);
// }
// }
// ```

use core::mem::MaybeUninit;

// MSAN defines:
//
// ```c
// #define COMMON_INTERCEPTOR_ENTER(ctx, func, ...) \
// if (msan_init_is_running) \
// return REAL(func)(__VA_ARGS__); \
// ENSURE_MSAN_INITED(); \
// MSanInterceptorContext msan_ctx = {IsInInterceptorScope()}; \
// ctx = (void *)&msan_ctx; \
// (void)ctx; \
// InterceptorScope interceptor_scope; \
// __msan_unpoison(__errno_location(), sizeof(int));
// ```
//
// * We assume that memory sanitizer will not use the this crate during the
// initialization of msan, so we don't have to worry about
// `msan_init_is_running`.
// * We assume that rustc/LLVM initializes MSAN before executing any Rust code,
// so we don't need to call `ENSURE_MSAN_INITED`.
// * Notice that `COMMON_INTERCEPTOR_WRITE_RANGE` doesn't use `ctx`, which
// means it is oblivious to `IsInInterceptorScope()`, so we don't have to
// call it. More generally, we don't have to worry about interceptor scopes
// because we are not an interceptor.
// * We don't read from `__errno_location()` so we don't need to unpoison it.
//
// Consequently, MSAN's `COMMON_INTERCEPTOR_ENTER` is a no-op.
//
// MSAN defines:
// ```c
// #define COMMON_SYSCALL_PRE_WRITE_RANGE(p, s) \
// do { \
// } while (false)
// ```
// So MSAN's PRE_SYSCALL hook is also a no-op.
//
// Consequently, we have nothing to do before invoking the syscall unless/until
// we support other sanitizers like ASAN.
#[allow(unused_variables)]
pub fn pre_write_range(_ptr: *mut MaybeUninit<u8>, _size: usize) {}

// MSNA defines:
// ```c
// #define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \
// __msan_unpoison(ptr, size)
// ```
// and:
// ```c
// #define COMMON_SYSCALL_POST_WRITE_RANGE(p, s) __msan_unpoison(p, s)
// ```
#[allow(unused_variables)]
pub unsafe fn post_write_range(ptr: *mut MaybeUninit<u8>, size: usize) {
#[cfg(feature = "unstable-sanitize")]
{
#[cfg(sanitize = "memory")]
{
use core::ffi::c_void;
extern "C" {
// void __msan_unpoison(const volatile void *a, size_t size);
fn __msan_unpoison(a: *mut c_void, size: usize);
}
__msan_unpoison(ptr.cast::<c_void>(), size)
}
}
}
19 changes: 19 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,25 @@ fn test_huge() {
fn test_huge_uninit() {
let mut huge = [MaybeUninit::uninit(); 100_000];
getrandom_uninit_impl(&mut huge).unwrap();
check_initialized(&huge);
}

#[allow(unused_variables)]
fn check_initialized(buf: &[MaybeUninit<u8>]) {
#[cfg(feature = "unstable-sanitize")]
{
#[cfg(sanitize = "memory")]
{
use core::ffi::c_void;
extern "C" {
// void __msan_check_mem_is_initialized(const volatile void *x, size_t size);
fn __msan_check_mem_is_initialized(x: *const c_void, size: usize);
}
unsafe {
__msan_check_mem_is_initialized(buf.as_ptr().cast::<c_void>(), buf.len());
}
}
}
}

// On WASM, the thread API always fails/panics
Expand Down
1 change: 1 addition & 0 deletions tests/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
feature = "custom",
not(feature = "js")
))]
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]

use wasm_bindgen_test::wasm_bindgen_test as test;

Expand Down
1 change: 1 addition & 0 deletions tests/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
feature = "custom",
not(feature = "js")
)))]
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]

// Use the normal getrandom implementation on this architecture.
use getrandom::{getrandom as getrandom_impl, getrandom_uninit as getrandom_uninit_impl};
Expand Down
1 change: 1 addition & 0 deletions tests/rdrand.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// We only test the RDRAND-based RNG source on supported architectures.
#![cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#![cfg_attr(feature = "unstable-sanitize", feature(cfg_sanitize))]

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

0 comments on commit e53f6e9

Please sign in to comment.