From 09a615c09761d5066b19d4b58a6a1d0e515ede45 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sun, 26 Aug 2018 21:13:44 +0200 Subject: [PATCH 1/3] Reduce number of syscalls in `rand` In case that it is statically known that the OS doesn't support `getrandom` (non-Linux) or becomes clear at runtime that `getrandom` isn't available (`ENOSYS`), the opened fd ("/dev/urandom") isn't closed after the function, so that future calls can reuse it. This saves repeated `open`/`close` system calls at the cost of one permanently open fd. Additionally, this skips the initial zero-length `getrandom` call and directly hands the user buffer to the operating system, saving one `getrandom` syscall. --- src/libstd/sys/unix/rand.rs | 96 ++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/src/libstd/sys/unix/rand.rs b/src/libstd/sys/unix/rand.rs index 01c0ada4ffbe5..857d0370c299b 100644 --- a/src/libstd/sys/unix/rand.rs +++ b/src/libstd/sys/unix/rand.rs @@ -30,8 +30,23 @@ mod imp { use fs::File; use io::Read; use libc; + use sync::atomic::{AtomicBool, AtomicI32, Ordering}; use sys::os::errno; + static GETRANDOM_URANDOM_FD: AtomicI32 = AtomicI32::new(-1); + #[cfg(any(target_os = "linux", target_os = "android"))] + static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false); + + #[cfg(any(target_os = "linux", target_os = "android"))] + fn is_getrandom_permanently_unavailable() -> bool { + GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) + } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + fn is_getrandom_permanently_unavailable() -> bool { + true + } + #[cfg(any(target_os = "linux", target_os = "android"))] fn getrandom(buf: &mut [u8]) -> libc::c_long { unsafe { @@ -40,9 +55,14 @@ mod imp { } #[cfg(not(any(target_os = "linux", target_os = "android")))] - fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 } + fn getrandom_fill_bytes(_buf: &mut [u8]) -> bool { false } + #[cfg(any(target_os = "linux", target_os = "android"))] fn getrandom_fill_bytes(v: &mut [u8]) -> bool { + if is_getrandom_permanently_unavailable() { + return false; + } + let mut read = 0; while read < v.len() { let result = getrandom(&mut v[read..]); @@ -50,8 +70,10 @@ mod imp { let err = errno() as libc::c_int; if err == libc::EINTR { continue; + } else if err == libc::ENOSYS { + GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed); } else if err == libc::EAGAIN { - return false + return false; } else { panic!("unexpected getrandom error: {}", err); } @@ -59,52 +81,48 @@ mod imp { read += result as usize; } } - - return true + true } - #[cfg(any(target_os = "linux", target_os = "android"))] - fn is_getrandom_available() -> bool { - use io; - use sync::atomic::{AtomicBool, Ordering}; - use sync::Once; - - static CHECKER: Once = Once::new(); - static AVAILABLE: AtomicBool = AtomicBool::new(false); - - CHECKER.call_once(|| { - let mut buf: [u8; 0] = []; - let result = getrandom(&mut buf); - let available = if result == -1 { - let err = io::Error::last_os_error().raw_os_error(); - err != Some(libc::ENOSYS) - } else { - true - }; - AVAILABLE.store(available, Ordering::Relaxed); - }); - - AVAILABLE.load(Ordering::Relaxed) - } - - #[cfg(not(any(target_os = "linux", target_os = "android")))] - fn is_getrandom_available() -> bool { false } - pub fn fill_bytes(v: &mut [u8]) { // getrandom_fill_bytes here can fail if getrandom() returns EAGAIN, // meaning it would have blocked because the non-blocking pool (urandom) - // has not initialized in the kernel yet due to a lack of entropy the + // has not initialized in the kernel yet due to a lack of entropy. The // fallback we do here is to avoid blocking applications which could // depend on this call without ever knowing they do and don't have a - // work around. The PRNG of /dev/urandom will still be used but not - // over a completely full entropy pool - if is_getrandom_available() && getrandom_fill_bytes(v) { - return + // work around. The PRNG of /dev/urandom will still be used but over a + // possibly predictable entropy pool. + if getrandom_fill_bytes(v) { + return; } - let mut file = File::open("/dev/urandom") - .expect("failed to open /dev/urandom"); - file.read_exact(v).expect("failed to read /dev/urandom"); + // getrandom failed for some reason. If the getrandom call is + // permanently unavailable (OS without getrandom, or OS version without + // getrandom), we'll keep around the fd for /dev/urandom for future + // requests, to avoid re-opening the file on every call. + // + // Otherwise, open /dev/urandom, read from it, and close it again. + use super::super::ext::io::{FromRawFd, IntoRawFd}; + let mut fd = GETRANDOM_URANDOM_FD.load(Ordering::Relaxed); + let mut close_fd = false; + if fd == -1 { + if !is_getrandom_permanently_unavailable() { + close_fd = true; + } + let file = File::open("/dev/urandom").expect("failed to open /dev/urandom"); + fd = file.into_raw_fd(); + // If some other thread also opened /dev/urandom and set the global + // fd already, we close our fd at the end of the function. + if !close_fd && GETRANDOM_URANDOM_FD.compare_and_swap(-1, fd, Ordering::Relaxed) != -1 { + close_fd = true; + } + } + let mut file = unsafe { File::from_raw_fd(fd) }; + let res = file.read_exact(v); + if !close_fd { + let _ = file.into_raw_fd(); + } + res.expect("failed to read /dev/urandom"); } } From d6d280b7df3bc3b333950bc5d0a766d17d433bf9 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Wed, 29 Aug 2018 22:30:29 +0200 Subject: [PATCH 2/3] Don't leak the file descriptor in `rand` --- src/libstd/sys/unix/rand.rs | 55 ++++++++----------------------------- 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/src/libstd/sys/unix/rand.rs b/src/libstd/sys/unix/rand.rs index 857d0370c299b..210ace9bbb226 100644 --- a/src/libstd/sys/unix/rand.rs +++ b/src/libstd/sys/unix/rand.rs @@ -29,23 +29,8 @@ pub fn hashmap_random_keys() -> (u64, u64) { mod imp { use fs::File; use io::Read; - use libc; - use sync::atomic::{AtomicBool, AtomicI32, Ordering}; - use sys::os::errno; - - static GETRANDOM_URANDOM_FD: AtomicI32 = AtomicI32::new(-1); - #[cfg(any(target_os = "linux", target_os = "android"))] - static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false); - #[cfg(any(target_os = "linux", target_os = "android"))] - fn is_getrandom_permanently_unavailable() -> bool { - GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) - } - - #[cfg(not(any(target_os = "linux", target_os = "android")))] - fn is_getrandom_permanently_unavailable() -> bool { - true - } + use libc; #[cfg(any(target_os = "linux", target_os = "android"))] fn getrandom(buf: &mut [u8]) -> libc::c_long { @@ -59,7 +44,11 @@ mod imp { #[cfg(any(target_os = "linux", target_os = "android"))] fn getrandom_fill_bytes(v: &mut [u8]) -> bool { - if is_getrandom_permanently_unavailable() { + use sync::atomic::{AtomicBool, Ordering}; + use sys::os::errno; + + static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false); + if GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) { return false; } @@ -96,33 +85,11 @@ mod imp { return; } - // getrandom failed for some reason. If the getrandom call is - // permanently unavailable (OS without getrandom, or OS version without - // getrandom), we'll keep around the fd for /dev/urandom for future - // requests, to avoid re-opening the file on every call. - // - // Otherwise, open /dev/urandom, read from it, and close it again. - use super::super::ext::io::{FromRawFd, IntoRawFd}; - let mut fd = GETRANDOM_URANDOM_FD.load(Ordering::Relaxed); - let mut close_fd = false; - if fd == -1 { - if !is_getrandom_permanently_unavailable() { - close_fd = true; - } - let file = File::open("/dev/urandom").expect("failed to open /dev/urandom"); - fd = file.into_raw_fd(); - // If some other thread also opened /dev/urandom and set the global - // fd already, we close our fd at the end of the function. - if !close_fd && GETRANDOM_URANDOM_FD.compare_and_swap(-1, fd, Ordering::Relaxed) != -1 { - close_fd = true; - } - } - let mut file = unsafe { File::from_raw_fd(fd) }; - let res = file.read_exact(v); - if !close_fd { - let _ = file.into_raw_fd(); - } - res.expect("failed to read /dev/urandom"); + // getrandom failed because it is permanently or temporarily (because + // of missing entropy) unavailable. Open /dev/urandom, read from it, + // and close it again. + let mut file = File::open("/dev/urandom").expect("failed to open /dev/urandom"); + file.read_exact(v).expect("failed to read /dev/urandom") } } From b95c491dfc77c17161ac858f8e466f6024f8bb4b Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sun, 2 Sep 2018 00:19:11 +0200 Subject: [PATCH 3/3] Fix an endless loop when `getrandom` is not available --- src/libstd/sys/unix/rand.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstd/sys/unix/rand.rs b/src/libstd/sys/unix/rand.rs index 210ace9bbb226..371e58a20c40d 100644 --- a/src/libstd/sys/unix/rand.rs +++ b/src/libstd/sys/unix/rand.rs @@ -61,6 +61,7 @@ mod imp { continue; } else if err == libc::ENOSYS { GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed); + return false; } else if err == libc::EAGAIN { return false; } else {