diff --git a/.travis.yml b/.travis.yml index 49353822..2ec4afaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ matrix: - gunzip cargo-web.gz - chmod +x cargo-web # Get wasmtime - - export VERSION=v0.3.0 # Pin version for stability + - export VERSION=v0.8.0 # Pin version for stability - wget -O wasmtime.tar.xz https://github.com/CraneStation/wasmtime/releases/download/$VERSION/wasmtime-$VERSION-x86_64-linux.tar.xz - tar -xf wasmtime.tar.xz --strip-components=1 # Get wasm-bindgen-test-runner which matches our wasm-bindgen version diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f43b26f..29b447c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.14] - 2020-01-07 +### Changed +- Remove use of spin-locks in the `use_file` module. [#125] +- Update `wasi` to v0.9. [#126] +- Do not read errno value on DragonFlyBSD to fix compilation failure. [#129] + +[#125]: https://github.com/rust-random/getrandom/pull/125 +[#126]: https://github.com/rust-random/getrandom/pull/126 +[#129]: https://github.com/rust-random/getrandom/pull/129 + +## [0.1.13] - 2019-08-25 +### Added +- VxWorks targets support. [#86] + +### Changed +- If zero-length slice is passed to the `getrandom` function, always return +`Ok(())` immediately without doing any calls to the underlying operating +system. [#104] +- Use the `kern.arandom` sysctl on NetBSD. [#115] + +### Fixed +- Bump `cfg-if` minimum version from 0.1.0 to 0.1.2. [#112] +- Typos and bad doc links. [#117] + +[#86]: https://github.com/rust-random/getrandom/pull/86 +[#104]: https://github.com/rust-random/getrandom/pull/104 +[#112]: https://github.com/rust-random/getrandom/pull/112 +[#115]: https://github.com/rust-random/getrandom/pull/115 +[#117]: https://github.com/rust-random/getrandom/pull/117 + ## [0.1.12] - 2019-08-18 ### Changed - Update wasi dependency from v0.5 to v0.7. [#100] diff --git a/Cargo.toml b/Cargo.toml index 7aa8780f..3999d311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" libc = { version = "0.2.64", default-features = false } [target.'cfg(target_os = "wasi")'.dependencies] -wasi = "0.7" +wasi = "0.9" [target.wasm32-unknown-unknown.dependencies] wasm-bindgen = { version = "0.2.29", optional = true } diff --git a/src/error.rs b/src/error.rs index 2ffc252f..b6662006 100644 --- a/src/error.rs +++ b/src/error.rs @@ -87,7 +87,7 @@ impl Error { cfg_if! { if #[cfg(unix)] { - fn os_err_desc(errno: i32, buf: &mut [u8]) -> Option<&str> { + fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> { let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char; if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 { return None; @@ -99,12 +99,11 @@ cfg_if! { core::str::from_utf8(&buf[..idx]).ok() } } else if #[cfg(target_os = "wasi")] { - fn os_err_desc(errno: i32, _buf: &mut [u8]) -> Option<&str> { - core::num::NonZeroU16::new(errno as u16) - .and_then(wasi::wasi_unstable::error_str) + fn os_err(errno: i32, _buf: &mut [u8]) -> Option { + wasi::Error::from_raw_error(errno as _) } } else { - fn os_err_desc(_errno: i32, _buf: &mut [u8]) -> Option<&str> { + fn os_err(_errno: i32, _buf: &mut [u8]) -> Option<&str> { None } } @@ -116,8 +115,8 @@ impl fmt::Debug for Error { if let Some(errno) = self.raw_os_error() { dbg.field("os_error", &errno); let mut buf = [0u8; 128]; - if let Some(desc) = os_err_desc(errno, &mut buf) { - dbg.field("description", &desc); + if let Some(err) = os_err(errno, &mut buf) { + dbg.field("description", &err); } } else if let Some(desc) = internal_desc(*self) { dbg.field("internal_code", &self.0.get()); @@ -133,8 +132,8 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(errno) = self.raw_os_error() { let mut buf = [0u8; 128]; - match os_err_desc(errno, &mut buf) { - Some(desc) => f.write_str(desc), + match os_err(errno, &mut buf) { + Some(err) => err.fmt(f), None => write!(f, "OS Error: {}", errno), } } else if let Some(desc) = internal_desc(*self) { diff --git a/src/use_file.rs b/src/use_file.rs index d3adaf2a..6e50955c 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -7,8 +7,11 @@ // except according to those terms. //! Implementations that just need to read from a file -use crate::util_libc::{last_os_error, open_readonly, sys_fill_exact, LazyFd}; +use crate::util::LazyUsize; +use crate::util_libc::{open_readonly, sys_fill_exact}; use crate::Error; +use core::cell::UnsafeCell; +use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; #[cfg(target_os = "redox")] const FILE_PATH: &str = "rand:\0"; @@ -21,10 +24,11 @@ const FILE_PATH: &str = "rand:\0"; target_os = "illumos" ))] const FILE_PATH: &str = "/dev/random\0"; +#[cfg(any(target_os = "android", target_os = "linux"))] +const FILE_PATH: &str = "/dev/urandom\0"; pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - static FD: LazyFd = LazyFd::new(); - let fd = FD.init(init_file).ok_or_else(last_os_error)?; + let fd = get_rng_fd()?; let read = |buf: &mut [u8]| unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) }; if cfg!(target_os = "emscripten") { @@ -38,36 +42,96 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { Ok(()) } -cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { - fn init_file() -> Option { - // Poll /dev/random to make sure it is ok to read from /dev/urandom. - let mut pfd = libc::pollfd { - fd: unsafe { open_readonly("/dev/random\0")? }, - events: libc::POLLIN, - revents: 0, - }; - - let ret = loop { - // A negative timeout means an infinite timeout. - let res = unsafe { libc::poll(&mut pfd, 1, -1) }; - if res == 1 { - break unsafe { open_readonly("/dev/urandom\0") }; - } else if res < 0 { - let e = last_os_error().raw_os_error(); - if e == Some(libc::EINTR) || e == Some(libc::EAGAIN) { - continue; - } - } - // We either hard failed, or poll() returned the wrong pfd. - break None; - }; - unsafe { libc::close(pfd.fd) }; - ret +// Returns the file descriptor for the device file used to retrieve random +// bytes. The file will be opened exactly once. All successful calls will +// return the same file descriptor. This file descriptor is never closed. +fn get_rng_fd() -> Result { + static FD: AtomicUsize = AtomicUsize::new(LazyUsize::UNINIT); + fn get_fd() -> Option { + match FD.load(Relaxed) { + LazyUsize::UNINIT => None, + val => Some(val as libc::c_int), } - } else { - fn init_file() -> Option { - unsafe { open_readonly(FILE_PATH) } + } + + // Use double-checked locking to avoid acquiring the lock if possible. + if let Some(fd) = get_fd() { + return Ok(fd); + } + + // SAFETY: We use the mutex only in this method, and we always unlock it + // before returning, making sure we don't violate the pthread_mutex_t API. + static MUTEX: Mutex = Mutex::new(); + unsafe { MUTEX.lock() }; + let _guard = DropGuard(|| unsafe { MUTEX.unlock() }); + + if let Some(fd) = get_fd() { + return Ok(fd); + } + + // On Linux, /dev/urandom might return insecure values. + #[cfg(any(target_os = "android", target_os = "linux"))] + wait_until_rng_ready()?; + + let fd = unsafe { open_readonly(FILE_PATH)? }; + // The fd always fits in a usize without conflicting with UNINIT. + debug_assert!(fd >= 0 && (fd as usize) < LazyUsize::UNINIT); + FD.store(fd as usize, Relaxed); + + Ok(fd) +} + +// Succeeds once /dev/urandom is safe to read from +#[cfg(any(target_os = "android", target_os = "linux"))] +fn wait_until_rng_ready() -> Result<(), Error> { + // Poll /dev/random to make sure it is ok to read from /dev/urandom. + let fd = unsafe { open_readonly("/dev/random\0")? }; + let mut pfd = libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }; + let _guard = DropGuard(|| unsafe { + libc::close(fd); + }); + + loop { + // A negative timeout means an infinite timeout. + let res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res >= 0 { + assert_eq!(res, 1); // We only used one fd, and cannot timeout. + return Ok(()); } + let err = crate::util_libc::last_os_error(); + match err.raw_os_error() { + Some(libc::EINTR) | Some(libc::EAGAIN) => continue, + _ => return Err(err), + } + } +} + +struct Mutex(UnsafeCell); + +impl Mutex { + const fn new() -> Self { + Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)) + } + unsafe fn lock(&self) { + let r = libc::pthread_mutex_lock(self.0.get()); + debug_assert_eq!(r, 0); + } + unsafe fn unlock(&self) { + let r = libc::pthread_mutex_unlock(self.0.get()); + debug_assert_eq!(r, 0); + } +} + +unsafe impl Sync for Mutex {} + +struct DropGuard(F); + +impl Drop for DropGuard { + fn drop(&mut self) { + self.0() } } diff --git a/src/util.rs b/src/util.rs index c0f4f9b2..06e23c28 100644 --- a/src/util.rs +++ b/src/util.rs @@ -35,8 +35,6 @@ impl LazyUsize { // The initialization is not completed. pub const UNINIT: usize = usize::max_value(); - // The initialization is currently running. - pub const ACTIVE: usize = usize::max_value() - 1; // Runs the init() function at least once, returning the value of some run // of init(). Multiple callers can run their init() functions in parallel. @@ -50,36 +48,6 @@ impl LazyUsize { } val } - - // Synchronously runs the init() function. Only one caller will have their - // init() function running at a time, and exactly one successful call will - // be run. init() returning UNINIT or ACTIVE will be considered a failure, - // and future calls to sync_init will rerun their init() function. - pub fn sync_init(&self, init: impl FnOnce() -> usize, mut wait: impl FnMut()) -> usize { - // Common and fast path with no contention. Don't wast time on CAS. - match self.0.load(Relaxed) { - Self::UNINIT | Self::ACTIVE => {} - val => return val, - } - // Relaxed ordering is fine, as we only have a single atomic variable. - loop { - match self.0.compare_and_swap(Self::UNINIT, Self::ACTIVE, Relaxed) { - Self::UNINIT => { - let val = init(); - self.0.store( - match val { - Self::UNINIT | Self::ACTIVE => Self::UNINIT, - val => val, - }, - Relaxed, - ); - return val; - } - Self::ACTIVE => wait(), - val => return val, - } - } - } } // Identical to LazyUsize except with bool instead of usize. diff --git a/src/util_libc.rs b/src/util_libc.rs index 3c36e01c..89f2f659 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -18,18 +18,27 @@ cfg_if! { 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", target_os = "dragonfly"))] { + } 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; } } +cfg_if! { + if #[cfg(target_os = "vxworks")] { + use libc::errnoGet as get_errno; + } else if #[cfg(target_os = "dragonfly")] { + // Until rust-lang/rust#29594 is stable, we cannot get the errno value + // on DragonFlyBSD. So we just return an out-of-range errno. + unsafe fn get_errno() -> libc::c_int { -1 } + } else { + unsafe fn get_errno() -> libc::c_int { *errno_location() } + } +} + pub fn last_os_error() -> Error { - #[cfg(not(target_os = "vxworks"))] - let errno = unsafe { *errno_location() }; - #[cfg(target_os = "vxworks")] - let errno = unsafe { libc::errnoGet() }; + let errno = unsafe { get_errno() }; if errno > 0 { Error::from(NonZeroU32::new(errno as u32).unwrap()) } else { @@ -89,37 +98,6 @@ impl Weak { } } -pub struct LazyFd(LazyUsize); - -impl LazyFd { - pub const fn new() -> Self { - Self(LazyUsize::new()) - } - - // If init() returns Some(x), x should be nonnegative. - pub fn init(&self, init: impl FnOnce() -> Option) -> Option { - let fd = self.0.sync_init( - || match init() { - // OK as val >= 0 and val <= c_int::MAX < usize::MAX - Some(val) => val as usize, - None => LazyUsize::UNINIT, - }, - || unsafe { - // We are usually waiting on an open(2) syscall to complete, - // which typically takes < 10us if the file is a device. - // However, we might end up waiting much longer if the entropy - // pool isn't initialized, but even in that case, this loop will - // consume a negligible amount of CPU on most platforms. - libc::usleep(10); - }, - ); - match fd { - LazyUsize::UNINIT => None, - val => Some(val as libc::c_int), - } - } -} - cfg_if! { if #[cfg(any(target_os = "linux", target_os = "emscripten"))] { use libc::open64 as open; @@ -129,15 +107,15 @@ cfg_if! { } // SAFETY: path must be null terminated, FD must be manually closed. -pub unsafe fn open_readonly(path: &str) -> Option { +pub unsafe fn open_readonly(path: &str) -> Result { debug_assert!(path.as_bytes().last() == Some(&0)); - let fd = open(path.as_ptr() as *mut _, libc::O_RDONLY | libc::O_CLOEXEC); + let fd = open(path.as_ptr() as *const _, libc::O_RDONLY | libc::O_CLOEXEC); if fd < 0 { - return None; + return Err(last_os_error()); } // O_CLOEXEC works on all Unix targets except for older Linux kernels (pre // 2.6.23), so we also use an ioctl to make sure FD_CLOEXEC is set. #[cfg(target_os = "linux")] libc::ioctl(fd, libc::FIOCLEX); - Some(fd) + Ok(fd) } diff --git a/src/wasi.rs b/src/wasi.rs index 713c1ab9..4674f439 100644 --- a/src/wasi.rs +++ b/src/wasi.rs @@ -8,12 +8,12 @@ //! Implementation for WASI use crate::Error; -use core::num; -use wasi::wasi_unstable::random_get; +use core::num::NonZeroU32; +use wasi::random_get; pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - random_get(dest).map_err(|e: num::NonZeroU16| { - // convert wasi's NonZeroU16 error into getrandom's NonZeroU32 error - num::NonZeroU32::new(e.get() as u32).unwrap().into() + unsafe { random_get(dest.as_mut_ptr(), dest.len()) }.map_err(|e: wasi::Error| { + // convert wasi's Error into getrandom's NonZeroU32 error + NonZeroU32::new(e.raw_error() as u32).unwrap().into() }) }