diff --git a/src/backends.rs b/src/backends.rs index 991d413b..e572b8b5 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -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..463c463b 100644 --- a/src/backends/getentropy.rs +++ b/src/backends/getentropy.rs @@ -12,15 +12,16 @@ use core::{ffi::c_void, mem::MaybeUninit}; pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; +#[path = "../utils/get_errno.rs"] +mod utils; #[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()); + let errno = utils::get_errno(); + return Err(Error::from_errno(errno)); } } Ok(()) diff --git a/src/backends/getrandom.rs b/src/backends/getrandom.rs index af97eab2..927dc85c 100644 --- a/src/backends/getrandom.rs +++ b/src/backends/getrandom.rs @@ -20,19 +20,12 @@ use core::mem::MaybeUninit; pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; +#[path = "../utils/sys_fill_exact.rs"] +mod utils; #[inline] pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - util_libc::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); - } - - ret + utils::sys_fill_exact(dest, |buf| unsafe { + libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) }) } diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs index d4ae6f24..48ce6288 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; use crate::Error; use core::{ ffi::c_void, @@ -7,7 +7,7 @@ use core::{ ptr::NonNull, sync::atomic::{AtomicPtr, Ordering}, }; -use use_file::util_libc; +use use_file::utils; pub use crate::util::{inner_u32, inner_u64}; @@ -44,13 +44,13 @@ 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() { - Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support + match utils::get_errno() { + 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 // on Android. See https://github.com/rust-random/getrandom/issues/229. #[cfg(target_os = "linux")] - Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp + libc::EPERM => NOT_AVAILABLE, // Blocked by seccomp _ => fptr, } } else { @@ -94,10 +94,8 @@ 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 { - let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0); - sanitizer::unpoison_linux_getrandom_result(buf, ret); - ret + utils::sys_fill_exact(dest, |buf| unsafe { + getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0) }) } } diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index aec160f3..31cddfc8 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -1,11 +1,13 @@ //! Implementation for Linux / Android using `asm!`-based syscalls. -use super::sanitizer; pub use crate::util::{inner_u32, inner_u64}; use crate::{Error, MaybeUninit}; #[cfg(not(any(target_os = "android", target_os = "linux")))] compile_error!("`linux_raw` backend can be enabled only for Linux/Android targets!"); +#[path = "../utils/sanitizer.rs"] +mod utils; + #[allow(non_upper_case_globals)] unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { let r0; @@ -147,11 +149,12 @@ 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) }; match usize::try_from(ret) { Ok(0) => return Err(Error::UNEXPECTED), Ok(len) => { - dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; + let (l, r) = dest.split_at_mut_checked(len).ok_or(Error::UNEXPECTED)?; + unsafe { utils::unpoison(l) }; + dest = r; if dest.is_empty() { return Ok(()); } diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs index f228a8b1..0da0d492 100644 --- a/src/backends/netbsd.rs +++ b/src/backends/netbsd.rs @@ -14,8 +14,8 @@ use core::{ pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; +#[path = "../utils/sys_fill_exact.rs"] +mod utils; unsafe extern "C" fn polyfill_using_kern_arand( buf: *mut c_void, @@ -72,7 +72,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 { + utils::sys_fill_exact(dest, |buf| unsafe { fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) }) } diff --git a/src/backends/rdrand.rs b/src/backends/rdrand.rs index ece9a434..cf617b36 100644 --- a/src/backends/rdrand.rs +++ b/src/backends/rdrand.rs @@ -2,7 +2,7 @@ use crate::{Error, util::slice_as_uninit}; use core::mem::{MaybeUninit, size_of}; -#[path = "../lazy.rs"] +#[path = "../utils/lazy.rs"] mod lazy; #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] diff --git a/src/backends/rndr.rs b/src/backends/rndr.rs index 0b0636ae..1a24b49f 100644 --- a/src/backends/rndr.rs +++ b/src/backends/rndr.rs @@ -69,7 +69,7 @@ fn is_rndr_available() -> bool { #[cfg(not(target_feature = "rand"))] fn is_rndr_available() -> bool { - #[path = "../lazy.rs"] + #[path = "../utils/lazy.rs"] mod lazy; static RNDR_GOOD: lazy::LazyBool = lazy::LazyBool::new(); diff --git a/src/backends/solaris.rs b/src/backends/solaris.rs index c27f91a5..f3e22de0 100644 --- a/src/backends/solaris.rs +++ b/src/backends/solaris.rs @@ -14,12 +14,10 @@ //! which also explains why this crate should not use getentropy(2). use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +use libc::___errno as errno_location; pub use crate::util::{inner_u32, inner_u64}; -#[path = "../util_libc.rs"] -mod util_libc; - const MAX_BYTES: usize = 1024; #[inline] @@ -33,7 +31,10 @@ 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) => { + let errno = unsafe { core::ptr::read(errno_location()) }; + return Err(Error::from_errno(errno)); + } // 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..a1d372a0 100644 --- a/src/backends/use_file.rs +++ b/src/backends/use_file.rs @@ -9,8 +9,8 @@ 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; +#[path = "../utils/sys_fill_exact.rs"] +pub(super) mod utils; /// For all platforms, we use `/dev/urandom` rather than `/dev/random`. /// For more information see the linked man pages in lib.rs. @@ -46,7 +46,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { if fd == FD_UNINIT || fd == FD_ONGOING_INIT { fd = open_or_wait()?; } - util_libc::sys_fill_exact(dest, |buf| unsafe { + utils::sys_fill_exact(dest, |buf| unsafe { libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) }) } @@ -58,10 +58,10 @@ fn open_readonly(path: &CStr) -> Result { if fd >= 0 { return Ok(fd); } - let err = util_libc::last_os_error(); + let errno = utils::get_errno(); // We should try again if open() was interrupted. - if err.raw_os_error() != Some(libc::EINTR) { - return Err(err); + if errno != libc::EINTR { + return Err(Error::from_errno(errno)); } } } @@ -136,7 +136,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, open_readonly, utils}; /// Wait for atomic `FD` to change value from `FD_ONGOING_INIT` to something else. /// @@ -152,7 +152,7 @@ mod sync { debug_assert!({ match ret { 0 => true, - -1 => last_os_error().raw_os_error() == Some(libc::EAGAIN), + -1 => utils::get_errno() == libc::EAGAIN, _ => false, } }); @@ -209,12 +209,12 @@ mod sync { debug_assert_eq!(res, 1); break Ok(()); } - let err = last_os_error(); + let errno = utils::get_errno(); // Assuming that `poll` is called correctly, // on Linux it can return only EINTR and ENOMEM errors. - match err.raw_os_error() { - Some(libc::EINTR) => continue, - _ => break Err(err), + match errno { + libc::EINTR => continue, + _ => break Err(Error::from_errno(errno)), } }; unsafe { libc::close(fd) }; diff --git a/src/backends/vxworks.rs b/src/backends/vxworks.rs index 5f5e6773..10d2e81b 100644 --- a/src/backends/vxworks.rs +++ b/src/backends/vxworks.rs @@ -6,9 +6,6 @@ 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); @@ -42,7 +39,8 @@ 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()); + let errno = unsafe { libc::errnoGet() }; + return Err(Error::from_errno(errno)); } } Ok(()) diff --git a/src/error.rs b/src/error.rs index 069d8c10..7708959c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,7 +57,25 @@ impl Error { /// Custom errors can be in the range of 2^17..(2^17 + 2^16) const CUSTOM_START: RawOsError = 1 << 17; - /// Creates a new instance of an `Error` from a negative error code. + /// Creates a new `Error` instance from a positive error code. + /// + /// Returns [`Error::ERRNO_NOT_POSITIVE`] for zero and negative error codes. + #[cfg(not(target_os = "uefi"))] + #[allow(dead_code)] + pub(super) fn from_errno(errno: i32) -> Self { + 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 + } + } + + /// Creates a new `Error` instance from a negative error code. + /// + /// Returns [`Error::UNEXPECTED`] for zero and positive error codes. #[cfg(not(target_os = "uefi"))] #[allow(dead_code)] pub(super) fn from_neg_error_code(code: RawOsError) -> Self { 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(()) -} diff --git a/src/utils/get_errno.rs b/src/utils/get_errno.rs new file mode 100644 index 00000000..1895fc05 --- /dev/null +++ b/src/utils/get_errno.rs @@ -0,0 +1,29 @@ +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(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"); + } +} + +pub(crate) fn get_errno() -> libc::c_int { + unsafe { core::ptr::read(errno_location()) } +} diff --git a/src/lazy.rs b/src/utils/lazy.rs similarity index 100% rename from src/lazy.rs rename to src/utils/lazy.rs diff --git a/src/backends/sanitizer.rs b/src/utils/sanitizer.rs similarity index 50% rename from src/backends/sanitizer.rs rename to src/utils/sanitizer.rs index 5dd19349..205dc94a 100644 --- a/src/backends/sanitizer.rs +++ b/src/utils/sanitizer.rs @@ -20,36 +20,9 @@ pub unsafe fn unpoison(buf: &mut [MaybeUninit]) { } let a = buf.as_mut_ptr().cast(); let size = buf.len(); - unsafe { - __msan_unpoison(a, size); - } + 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/utils/sys_fill_exact.rs b/src/utils/sys_fill_exact.rs new file mode 100644 index 00000000..c57df04f --- /dev/null +++ b/src/utils/sys_fill_exact.rs @@ -0,0 +1,36 @@ +use crate::Error; +use core::mem::MaybeUninit; + +mod get_errno; +mod sanitizer; + +pub(crate) use get_errno::get_errno; + +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)?; + let (l, r) = buf.split_at_mut_checked(len).ok_or(Error::UNEXPECTED)?; + unsafe { sanitizer::unpoison(l) }; + buf = r; + } + -1 => { + let errno = get_errno(); + // We should try again if the call was interrupted. + if errno != libc::EINTR { + return Err(Error::from_errno(errno)); + } + } + // 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(()) +}