Skip to content

Commit fdf5195

Browse files
committed
std::rand::OsRng: Use getrandom syscall on Linux
`getrandom(2)` system call [1] has been added on Linux 3.17. This patch makes `OsRng` use `getrandom` if available, and use traditional `/dev/urandom` fallback if not. [1]: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=c6e9d6f38894798696f23c8084ca7edbf16ee895
1 parent 14cd5c5 commit fdf5195

File tree

2 files changed

+120
-12
lines changed

2 files changed

+120
-12
lines changed

src/libstd/rand/mod.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,12 @@
4545
//! so the "quality" of `/dev/random` is not better than `/dev/urandom` in most cases.
4646
//! However, this means that `/dev/urandom` can yield somewhat predictable randomness
4747
//! if the entropy pool is very small, such as immediately after first booting.
48-
//! If an application likely to be run soon after first booting, or on a system with very
49-
//! few entropy sources, one should consider using `/dev/random` via `ReaderRng`.
48+
//! Linux 3,17 added `getrandom(2)` system call which solves the issue: it blocks if entropy
49+
//! pool is not initialized yet, but it does not block once initialized.
50+
//! `OsRng` tries to use `getrandom(2)` if available, and use `/dev/urandom` fallback if not.
51+
//! If an application does not have `getrandom` and likely to be run soon after first booting,
52+
//! or on a system with very few entropy sources, one should consider using `/dev/random` via
53+
//! `ReaderRng`.
5054
//! - On some systems (e.g. FreeBSD, OpenBSD and Mac OS X) there is no difference
5155
//! between the two sources. (Also note that, on some systems e.g. FreeBSD, both `/dev/random`
5256
//! and `/dev/urandom` may block once if the CSPRNG has not seeded yet.)

src/libstd/rand/os.rs

+114-10
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,149 @@ pub use self::imp::OsRng;
1515

1616
#[cfg(all(unix, not(target_os = "ios")))]
1717
mod imp {
18+
extern crate libc;
19+
1820
use io::{IoResult, File};
1921
use path::Path;
2022
use rand::Rng;
2123
use rand::reader::ReaderRng;
2224
use result::{Ok, Err};
25+
use slice::{ImmutableSlice, MutableSlice};
26+
use mem;
27+
use os::errno;
28+
29+
#[cfg(all(target_os = "linux",
30+
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
31+
fn getrandom(buf: &mut [u8]) -> libc::c_long {
32+
extern "C" {
33+
fn syscall(number: libc::c_long, ...) -> libc::c_long;
34+
}
35+
36+
#[cfg(target_arch = "x86_64")]
37+
const NR_GETRANDOM: libc::c_long = 318;
38+
#[cfg(target_arch = "x86")]
39+
const NR_GETRANDOM: libc::c_long = 355;
40+
#[cfg(target_arch = "arm")]
41+
const NR_GETRANDOM: libc::c_long = 384;
42+
43+
unsafe {
44+
syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), 0u)
45+
}
46+
}
47+
48+
#[cfg(not(all(target_os = "linux",
49+
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
50+
fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }
51+
52+
fn getrandom_fill_bytes(v: &mut [u8]) {
53+
let mut read = 0;
54+
let len = v.len();
55+
while read < len {
56+
let result = getrandom(v[mut read..]);
57+
if result == -1 {
58+
let err = errno() as libc::c_int;
59+
if err == libc::EINTR {
60+
continue;
61+
} else {
62+
panic!("unexpected getrandom error: {}", err);
63+
}
64+
} else {
65+
read += result as uint;
66+
}
67+
}
68+
}
69+
70+
fn getrandom_next_u32() -> u32 {
71+
let mut buf: [u8, ..4] = [0u8, ..4];
72+
getrandom_fill_bytes(&mut buf);
73+
unsafe { mem::transmute::<[u8, ..4], u32>(buf) }
74+
}
75+
76+
fn getrandom_next_u64() -> u64 {
77+
let mut buf: [u8, ..8] = [0u8, ..8];
78+
getrandom_fill_bytes(&mut buf);
79+
unsafe { mem::transmute::<[u8, ..8], u64>(buf) }
80+
}
81+
82+
#[cfg(all(target_os = "linux",
83+
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
84+
fn is_getrandom_available() -> bool {
85+
use sync::atomic::{AtomicBool, INIT_ATOMIC_BOOL, Relaxed};
86+
87+
static GETRANDOM_CHECKED: AtomicBool = INIT_ATOMIC_BOOL;
88+
static GETRANDOM_AVAILABLE: AtomicBool = INIT_ATOMIC_BOOL;
89+
90+
if !GETRANDOM_CHECKED.load(Relaxed) {
91+
let mut buf: [u8, ..0] = [];
92+
let result = getrandom(&mut buf);
93+
let available = if result == -1 {
94+
let err = errno() as libc::c_int;
95+
err != libc::ENOSYS
96+
} else {
97+
true
98+
};
99+
GETRANDOM_AVAILABLE.store(available, Relaxed);
100+
GETRANDOM_CHECKED.store(true, Relaxed);
101+
available
102+
} else {
103+
GETRANDOM_AVAILABLE.load(Relaxed)
104+
}
105+
}
106+
107+
#[cfg(not(all(target_os = "linux",
108+
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
109+
fn is_getrandom_available() -> bool { false }
23110

24111
/// A random number generator that retrieves randomness straight from
25112
/// the operating system. Platform sources:
26113
///
27114
/// - Unix-like systems (Linux, Android, Mac OSX): read directly from
28-
/// `/dev/urandom`.
115+
/// `/dev/urandom`, or from `getrandom(2)` system call if available.
29116
/// - Windows: calls `CryptGenRandom`, using the default cryptographic
30117
/// service provider with the `PROV_RSA_FULL` type.
31118
/// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
32119
/// This does not block.
33-
#[cfg(unix)]
34120
pub struct OsRng {
35-
inner: ReaderRng<File>
121+
inner: OsRngInner,
122+
}
123+
124+
enum OsRngInner {
125+
OsGetrandomRng,
126+
OsReaderRng(ReaderRng<File>),
36127
}
37128

38129
impl OsRng {
39130
/// Create a new `OsRng`.
40131
pub fn new() -> IoResult<OsRng> {
132+
if is_getrandom_available() {
133+
return Ok(OsRng { inner: OsGetrandomRng });
134+
}
135+
41136
let reader = try!(File::open(&Path::new("/dev/urandom")));
42137
let reader_rng = ReaderRng::new(reader);
43138

44-
Ok(OsRng { inner: reader_rng })
139+
Ok(OsRng { inner: OsReaderRng(reader_rng) })
45140
}
46141
}
47142

48143
impl Rng for OsRng {
49144
fn next_u32(&mut self) -> u32 {
50-
self.inner.next_u32()
145+
match self.inner {
146+
OsGetrandomRng => getrandom_next_u32(),
147+
OsReaderRng(ref mut rng) => rng.next_u32(),
148+
}
51149
}
52150
fn next_u64(&mut self) -> u64 {
53-
self.inner.next_u64()
151+
match self.inner {
152+
OsGetrandomRng => getrandom_next_u64(),
153+
OsReaderRng(ref mut rng) => rng.next_u64(),
154+
}
54155
}
55156
fn fill_bytes(&mut self, v: &mut [u8]) {
56-
self.inner.fill_bytes(v)
157+
match self.inner {
158+
OsGetrandomRng => getrandom_fill_bytes(v),
159+
OsReaderRng(ref mut rng) => rng.fill_bytes(v)
160+
}
57161
}
58162
}
59163
}
@@ -75,7 +179,7 @@ mod imp {
75179
/// the operating system. Platform sources:
76180
///
77181
/// - Unix-like systems (Linux, Android, Mac OSX): read directly from
78-
/// `/dev/urandom`.
182+
/// `/dev/urandom`, or from `getrandom(2)` system call if available.
79183
/// - Windows: calls `CryptGenRandom`, using the default cryptographic
80184
/// service provider with the `PROV_RSA_FULL` type.
81185
/// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
@@ -145,10 +249,10 @@ mod imp {
145249
/// the operating system. Platform sources:
146250
///
147251
/// - Unix-like systems (Linux, Android, Mac OSX): read directly from
148-
/// `/dev/urandom`.
252+
/// `/dev/urandom`, or from `getrandom(2)` system call if available.
149253
/// - Windows: calls `CryptGenRandom`, using the default cryptographic
150254
/// service provider with the `PROV_RSA_FULL` type.
151-
///
255+
/// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
152256
/// This does not block.
153257
pub struct OsRng {
154258
hcryptprov: HCRYPTPROV

0 commit comments

Comments
 (0)