From 142ea1aa1eb444206fcc94cec233df87e2e253a0 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sun, 7 Jul 2019 02:38:46 -0700 Subject: [PATCH 01/11] Remove libstd for opening/reading files --- src/lib.rs | 18 ++-------------- src/use_file.rs | 55 ++++++++++++++++++++++++++++++------------------ src/util_libc.rs | 18 ++++++++++++++++ 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 338e806d..87927ac8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,22 +152,8 @@ mod util; #[allow(dead_code)] mod util_libc; -// std-only trait definitions (also need for use_file) -#[cfg(any( - feature = "std", - target_os = "android", - target_os = "dragonfly", - target_os = "emscripten", - target_os = "freebsd", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox", - target_os = "solaris", -))] +// std-only trait definitions +#[cfg(feature = "std")] mod error_impls; // These targets read from a file as a fallback method. diff --git a/src/use_file.rs b/src/use_file.rs index 74a12ef7..a1a01c9b 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -7,18 +7,13 @@ // except according to those terms. //! Implementations that just need to read from a file -extern crate std; - -use crate::util_libc::{last_os_error, LazyFd}; +use crate::util_libc::{last_os_error, open_readonly, sys_fill_exact, LazyFd}; use crate::Error; -use core::mem::ManuallyDrop; -use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; -use std::{fs::File, io::Read}; #[cfg(target_os = "redox")] -const FILE_PATH: &str = "rand:"; +const FILE_PATH: &str = "rand:\0"; #[cfg(any(target_os = "android", target_os = "linux", target_os = "netbsd"))] -const FILE_PATH: &str = "/dev/urandom"; +const FILE_PATH: &str = "/dev/urandom\0"; #[cfg(any( target_os = "dragonfly", target_os = "emscripten", @@ -27,32 +22,50 @@ const FILE_PATH: &str = "/dev/urandom"; target_os = "solaris", target_os = "illumos" ))] -const FILE_PATH: &str = "/dev/random"; +const FILE_PATH: &str = "/dev/random\0"; pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { static FD: LazyFd = LazyFd::new(); let fd = FD.init(init_file).ok_or(last_os_error())?; - let file = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }); - let mut file_ref: &File = &file; + let read = |buf: &mut [u8]| unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) }; if cfg!(target_os = "emscripten") { // `Crypto.getRandomValues` documents `dest` should be at most 65536 bytes. for chunk in dest.chunks_mut(65536) { - file_ref.read_exact(chunk)?; + sys_fill_exact(chunk, read)?; } } else { - file_ref.read_exact(dest)?; + sys_fill_exact(dest, read)?; } Ok(()) } -fn init_file() -> Option { - if FILE_PATH == "/dev/urandom" { - // read one byte from "/dev/random" to ensure that OS RNG has initialized - File::open("/dev/random") - .ok()? - .read_exact(&mut [0u8; 1]) - .ok()?; +fn init_file() -> Option { + if FILE_PATH == "/dev/urandom\0" { + // 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 mut res = -1; + while res <= 0 { + // A negative timeout means an infinite timeout. + res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res < 0 { + match last_os_error().raw_os_error() { + Some(libc::EINTR) | Some(libc::EAGAIN) => {} + _ => break, + } + } + } + + unsafe { libc::close(pfd.fd) }; + if res != 1 { + // We either hard failed, or poll() returned the wrong pfd. + return None; + } } - Some(File::open(FILE_PATH).ok()?.into_raw_fd()) + unsafe { open_readonly(FILE_PATH) } } diff --git a/src/util_libc.rs b/src/util_libc.rs index 015d1a04..7fc9c973 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -116,3 +116,21 @@ impl LazyFd { } } } + +cfg_if! { + if #[cfg(any(target_os = "linux", target_os = "emscripten"))] { + use libc::open64 as open; + } else { + use libc::open; + } +} + +// SAFETY: path must be null terminated, FD must be manually closed. +pub unsafe fn open_readonly(path: &str) -> Option { + // We don't care about Linux OSes too old to support O_CLOEXEC. + let fd = open(path.as_ptr() as *mut _, libc::O_RDONLY | libc::O_CLOEXEC); + if fd < 0 { + return None; + } + Some(fd) +} From 55fddf0bf94fccc2e0ca82d539b89a454db52bcd Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 29 Jul 2019 00:13:30 -0700 Subject: [PATCH 02/11] Add debug_assert! for null termination --- src/util_libc.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util_libc.rs b/src/util_libc.rs index 7fc9c973..9bcd02b2 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -127,6 +127,7 @@ cfg_if! { // SAFETY: path must be null terminated, FD must be manually closed. pub unsafe fn open_readonly(path: &str) -> Option { + debug_assert!(path.as_bytes().last() == Some(&0)); // We don't care about Linux OSes too old to support O_CLOEXEC. let fd = open(path.as_ptr() as *mut _, libc::O_RDONLY | libc::O_CLOEXEC); if fd < 0 { From ab3d7dbb93e32aab0d5bf4e04b8d4b89b46c43e8 Mon Sep 17 00:00:00 2001 From: Joseph Richey Date: Mon, 29 Jul 2019 02:55:41 -0700 Subject: [PATCH 03/11] Disable libc default features --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5e1f1380..98663509 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ log = { version = "0.4", optional = true } cfg-if = "0.1" [target.'cfg(any(unix, target_os = "redox", target_os = "wasi"))'.dependencies] -libc = "0.2.60" +libc = { version = "0.2.60", default-features = false } [target.wasm32-unknown-unknown.dependencies] wasm-bindgen = { version = "0.2.29", optional = true } From 9d04e3a3d61c974fa0780dd77f6e6550f40a25f6 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 29 Jul 2019 17:30:10 -0700 Subject: [PATCH 04/11] Update documentation --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92d20341..cd1f2775 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A Rust library for retrieving random data from (operating) system source. It is assumed that system always provides high-quality cryptographically secure random data, ideally backed by hardware entropy sources. This crate derives its name -from Linux's `getrandom` function, but is cross platform, roughly supporting +from Linux's `getrandom` function, but is cross platform, aiming to support the same set of platforms as Rust's `std` lib. This is a low-level API. Most users should prefer using high-level random-number @@ -40,7 +40,10 @@ fn get_random_buf() -> Result<[u8; 32], getrandom::Error> { ## Features -This library is `no_std` compatible, but uses `std` on most platforms. +This library is `no_std` for every supported target. However, getting randomness +usually requires calling some external system API. This means most platforms +will require linking against system libraries (i.e. `libc` for Unix, +`Advapi32.dll` for Windows, Security framework on iOS, etc...). The `log` library is supported as an optional dependency. If enabled, error reporting will be improved on some platforms. From 6a45c49c259544ce34c745d23b01886b559b7e4f Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 30 Jul 2019 19:50:06 -0700 Subject: [PATCH 05/11] Add RHEL 5 compat --- src/util_libc.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util_libc.rs b/src/util_libc.rs index 9bcd02b2..dea5a436 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -128,10 +128,13 @@ cfg_if! { // SAFETY: path must be null terminated, FD must be manually closed. pub unsafe fn open_readonly(path: &str) -> Option { debug_assert!(path.as_bytes().last() == Some(&0)); - // We don't care about Linux OSes too old to support O_CLOEXEC. let fd = open(path.as_ptr() as *mut _, libc::O_RDONLY | libc::O_CLOEXEC); if fd < 0 { return None; } + // 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(self.fd, libc::FIOCLEX); Some(fd) } From 2602f24a68a633c36e368540e251cc42043a6633 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 30 Jul 2019 19:51:33 -0700 Subject: [PATCH 06/11] Revert docs change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd1f2775..cbc1a285 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A Rust library for retrieving random data from (operating) system source. It is assumed that system always provides high-quality cryptographically secure random data, ideally backed by hardware entropy sources. This crate derives its name -from Linux's `getrandom` function, but is cross platform, aiming to support +from Linux's `getrandom` function, but is cross platform, roughly supporting the same set of platforms as Rust's `std` lib. This is a low-level API. Most users should prefer using high-level random-number From 2a638fd993490ad9118764068c7caa5ea43d91c3 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 31 Jul 2019 16:10:14 +0000 Subject: [PATCH 07/11] fix ioctl call --- src/util_libc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util_libc.rs b/src/util_libc.rs index dea5a436..d662fc62 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -135,6 +135,6 @@ pub unsafe fn open_readonly(path: &str) -> Option { // 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(self.fd, libc::FIOCLEX); + libc::ioctl(fd, libc::FIOCLEX); Some(fd) } From ac0bf75c1f37d69d482e28a2282a0095f351ad23 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 5 Aug 2019 02:39:52 -0700 Subject: [PATCH 08/11] Use cfg-if to switch init_file implementation --- src/use_file.rs | 52 ++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/use_file.rs b/src/use_file.rs index a1a01c9b..65d327c2 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -12,8 +12,6 @@ use crate::Error; #[cfg(target_os = "redox")] const FILE_PATH: &str = "rand:\0"; -#[cfg(any(target_os = "android", target_os = "linux", target_os = "netbsd"))] -const FILE_PATH: &str = "/dev/urandom\0"; #[cfg(any( target_os = "dragonfly", target_os = "emscripten", @@ -40,32 +38,38 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { Ok(()) } -fn init_file() -> Option { - if FILE_PATH == "/dev/urandom\0" { - // 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, - }; +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux", target_os = "netbsd"))] { + 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 mut res = -1; - while res <= 0 { - // A negative timeout means an infinite timeout. - res = unsafe { libc::poll(&mut pfd, 1, -1) }; - if res < 0 { - match last_os_error().raw_os_error() { - Some(libc::EINTR) | Some(libc::EAGAIN) => {} - _ => break, + let mut res = -1; + while res <= 0 { + // A negative timeout means an infinite timeout. + res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res < 0 { + match last_os_error().raw_os_error() { + Some(libc::EINTR) | Some(libc::EAGAIN) => {} + _ => break, + } } } - } - unsafe { libc::close(pfd.fd) }; - if res != 1 { - // We either hard failed, or poll() returned the wrong pfd. - return None; + unsafe { libc::close(pfd.fd) }; + if res != 1 { + // We either hard failed, or poll() returned the wrong pfd. + return None; + } + unsafe { open_readonly("/dev/urandom\0") } + } + } else { + fn init_file() -> Option { + unsafe { open_readonly(FILE_PATH) } } } - unsafe { open_readonly(FILE_PATH) } } From e9a514389d60ea958e0deab44de7264a7b5c4054 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 5 Aug 2019 03:01:24 -0700 Subject: [PATCH 09/11] Simplify init_file loops --- src/use_file.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/use_file.rs b/src/use_file.rs index 65d327c2..814de32b 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -48,24 +48,23 @@ cfg_if! { revents: 0, }; - let mut res = -1; - while res <= 0 { + loop { // A negative timeout means an infinite timeout. - res = unsafe { libc::poll(&mut pfd, 1, -1) }; + let res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res == 1 { + unsafe { libc::close(pfd.fd) }; + return unsafe { open_readonly("/dev/urandom\0") }; + } if res < 0 { - match last_os_error().raw_os_error() { - Some(libc::EINTR) | Some(libc::EAGAIN) => {} - _ => break, + let e = last_os_error().raw_os_error(); + if e == Some(libc::EINTR) || e == Some(libc::EAGAIN) { + continue; } } - } - - unsafe { libc::close(pfd.fd) }; - if res != 1 { // We either hard failed, or poll() returned the wrong pfd. + unsafe { libc::close(pfd.fd) }; return None; } - unsafe { open_readonly("/dev/urandom\0") } } } else { fn init_file() -> Option { From 4014891d6ed37e9492ba29cc0ee7d6bb6f6794cc Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 5 Aug 2019 03:08:26 -0700 Subject: [PATCH 10/11] Update docs --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 87927ac8..d308d431 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,13 +12,13 @@ //! //! | OS | interface //! |------------------|--------------------------------------------------------- -//! | Linux, Android | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after reading from `/dev/random` once +//! | Linux, Android | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` //! | Windows | [`RtlGenRandom`][3] //! | macOS | [`getentropy()`][19] if available, otherwise [`/dev/random`][20] (identical to `/dev/urandom`) //! | iOS | [`SecRandomCopyBytes`][4] //! | FreeBSD | [`getrandom()`][21] if available, otherwise [`kern.arandom`][5] //! | OpenBSD | [`getentropy`][6] -//! | NetBSD | [`/dev/urandom`][7] after reading from `/dev/random` once +//! | NetBSD | [`/dev/urandom`][7] after successfully polling `/dev/random` //! | Dragonfly BSD | [`/dev/random`][8] //! | Solaris, illumos | [`getrandom`][9] system call if available, otherwise [`/dev/random`][10] //! | Fuchsia OS | [`cprng_draw`][11] From b27cf13476679310214dd161838657b0b9791212 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 5 Aug 2019 11:36:18 -0700 Subject: [PATCH 11/11] Return values from init_file loop --- src/use_file.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/use_file.rs b/src/use_file.rs index 814de32b..f5834795 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -48,23 +48,22 @@ cfg_if! { revents: 0, }; - loop { + let ret = loop { // A negative timeout means an infinite timeout. let res = unsafe { libc::poll(&mut pfd, 1, -1) }; if res == 1 { - unsafe { libc::close(pfd.fd) }; - return unsafe { open_readonly("/dev/urandom\0") }; - } - if res < 0 { + 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. - unsafe { libc::close(pfd.fd) }; - return None; - } + break None; + }; + unsafe { libc::close(pfd.fd) }; + ret } } else { fn init_file() -> Option {