Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove libstd dependancy for Opening and Reading files #58

Merged
merged 11 commits into from
Aug 6, 2019
Merged
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 4 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens in this case when the getrandom crate is used both by std and by other crates wanting std features?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC right now getrandom will be compiled into std, so enabling features for it during usual builds will not influence the copy of the crate used by std. Although I am not sure how it will work with rust-lang/rfcs#2663.


// These targets read from a file as a fallback method.
Expand Down
59 changes: 37 additions & 22 deletions src/use_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,11 @@
// 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:";
#[cfg(any(target_os = "android", target_os = "linux", target_os = "netbsd"))]
const FILE_PATH: &str = "/dev/urandom";
const FILE_PATH: &str = "rand:\0";
#[cfg(any(
target_os = "dragonfly",
target_os = "emscripten",
Expand All @@ -27,32 +20,54 @@ 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<RawFd> {
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()?;
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux", target_os = "netbsd"))] {
fn init_file() -> Option<libc::c_int> {
// 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
}
} else {
fn init_file() -> Option<libc::c_int> {
unsafe { open_readonly(FILE_PATH) }
}
}
Some(File::open(FILE_PATH).ok()?.into_raw_fd())
}
22 changes: 22 additions & 0 deletions src/util_libc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,25 @@ 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<libc::c_int> {
debug_assert!(path.as_bytes().last() == Some(&0));
let fd = open(path.as_ptr() as *mut _, libc::O_RDONLY | libc::O_CLOEXEC);
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
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(fd, libc::FIOCLEX);
Some(fd)
}