Skip to content

Commit dd3a01f

Browse files
committed
m: Add rustix backend
This commit adds a backend based on the rustix crate. The main advantage of rustix, in addition to greater safety, is that it uses raw syscalls instead of going through libc. I've tried to modify the existing libc code as little as possible, in order to make this change as auditable as possible. I haven't touched the pthreads code yet, as that's a little tricky. This exists for discussion purposes. cc rust-random#401 Signed-off-by: John Nunley <[email protected]>
1 parent cf65e83 commit dd3a01f

File tree

7 files changed

+149
-11
lines changed

7 files changed

+149
-11
lines changed

.github/workflows/tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
strategy:
4545
matrix:
4646
os: [ubuntu-22.04, windows-2022]
47-
toolchain: [nightly, beta, stable, 1.36]
47+
toolchain: [nightly, beta, stable, 1.63]
4848
# Only Test macOS on stable to reduce macOS CI jobs
4949
include:
5050
# x86_64-apple-darwin.

Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ documentation = "https://docs.rs/getrandom"
99
repository = "https://github.com/rust-random/getrandom"
1010
categories = ["os", "no-std"]
1111
exclude = [".*"]
12+
rust-version = "1.63"
1213

1314
[dependencies]
1415
cfg-if = "1"
@@ -23,13 +24,17 @@ libc = { version = "0.2.154", default-features = false }
2324
[target.'cfg(target_os = "wasi")'.dependencies]
2425
wasi = { version = "0.11", default-features = false }
2526

27+
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
28+
rustix = { version = "0.38.0", features = ["event", "fs", "rand"], default-features = false, optional = true }
29+
2630
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
2731
wasm-bindgen = { version = "0.2.62", default-features = false, optional = true }
2832
js-sys = { version = "0.3", optional = true }
2933
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies]
3034
wasm-bindgen-test = "0.3.18"
3135

3236
[features]
37+
default = ["rustix"]
3338
# Implement std-only traits for getrandom::Error
3439
std = []
3540
# Disable `/dev/urandom` fallback for Linux and Android targets.
@@ -46,6 +51,7 @@ rustc-dep-of-std = [
4651
"compiler_builtins",
4752
"core",
4853
"libc/rustc-dep-of-std",
54+
"rustix?/rustc-dep-of-std",
4955
"wasi/rustc-dep-of-std",
5056
]
5157
# Unstable/test-only feature to run wasm-bindgen tests in a browser

src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,15 @@ cfg_if! {
294294
)
295295
),
296296
))] {
297+
#[cfg(feature = "rustix")]
298+
mod util_rustix;
297299
mod util_libc;
298300
mod use_file;
299301
mod lazy;
300302
#[path = "linux_android_with_fallback.rs"] mod imp;
301303
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
304+
#[cfg(feature = "rustix")]
305+
mod util_rustix;
302306
mod util_libc;
303307
#[path = "linux_android.rs"] mod imp;
304308
} else if #[cfg(target_os = "solaris")] {

src/linux_android.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
//! Implementation for Linux / Android without `/dev/urandom` fallback
2-
use crate::{util_libc, Error};
2+
use crate::Error;
33
use core::mem::MaybeUninit;
44

5+
#[cfg(not(feature = "rustix"))]
6+
use crate::util_libc;
7+
#[cfg(feature = "rustix")]
8+
use crate::util_rustix as util_libc;
9+
510
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
611
util_libc::sys_fill_exact(dest, util_libc::getrandom_syscall)
712
}

src/linux_android_with_fallback.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
//! Implementation for Linux / Android with `/dev/urandom` fallback
22
use crate::{
33
lazy::LazyBool,
4-
util_libc::{getrandom_syscall, last_os_error, sys_fill_exact},
54
{use_file, Error},
65
};
76
use core::mem::MaybeUninit;
87

8+
#[cfg(not(feature = "rustix"))]
9+
use crate::util_libc::{getrandom_syscall, last_os_error, sys_fill_exact};
10+
11+
#[cfg(feature = "rustix")]
12+
use {
13+
crate::util_rustix::{getrandom_syscall, sys_fill_exact},
14+
rustix::{io::Errno, rand},
15+
};
16+
917
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
1018
// getrandom(2) was introduced in Linux 3.17
1119
static HAS_GETRANDOM: LazyBool = LazyBool::new();
@@ -16,6 +24,7 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
1624
}
1725
}
1826

27+
#[cfg(not(feature = "rustix"))]
1928
fn is_getrandom_available() -> bool {
2029
if getrandom_syscall(&mut []) < 0 {
2130
match last_os_error().raw_os_error() {
@@ -31,3 +40,13 @@ fn is_getrandom_available() -> bool {
3140
true
3241
}
3342
}
43+
44+
#[cfg(feature = "rustix")]
45+
fn is_getrandom_available() -> bool {
46+
match rand::getrandom(&mut [], rand::GetRandomFlags::empty()) {
47+
Err(Errno::NOSYS) => false,
48+
#[cfg(target_os = "linux")]
49+
Err(Errno::PERM) => false,
50+
_ => true,
51+
}
52+
}

src/use_file.rs

+49-8
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
11
//! Implementations that just need to read from a file
2-
use crate::{
3-
util_libc::{open_readonly, sys_fill_exact},
4-
Error,
5-
};
2+
use crate::Error;
63
use core::{
74
cell::UnsafeCell,
85
mem::MaybeUninit,
96
sync::atomic::{AtomicUsize, Ordering::Relaxed},
107
};
118

9+
#[cfg(not(all(any(target_os = "linux", target_os = "android"), feature = "rustix")))]
10+
use crate::util_libc::{open_readonly, sys_fill_exact};
11+
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "rustix"))]
12+
use crate::util_rustix::{open_readonly, sys_fill_exact};
13+
1214
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
1315
/// For more information see the linked man pages in lib.rs.
1416
/// - On Linux, "/dev/urandom is preferred and sufficient in all use cases".
1517
/// - On Redox, only /dev/urandom is provided.
1618
/// - On AIX, /dev/urandom will "provide cryptographically secure output".
1719
/// - On Haiku and QNX Neutrino they are identical.
20+
#[cfg(not(feature = "rustix"))]
1821
const FILE_PATH: &str = "/dev/urandom\0";
22+
#[cfg(feature = "rustix")]
23+
const FILE_PATH: &str = "/dev/urandom";
1924
const FD_UNINIT: usize = usize::max_value();
2025

2126
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
2227
let fd = get_rng_fd()?;
23-
sys_fill_exact(dest, |buf| unsafe {
24-
libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len())
25-
})
28+
sys_fill_exact(dest, |buf| read_from_fd(fd, buf))
29+
}
30+
31+
#[cfg(not(feature = "rustix"))]
32+
fn read_from_fd(fd: libc::c_int, buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
33+
unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }
34+
}
35+
36+
#[cfg(feature = "rustix")]
37+
fn read_from_fd(
38+
fd: libc::c_int,
39+
buf: &mut [MaybeUninit<u8>],
40+
) -> Result<(&mut [u8], &mut [MaybeUninit<u8>]), rustix::io::Errno> {
41+
rustix::io::read_uninit(unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) }, buf)
2642
}
2743

2844
// Returns the file descriptor for the device file used to retrieve random
@@ -56,7 +72,10 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
5672
#[cfg(any(target_os = "android", target_os = "linux"))]
5773
wait_until_rng_ready()?;
5874

75+
#[allow(unused_unsafe)]
5976
let fd = unsafe { open_readonly(FILE_PATH)? };
77+
#[cfg(feature = "rustix")]
78+
let fd = rustix::fd::IntoRawFd::into_raw_fd(fd);
6079
// The fd always fits in a usize without conflicting with FD_UNINIT.
6180
debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT);
6281
FD.store(fd as usize, Relaxed);
@@ -65,7 +84,10 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
6584
}
6685

6786
// Succeeds once /dev/urandom is safe to read from
68-
#[cfg(any(target_os = "android", target_os = "linux"))]
87+
#[cfg(all(
88+
any(target_os = "android", target_os = "linux"),
89+
not(feature = "rustix")
90+
))]
6991
fn wait_until_rng_ready() -> Result<(), Error> {
7092
// Poll /dev/random to make sure it is ok to read from /dev/urandom.
7193
let fd = unsafe { open_readonly("/dev/random\0")? };
@@ -93,6 +115,25 @@ fn wait_until_rng_ready() -> Result<(), Error> {
93115
}
94116
}
95117

118+
// Succeeds once /dev/urandom is safe to read from
119+
#[cfg(all(any(target_os = "android", target_os = "linux"), feature = "rustix"))]
120+
fn wait_until_rng_ready() -> Result<(), Error> {
121+
use rustix::event;
122+
123+
// Open the file.
124+
let fd = crate::util_rustix::open_readonly("/dev/random")?;
125+
126+
// Poll it until it is ready.
127+
let mut pfd = [event::PollFd::new(&fd, event::PollFlags::IN)];
128+
loop {
129+
match event::poll(&mut pfd, -1) {
130+
Ok(_) => return Ok(()),
131+
Err(rustix::io::Errno::INTR) => continue,
132+
Err(err) => return Err(crate::util_rustix::cvt(err)),
133+
}
134+
}
135+
}
136+
96137
struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
97138

98139
impl Mutex {

src/util_rustix.rs

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! Utilities for using rustix.
2+
//!
3+
//! At this point in time it is only used on Linux-like operating systems.
4+
5+
use crate::Error;
6+
use core::convert::TryInto;
7+
use core::mem::MaybeUninit;
8+
use core::num::NonZeroU32;
9+
10+
use rustix::fd::OwnedFd;
11+
use rustix::fs;
12+
use rustix::io::Errno;
13+
use rustix::rand;
14+
15+
/// Convert a Rustix error to one of our errors.
16+
pub(crate) fn cvt(err: Errno) -> Error {
17+
match TryInto::<u32>::try_into(err.raw_os_error())
18+
.ok()
19+
.and_then(NonZeroU32::new)
20+
{
21+
Some(code) => Error::from(code),
22+
None => Error::ERRNO_NOT_POSITIVE,
23+
}
24+
}
25+
26+
/// Fill a buffer by repeatedly invoking a `rustix` call.
27+
pub(crate) fn sys_fill_exact(
28+
mut buf: &mut [MaybeUninit<u8>],
29+
fill: impl Fn(&mut [MaybeUninit<u8>]) -> Result<(&mut [u8], &mut [MaybeUninit<u8>]), Errno>,
30+
) -> Result<(), Error> {
31+
while !buf.is_empty() {
32+
// Read into the buffer.
33+
match fill(buf) {
34+
Err(err) => return Err(cvt(err)),
35+
Ok((_filled, unfilled)) => {
36+
buf = unfilled;
37+
}
38+
}
39+
}
40+
41+
Ok(())
42+
}
43+
44+
/// Open a file as read-only.
45+
pub(crate) fn open_readonly(path: &str) -> Result<OwnedFd, Error> {
46+
loop {
47+
match fs::open(
48+
path,
49+
fs::OFlags::CLOEXEC | fs::OFlags::RDONLY,
50+
fs::Mode::empty(),
51+
) {
52+
Ok(file) => return Ok(file),
53+
Err(Errno::INTR) => continue,
54+
Err(err) => return Err(cvt(err)),
55+
}
56+
}
57+
}
58+
59+
pub(crate) fn getrandom_syscall(
60+
buf: &mut [MaybeUninit<u8>],
61+
) -> Result<(&mut [u8], &mut [MaybeUninit<u8>]), Errno> {
62+
rand::getrandom_uninit(buf, rand::GetRandomFlags::empty())
63+
}

0 commit comments

Comments
 (0)