diff --git a/src/backends.rs b/src/backends.rs index 991d413b..b3ba8bf8 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -3,7 +3,7 @@ //! This module should provide `fill_inner` with the signature //! `fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error>`. //! The function MUST fully initialize `dest` when `Ok(())` is returned; -//! the function may need to use `sanitizer::unpoison` as well. +//! the function may need to use `unpoison` as well. //! The function MUST NOT ever write uninitialized bytes into `dest`, //! regardless of what value it returns. @@ -13,11 +13,9 @@ cfg_if! { pub use custom::*; } else if #[cfg(getrandom_backend = "linux_getrandom")] { mod getrandom; - mod sanitizer; pub use getrandom::*; } else if #[cfg(getrandom_backend = "linux_raw")] { mod linux_raw; - mod sanitizer; pub use linux_raw::*; } else if #[cfg(getrandom_backend = "rdrand")] { mod rdrand; @@ -49,7 +47,6 @@ cfg_if! { pub use unsupported::*; } else if #[cfg(all(target_os = "linux", target_env = ""))] { mod linux_raw; - mod sanitizer; pub use linux_raw::*; } else if #[cfg(target_os = "espidf")] { mod esp_idf; @@ -117,7 +114,6 @@ cfg_if! { ))] { mod use_file; mod linux_android_with_fallback; - mod sanitizer; pub use linux_android_with_fallback::*; } else if #[cfg(any( target_os = "android", @@ -132,8 +128,6 @@ cfg_if! { all(target_os = "horizon", target_arch = "arm"), ))] { mod getrandom; - #[cfg(any(target_os = "android", target_os = "linux"))] - mod sanitizer; pub use getrandom::*; } else if #[cfg(target_os = "solaris")] { mod solaris; diff --git a/src/backends/getentropy.rs b/src/backends/getentropy.rs index ed181f01..b77b08ba 100644 --- a/src/backends/getentropy.rs +++ b/src/backends/getentropy.rs @@ -12,15 +12,14 @@ use core::{ffi::c_void, mem::MaybeUninit}; pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; +crate::impl_utils!(get_errno, last_os_error); #[inline] pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { for chunk in dest.chunks_mut(256) { let ret = unsafe { libc::getentropy(chunk.as_mut_ptr().cast::(), chunk.len()) }; if ret != 0 { - return Err(util_libc::last_os_error()); + return Err(last_os_error()); } } Ok(()) diff --git a/src/backends/getrandom.rs b/src/backends/getrandom.rs index af97eab2..455ff022 100644 --- a/src/backends/getrandom.rs +++ b/src/backends/getrandom.rs @@ -20,17 +20,18 @@ use core::mem::MaybeUninit; pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; +crate::impl_utils!(get_errno, last_os_error, sys_fill_exact); #[inline] pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - util_libc::sys_fill_exact(dest, |buf| { + sys_fill_exact(dest, |buf| { let ret = unsafe { libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) }; #[cfg(any(target_os = "android", target_os = "linux"))] unsafe { - super::sanitizer::unpoison_linux_getrandom_result(buf, ret); + crate::impl_utils!(unpoison_linux_getrandom_result); + + unpoison_linux_getrandom_result(buf, ret); } ret diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs index d4ae6f24..9c10afc5 100644 --- a/src/backends/linux_android_with_fallback.rs +++ b/src/backends/linux_android_with_fallback.rs @@ -1,5 +1,5 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback -use super::{sanitizer, use_file}; +use super::use_file::{self, last_os_error, sys_fill_exact}; use crate::Error; use core::{ ffi::c_void, @@ -7,7 +7,6 @@ use core::{ ptr::NonNull, sync::atomic::{AtomicPtr, Ordering}, }; -use use_file::util_libc; pub use crate::util::{inner_u32, inner_u64}; @@ -19,6 +18,8 @@ const NOT_AVAILABLE: NonNull = unsafe { NonNull::new_unchecked(usize::MA static GETRANDOM_FN: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); +crate::impl_utils!(unpoison_linux_getrandom_result); + #[cold] #[inline(never)] fn init() -> NonNull { @@ -44,7 +45,7 @@ fn init() -> NonNull { if cfg!(getrandom_test_linux_fallback) { NOT_AVAILABLE } else if res.is_negative() { - match util_libc::last_os_error().raw_os_error() { + match last_os_error().raw_os_error() { Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support // The fallback on EPERM is intentionally not done on Android since this workaround // seems to be needed only for specific Linux-based products that aren't based @@ -94,9 +95,9 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } else { // note: `transmute` is currently the only way to convert a pointer into a function reference let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; - util_libc::sys_fill_exact(dest, |buf| unsafe { + sys_fill_exact(dest, |buf| unsafe { let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0); - sanitizer::unpoison_linux_getrandom_result(buf, ret); + unpoison_linux_getrandom_result(buf, ret); ret }) } diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index aec160f3..830782cc 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -1,5 +1,4 @@ //! Implementation for Linux / Android using `asm!`-based syscalls. -use super::sanitizer; pub use crate::util::{inner_u32, inner_u64}; use crate::{Error, MaybeUninit}; @@ -140,6 +139,8 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { r0 } +crate::impl_utils!(unpoison_linux_getrandom_result); + #[inline] pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { // Value of this error code is stable across all target arches. @@ -147,7 +148,7 @@ pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { loop { let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) }; - unsafe { sanitizer::unpoison_linux_getrandom_result(dest, ret) }; + unsafe { unpoison_linux_getrandom_result(dest, ret) }; match usize::try_from(ret) { Ok(0) => return Err(Error::UNEXPECTED), Ok(len) => { diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs index f228a8b1..aabfc1fe 100644 --- a/src/backends/netbsd.rs +++ b/src/backends/netbsd.rs @@ -14,8 +14,7 @@ use core::{ pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; +crate::impl_utils!(get_errno, last_os_error, sys_fill_exact); unsafe extern "C" fn polyfill_using_kern_arand( buf: *mut c_void, @@ -72,7 +71,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { fptr = init(); } let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) }; - util_libc::sys_fill_exact(dest, |buf| unsafe { + sys_fill_exact(dest, |buf| unsafe { fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) }) } diff --git a/src/backends/sanitizer.rs b/src/backends/sanitizer.rs deleted file mode 100644 index 5dd19349..00000000 --- a/src/backends/sanitizer.rs +++ /dev/null @@ -1,55 +0,0 @@ -use core::mem::MaybeUninit; - -/// Unpoisons `buf` if MSAN support is enabled. -/// -/// Most backends do not need to unpoison their output. Rust language- and -/// library- provided functionality unpoisons automatically. Similarly, libc -/// either natively supports MSAN and/or MSAN hooks libc-provided functions -/// to unpoison outputs on success. Only when all of these things are -/// bypassed do we need to do it ourselves. -/// -/// The call to unpoison should be done as close to the write as possible. -/// For example, if the backend partially fills the output buffer in chunks, -/// each chunk should be unpoisoned individually. This way, the correctness of -/// the chunking logic can be validated (in part) using MSAN. -pub unsafe fn unpoison(buf: &mut [MaybeUninit]) { - cfg_if! { - if #[cfg(getrandom_msan)] { - unsafe extern "C" { - fn __msan_unpoison(a: *mut core::ffi::c_void, size: usize); - } - let a = buf.as_mut_ptr().cast(); - let size = buf.len(); - unsafe { - __msan_unpoison(a, size); - } - } else { - let _ = buf; - } - } -} - -/// Interprets the result of the `getrandom` syscall of Linux, unpoisoning any -/// written part of `buf`. -/// -/// `buf` must be the output buffer that was originally passed to the `getrandom` -/// syscall. -/// -/// `ret` must be the result returned by `getrandom`. If `ret` is negative or -/// larger than the length of `buf` then nothing is done. -/// -/// Memory Sanitizer only intercepts `getrandom` on this condition (from its -/// source code): -/// ```c -/// #define SANITIZER_INTERCEPT_GETRANDOM \ -/// ((SI_LINUX && __GLIBC_PREREQ(2, 25)) || SI_FREEBSD || SI_SOLARIS) -/// ``` -/// So, effectively, we have to assume that it is never intercepted on Linux. -#[cfg(any(target_os = "android", target_os = "linux"))] -pub unsafe fn unpoison_linux_getrandom_result(buf: &mut [MaybeUninit], ret: isize) { - if let Ok(bytes_written) = usize::try_from(ret) { - if let Some(written) = buf.get_mut(..bytes_written) { - unsafe { unpoison(written) } - } - } -} diff --git a/src/backends/solaris.rs b/src/backends/solaris.rs index c27f91a5..f8e442fb 100644 --- a/src/backends/solaris.rs +++ b/src/backends/solaris.rs @@ -17,8 +17,7 @@ use core::{ffi::c_void, mem::MaybeUninit}; pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; +crate::impl_utils!(get_errno, last_os_error); const MAX_BYTES: usize = 1024; @@ -33,7 +32,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // Good. Keep going. Ok(ret) if ret == chunk.len() => {} // The syscall failed. - Ok(0) => return Err(util_libc::last_os_error()), + Ok(0) => return Err(last_os_error()), // All other cases should be impossible. _ => return Err(Error::UNEXPECTED), } diff --git a/src/backends/use_file.rs b/src/backends/use_file.rs index 796dbbc5..f298377a 100644 --- a/src/backends/use_file.rs +++ b/src/backends/use_file.rs @@ -9,9 +9,6 @@ use core::{ #[cfg(not(any(target_os = "android", target_os = "linux")))] pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -pub(super) mod util_libc; - /// For all platforms, we use `/dev/urandom` rather than `/dev/random`. /// For more information see the linked man pages in lib.rs. /// - On Linux, "/dev/urandom is preferred and sufficient in all use cases". @@ -40,13 +37,15 @@ const FD_ONGOING_INIT: libc::c_int = -2; // `Ordering::Acquire` to synchronize with it. static FD: AtomicI32 = AtomicI32::new(FD_UNINIT); +crate::impl_utils!(get_errno, last_os_error, sys_fill_exact); + #[inline] pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let mut fd = FD.load(Ordering::Acquire); if fd == FD_UNINIT || fd == FD_ONGOING_INIT { fd = open_or_wait()?; } - util_libc::sys_fill_exact(dest, |buf| unsafe { + sys_fill_exact(dest, |buf| unsafe { libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) }) } @@ -58,7 +57,7 @@ fn open_readonly(path: &CStr) -> Result { if fd >= 0 { return Ok(fd); } - let err = util_libc::last_os_error(); + let err = last_os_error(); // We should try again if open() was interrupted. if err.raw_os_error() != Some(libc::EINTR) { return Err(err); @@ -136,7 +135,7 @@ mod sync { #[cfg(any(target_os = "android", target_os = "linux"))] mod sync { - use super::{Error, FD, FD_ONGOING_INIT, open_readonly, util_libc::last_os_error}; + use super::{Error, FD, FD_ONGOING_INIT, last_os_error, open_readonly}; /// Wait for atomic `FD` to change value from `FD_ONGOING_INIT` to something else. /// diff --git a/src/backends/vxworks.rs b/src/backends/vxworks.rs index 5f5e6773..412d9666 100644 --- a/src/backends/vxworks.rs +++ b/src/backends/vxworks.rs @@ -6,13 +6,13 @@ use core::{ sync::atomic::{AtomicBool, Ordering::Relaxed}, }; -#[path = "../util_libc.rs"] -mod util_libc; - pub use crate::util::{inner_u32, inner_u64}; static RNG_INIT: AtomicBool = AtomicBool::new(false); +use libc::errnoGet as get_errno; +crate::impl_utils!(last_os_error); + #[cold] fn init() -> Result<(), Error> { let ret = unsafe { libc::randSecure() }; @@ -42,7 +42,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let p: *mut libc::c_uchar = chunk.as_mut_ptr().cast(); let ret = unsafe { libc::randABytes(p, chunk_len) }; if ret != 0 { - return Err(util_libc::last_os_error()); + return Err(last_os_error()); } } Ok(()) diff --git a/src/impl_utils.rs b/src/impl_utils.rs new file mode 100644 index 00000000..85800690 --- /dev/null +++ b/src/impl_utils.rs @@ -0,0 +1,151 @@ +/// Implement utilities used in multiple backends. +// TODO: remove `pub(crate)` after the `linux_android_with_fallback` backend is removed +#[allow(unused_macros, reason = "not used in all backends")] +macro_rules! impl_utils { + (last_os_error) => { + pub(crate) fn last_os_error() -> $crate::Error { + // We assume that on all targets which use the `util_libc` module `c_int` is equal to `i32` + let errno: i32 = unsafe { get_errno() }; + + if errno > 0 { + let code = errno + .checked_neg() + .expect("Positive number can be always negated"); + Error::from_neg_error_code(code) + } else { + Error::ERRNO_NOT_POSITIVE + } + } + }; + + (sys_fill_exact) => { + /// Fill a buffer by repeatedly invoking `sys_fill`. + /// + /// The `sys_fill` function: + /// - should return -1 and set errno on failure + /// - should return the number of bytes written on success + pub(crate) fn sys_fill_exact( + mut buf: &mut [core::mem::MaybeUninit], + sys_fill: impl Fn(&mut [core::mem::MaybeUninit]) -> libc::ssize_t, + ) -> Result<(), Error> { + while !buf.is_empty() { + let res = sys_fill(buf); + match res { + res if res > 0 => { + let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; + buf = buf.get_mut(len..).ok_or(Error::UNEXPECTED)?; + } + -1 => { + let err = last_os_error(); + // We should try again if the call was interrupted. + if err.raw_os_error() != Some(libc::EINTR) { + return Err(err); + } + } + // Negative return codes not equal to -1 should be impossible. + // EOF (ret = 0) should be impossible, as the data we are reading + // should be an infinite stream of random bytes. + _ => return Err(Error::UNEXPECTED), + } + } + Ok(()) + } + }; + + (get_errno) => { + cfg_if! { + if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android", target_os = "cygwin"))] { + use libc::__errno as errno_location; + } else if #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "hurd", target_os = "redox", target_os = "dragonfly"))] { + use libc::__errno_location as errno_location; + } else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] { + use libc::___errno as errno_location; + } else if #[cfg(any(target_os = "macos", target_os = "freebsd"))] { + use libc::__error as errno_location; + } else if #[cfg(target_os = "haiku")] { + use libc::_errnop as errno_location; + } else if #[cfg(target_os = "nto")] { + use libc::__get_errno_ptr as errno_location; + } else if #[cfg(any(all(target_os = "horizon", target_arch = "arm"), target_os = "vita"))] { + unsafe extern "C" { + // Not provided by libc: https://github.com/rust-lang/libc/issues/1995 + fn __errno() -> *mut libc::c_int; + } + use __errno as errno_location; + } else if #[cfg(target_os = "aix")] { + use libc::_Errno as errno_location; + } else { + compile_error!("`errno_location` is not provided for the target!"); + } + } + + unsafe fn get_errno() -> libc::c_int { + unsafe { core::ptr::read(errno_location()) } + } + }; + + (unpoison) => { + /// Unpoisons `buf` if MSAN support is enabled. + /// + /// Most backends do not need to unpoison their output. Rust language- and + /// library- provided functionality unpoisons automatically. Similarly, libc + /// either natively supports MSAN and/or MSAN hooks libc-provided functions + /// to unpoison outputs on success. Only when all of these things are + /// bypassed do we need to do it ourselves. + /// + /// The call to unpoison should be done as close to the write as possible. + /// For example, if the backend partially fills the output buffer in chunks, + /// each chunk should be unpoisoned individually. This way, the correctness of + /// the chunking logic can be validated (in part) using MSAN. + unsafe fn unpoison(buf: &mut [core::mem::MaybeUninit]) { + cfg_if! { + if #[cfg(getrandom_msan)] { + unsafe extern "C" { + fn __msan_unpoison(a: *mut core::ffi::c_void, size: usize); + } + let a = buf.as_mut_ptr().cast(); + let size = buf.len(); + unsafe { + __msan_unpoison(a, size); + } + } else { + let _ = buf; + } + } + } + }; + + (unpoison_linux_getrandom_result) => { + /// Interprets the result of the `getrandom` syscall of Linux, unpoisoning any + /// written part of `buf`. + /// + /// `buf` must be the output buffer that was originally passed to the `getrandom` + /// syscall. + /// + /// `ret` must be the result returned by `getrandom`. If `ret` is negative or + /// larger than the length of `buf` then nothing is done. + /// + /// Memory Sanitizer only intercepts `getrandom` on this condition (from its + /// source code): + /// ```c + /// #define SANITIZER_INTERCEPT_GETRANDOM \ + /// ((SI_LINUX && __GLIBC_PREREQ(2, 25)) || SI_FREEBSD || SI_SOLARIS) + /// ``` + /// So, effectively, we have to assume that it is never intercepted on Linux. + unsafe fn unpoison_linux_getrandom_result(buf: &mut [core::mem::MaybeUninit], ret: isize) { + $crate::impl_utils!(unpoison); + + if let Ok(bytes_written) = usize::try_from(ret) { + if let Some(written) = buf.get_mut(..bytes_written) { + unsafe { unpoison(written) } + } + } + } + }; + + ($($util:ident),* $(,)?) => { + $($crate::impl_utils!($util);)* + }; +} + +pub(crate) use impl_utils; diff --git a/src/lib.rs b/src/lib.rs index ed69ad23..cc3ee074 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,10 @@ extern crate cfg_if; use core::mem::MaybeUninit; +mod impl_utils; +#[allow(unused_imports, reason = "not used in all backends")] +pub(crate) use impl_utils::impl_utils; + mod backends; mod error; mod util; @@ -108,11 +112,6 @@ pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { backends::fill_inner(dest)?; } - #[cfg(getrandom_msan)] - unsafe extern "C" { - fn __msan_unpoison(a: *mut core::ffi::c_void, size: usize); - } - // SAFETY: `dest` has been fully initialized by `imp::fill_inner` // since it returned `Ok`. Ok(unsafe { util::slice_assume_init_mut(dest) }) diff --git a/src/util_libc.rs b/src/util_libc.rs deleted file mode 100644 index 69432891..00000000 --- a/src/util_libc.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::Error; -use core::mem::MaybeUninit; - -cfg_if! { - if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android", target_os = "cygwin"))] { - use libc::__errno as errno_location; - } else if #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "hurd", target_os = "redox", target_os = "dragonfly"))] { - use libc::__errno_location as errno_location; - } else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] { - use libc::___errno as errno_location; - } else if #[cfg(any(target_os = "macos", target_os = "freebsd"))] { - use libc::__error as errno_location; - } else if #[cfg(target_os = "haiku")] { - use libc::_errnop as errno_location; - } else if #[cfg(target_os = "nto")] { - use libc::__get_errno_ptr as errno_location; - } else if #[cfg(any(all(target_os = "horizon", target_arch = "arm"), target_os = "vita"))] { - unsafe extern "C" { - // Not provided by libc: https://github.com/rust-lang/libc/issues/1995 - fn __errno() -> *mut libc::c_int; - } - use __errno as errno_location; - } else if #[cfg(target_os = "aix")] { - use libc::_Errno as errno_location; - } -} - -cfg_if! { - if #[cfg(target_os = "vxworks")] { - use libc::errnoGet as get_errno; - } else { - unsafe fn get_errno() -> libc::c_int { unsafe { *errno_location() }} - } -} - -pub(crate) fn last_os_error() -> Error { - // We assume that on all targets which use the `util_libc` module `c_int` is equal to `i32` - let errno: i32 = unsafe { get_errno() }; - - if errno > 0 { - let code = errno - .checked_neg() - .expect("Positive number can be always negated"); - Error::from_neg_error_code(code) - } else { - Error::ERRNO_NOT_POSITIVE - } -} - -/// Fill a buffer by repeatedly invoking `sys_fill`. -/// -/// The `sys_fill` function: -/// - should return -1 and set errno on failure -/// - should return the number of bytes written on success -#[allow(dead_code)] -pub(crate) fn sys_fill_exact( - mut buf: &mut [MaybeUninit], - sys_fill: impl Fn(&mut [MaybeUninit]) -> libc::ssize_t, -) -> Result<(), Error> { - while !buf.is_empty() { - let res = sys_fill(buf); - match res { - res if res > 0 => { - let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; - buf = buf.get_mut(len..).ok_or(Error::UNEXPECTED)?; - } - -1 => { - let err = last_os_error(); - // We should try again if the call was interrupted. - if err.raw_os_error() != Some(libc::EINTR) { - return Err(err); - } - } - // Negative return codes not equal to -1 should be impossible. - // EOF (ret = 0) should be impossible, as the data we are reading - // should be an infinite stream of random bytes. - _ => return Err(Error::UNEXPECTED), - } - } - Ok(()) -}