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

Linux/Android: Read a byte from /dev/random instead of polling it. #449

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions src/use_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,34 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
// 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.
// Read a byte from /dev/random to make sure it is ok to read from
// /dev/urandom. reading a byte instead of polling is more compatible with
// sandboxes that disallow `poll()` but which allow reading /dev/random,
Copy link
Member

Choose a reason for hiding this comment

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

I think this sounds reasonable, but do we have any documentation or examples of sandboxes which have this issue? It's not obvious to me that read-ing from an open file descriptor would be better or worse than poll-ing or select-ing it.

The reason for the current implementation (discussed in #58 and briansmith/ring#558 (comment)) is to avoid "debiting" a single byte from the /dev/random "entropy estimate". Both libsodium and openssl try to avoid this read:

Personally, I think the downside of reading one byte from the /dev/random pool is not that bad, and I prefer the simplicity of this approch. I just want to understand why select/poll is not a good fit in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just want to understand why select/poll is not a good fit in practice.

I am submitting this change is because I have to write documentation about how to configure sandboxes and when one can avoid doing pre-sandbox initializations. In investigating this, I found that it is actually really difficult to document the seccomp policy for poll. The Chromium sandbox has conditional logic for __NR_POLL vs __NR_PPOLL for example. I added some more commentary about this in the comments of this PR.

I am also trying to ensure that if somebody is already using OpenSSL (or a fork of it) then switching to a getrandom-based library will not affect their sandbox policies.

So, in some sense it is hypothetical, but that's mostly because I write libraries for orgs who make frameworks for people who product applications.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

openssl uses select() by default but can be configured to use read()

OpenSSL uses select() unless there are too many file descriptors open, in which case it will use read().

Copy link
Member

Choose a reason for hiding this comment

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

Those additional comments look great! Thanks for the explanation, makes sense to me!

// e.g. sandboxes that assume that `poll()` is for network I/O. This way,
// fewer applications will have to insert pre-sandbox-initialization logic.
// Often (blocking) file I/O is not allowed in such early phases of an
// application for performance and/or security reasons.
//
// It is hard to write a sandbox policy to support `libc::poll()` because
// it may be invoked as `SYS_POLL`, `SYS_PPOLL`, or even `SYS_SELECT`,
// depending on the libc implementation (e.g. glibc vs musl), libc version,
// potentially the kernel version at runtime, and/or the target
// architecture.
//
// BoringSSL and libstd don't try to protect against insecure output from
// `/dev/urandom'; they don't open `/dev/random` at all.
//
// OpenSSL uses `libc::select()` unless the `dev/random` file descriptor
// is too large; if it is too large then it does what we do here.

let fd = open_readonly(b"/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 {
debug_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),
}
}
let mut dummy: [MaybeUninit<u8>; 1] = [MaybeUninit::uninit()];
sys_fill_exact(&mut dummy, |buf| unsafe {
libc::read(fd, buf.as_mut_ptr().cast::<c_void>(), buf.len())
briansmith marked this conversation as resolved.
Show resolved Hide resolved
})
}

struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
Expand Down
Loading