-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Implement io::Entropy
and refactor random data generation
#108874
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,9 +13,9 @@ use crate::error::Error; | |
use crate::fmt::{self, Debug}; | ||
#[allow(deprecated)] | ||
use crate::hash::{BuildHasher, Hash, Hasher, SipHasher13}; | ||
use crate::io::{entropy, Read}; | ||
use crate::iter::FusedIterator; | ||
use crate::ops::Index; | ||
use crate::sys; | ||
|
||
/// A [hash map] implemented with quadratic probing and SIMD lookup. | ||
/// | ||
|
@@ -3122,7 +3122,17 @@ impl RandomState { | |
// increment one of the seeds on every RandomState creation, giving | ||
// every corresponding HashMap a different iteration order. | ||
thread_local!(static KEYS: Cell<(u64, u64)> = { | ||
Cell::new(sys::hashmap_random_keys()) | ||
if crate::sys::entropy::INSECURE_HASHMAP { | ||
Cell::new((1, 2)) | ||
} else { | ||
let mut v = [0u8; 16]; | ||
let mut entropy = entropy(); | ||
entropy.set_insecure(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the purpose is to prevent hash DOS, attacks, wouldn't setting insecure be counter to that? Or am I missing the purpose of this flag? |
||
entropy.read_exact(&mut v).expect("failed to generate random keys for hashmap"); | ||
let key1 = v[..8].try_into().unwrap(); | ||
let key2 = v[8..].try_into().unwrap(); | ||
Cell::new((u64::from_ne_bytes(key1), u64::from_ne_bytes(key2))) | ||
} | ||
}); | ||
|
||
KEYS.with(|keys| { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
use super::{BorrowedBuf, BorrowedCursor, Read, Result}; | ||
use crate::sys::entropy as sys; | ||
|
||
/// A reader which returns random bytes from the system entropy source. | ||
/// | ||
/// This struct is generally created by calling [`entropy()`]. Please | ||
/// see the documentation of [`entropy()`] for more details. | ||
#[derive(Debug)] | ||
#[unstable(feature = "io_entropy", issue = "none")] | ||
pub struct Entropy { | ||
insecure: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could probably use |
||
} | ||
|
||
impl Entropy { | ||
pub(crate) fn set_insecure(&mut self, insecure: bool) { | ||
self.insecure = insecure; | ||
} | ||
} | ||
|
||
#[unstable(feature = "io_entropy", issue = "none")] | ||
impl Read for Entropy { | ||
#[inline] | ||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
sys::Entropy { insecure: self.insecure }.read(buf) | ||
} | ||
|
||
#[inline] | ||
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { | ||
let mut buf = BorrowedBuf::from(buf); | ||
self.read_buf_exact(buf.unfilled()) | ||
} | ||
|
||
#[inline] | ||
fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> { | ||
sys::Entropy { insecure: self.insecure }.read_buf(buf) | ||
} | ||
|
||
#[inline] | ||
fn read_buf_exact(&mut self, buf: BorrowedCursor<'_>) -> Result<()> { | ||
sys::Entropy { insecure: self.insecure }.read_buf_exact(buf) | ||
} | ||
} | ||
|
||
/// Constructs a new handle to the system entropy source. | ||
/// | ||
/// Reads from the resulting reader will return high-quality random data that | ||
/// is suited for cryptographic purposes (by the standard of the platform defaults). | ||
/// | ||
/// Be aware that, because the data is of very high quality, reading high amounts | ||
/// of data can be very slow, and potentially slow down other processes requiring | ||
/// random data. Use a pseudo-random number generator if speed is important. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we do this elsewhere in libstd with other crates, perhaps we could just straight-up recommend the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have been trying to not advertise external crates from std docs. And there are other potential options beside |
||
/// | ||
/// # Platform sources | ||
/// | ||
/// | OS | Source | ||
/// |------------------|-------- | ||
/// | Linux, Android | [`getrandom`][1] if available, otherwise [`/dev/urandom`][2] after successfully polling [`/dev/random`][2] | ||
/// | Windows | [`BCryptGenRandom`][3], falling back to [`RtlGenRandom`][4] | ||
/// | macOS | [`getentropy`][5] if available, falling back to [`/dev/urandom`][6] | ||
/// | OpenBSD | [`getentropy`][7] | ||
/// | iOS, watchOS | [`SecRandomCopyBytes`][8] | ||
/// | FreeBSD | [`kern.arandom`][9] | ||
/// | NetBSD | [`kern.arandom`][10] | ||
/// | Fuchsia | [`zx_cprng_draw`][11] | ||
/// | WASM | *Unsupported* | ||
/// | WASI | [`random_get`][12] | ||
/// | Emscripten | [`getentropy`][7] | ||
/// | Redox | `rand:` | ||
/// | VxWorks | `randABytes` after checking entropy pool initialization with `randSecure` | ||
/// | Haiku | `/dev/urandom` | ||
/// | ESP-IDF, Horizon | [`getrandom`][1] | ||
/// | Other UNIXes | `/dev/random` | ||
/// | Hermit | [`read_entropy`][13] | ||
/// | SGX | [`RDRAND`][14] | ||
/// | SOLID | `SOLID_RNG_SampleRandomBytes` | ||
/// | ||
/// [1]: https://man7.org/linux/man-pages/man2/getrandom.2.html | ||
/// [2]: https://man7.org/linux/man-pages/man7/random.7.html | ||
/// [3]: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom | ||
/// [4]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom | ||
/// [5]: https://www.unix.com/man-page/mojave/2/getentropy/ | ||
/// [6]: https://www.unix.com/man-page/mojave/4/random/ | ||
/// [7]: https://man.openbsd.org/getentropy.2 | ||
/// [8]: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc | ||
/// [9]: https://man.freebsd.org/cgi/man.cgi?query=random&sektion=4&manpath=FreeBSD+13.1-RELEASE+and+Ports | ||
/// [10]: https://man.netbsd.org/rnd.4 | ||
/// [11]: https://fuchsia.dev/fuchsia-src/reference/syscalls/cprng_draw | ||
/// [12]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md | ||
/// [13]: https://docs.rs/hermit-abi/latest/hermit_abi/fn.read_entropy.html | ||
/// [14]: https://www.intel.com/content/www/us/en/developer/articles/guide/intel-digital-random-number-generator-drng-software-implementation-guide.html | ||
/// | ||
/// # Examples | ||
/// | ||
/// Generating a seed for a random number generator: | ||
/// | ||
/// ```rust | ||
/// #![feature(io_entropy)] | ||
/// | ||
/// # use std::io::Result; | ||
/// # fn main() -> Result<()> { | ||
/// use std::io::{entropy, Read}; | ||
/// | ||
/// let mut seed = [0u8; 32]; | ||
/// entropy().read_exact(&mut seed)?; | ||
/// println!("seed: {seed:?}"); | ||
/// # Ok(()) | ||
/// # } | ||
/// ``` | ||
/// | ||
/// Implementing your very own `/dev/random`: | ||
/// | ||
/// ```rust, no_run | ||
/// #![feature(io_entropy)] | ||
/// | ||
/// use std::io::{copy, entropy, stdout}; | ||
/// | ||
/// fn main() { | ||
/// let _ = copy(&mut entropy(), &mut stdout()); | ||
/// } | ||
/// ``` | ||
#[inline] | ||
#[unstable(feature = "io_entropy", issue = "none")] | ||
pub fn entropy() -> Entropy { | ||
Entropy { insecure: false } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
use super::{abi, cvt}; | ||
use crate::io::{default_read, BorrowedCursor, Read, Result}; | ||
|
||
pub const INSECURE_HASHMAP: bool = false; | ||
|
||
pub struct Entropy { | ||
pub insecure: bool, | ||
} | ||
|
||
impl Read for Entropy { | ||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
default_read(self, buf) | ||
} | ||
|
||
fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> Result<()> { | ||
unsafe { | ||
let len = cvt(abi::read_entropy(buf.as_ptr(), buf.capacity(), 0))?; | ||
buf.advance(len as usize); | ||
Ok(()) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
use crate::cmp; | ||
use crate::io::{Error as IoError, ErrorKind, IoSlice, IoSliceMut, Result as IoResult}; | ||
use crate::sys::rand::rdrand64; | ||
use crate::sys::entropy::rdrand64; | ||
use crate::time::{Duration, Instant}; | ||
|
||
pub(crate) mod alloc; | ||
|
@@ -164,7 +164,8 @@ pub fn wait(event_mask: u64, mut timeout: u64) -> IoResult<u64> { | |
// trusted to ensure accurate timeouts. | ||
if let Ok(timeout_signed) = i64::try_from(timeout) { | ||
let tenth = timeout_signed / 10; | ||
let deviation = (rdrand64() as i64).checked_rem(tenth).unwrap_or(0); | ||
let rand = rdrand64().unwrap_or_else(|| rtabort!("Failed to obtain random data")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: There's no need to abort here when |
||
let deviation = (rand as i64).checked_rem(tenth).unwrap_or(0); | ||
timeout = timeout_signed.saturating_add(deviation) as _; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use crate::io::{const_io_error, default_read, BorrowedCursor, ErrorKind, Read, Result}; | ||
|
||
pub const INSECURE_HASHMAP: bool = false; | ||
|
||
pub struct Entropy { | ||
pub insecure: bool, | ||
} | ||
|
||
impl Read for Entropy { | ||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
default_read(self, buf) | ||
} | ||
|
||
fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> Result<()> { | ||
if buf.capacity() != 0 { | ||
let rand = rdrand64() | ||
.ok_or(const_io_error!(ErrorKind::WouldBlock, "no random data available"))?; | ||
buf.append(&rand.to_ne_bytes()[..usize::min(buf.capacity(), 8)]); | ||
Ok(()) | ||
} else { | ||
Ok(()) | ||
} | ||
} | ||
} | ||
|
||
pub(super) fn rdrand64() -> Option<u64> { | ||
unsafe { | ||
let mut ret: u64 = 0; | ||
for _ in 0..10 { | ||
if crate::arch::x86_64::_rdrand64_step(&mut ret) == 1 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a maintainer of sgx but this does not offer high quality entropy. See the "Generating seeds with rdrand" section in https://www.intel.com/content/www/us/en/developer/articles/guide/intel-digital-random-number-generator-drng-software-implementation-guide.html for how to do it (which we probably don't want in std...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Edit: Ah, I missed the part about forward and backward prediction resistance... Still, I don't know if it's really, truly necessary here. I didn't change this, because If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think that sums it up nicely. If someone needs stronger guarantees for a PRNG, they should take a look at what exactly happens in Indeed, AMD hardware bugs have no impact on SGX and the values There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense to me. Intel documentation looks clear, and there is useful discussion on security levels here. |
||
return Some(ret); | ||
} | ||
} | ||
None | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use super::abi; | ||
use super::error::SolidError; | ||
use crate::io::{default_read, BorrowedCursor, Read, Result}; | ||
|
||
pub const INSECURE_HASHMAP: bool = false; | ||
|
||
pub struct Entropy { | ||
pub insecure: bool, | ||
} | ||
|
||
impl Read for Entropy { | ||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
default_read(self, buf) | ||
} | ||
|
||
fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> Result<()> { | ||
SolidError::err_if_negative(unsafe { | ||
abi::SOLID_RNG_SampleRandomBytes(buf.as_mut().as_mut_ptr().cast(), buf.capacity()) | ||
}) | ||
.map_err(|e| e.as_io_error())?; | ||
|
||
unsafe { | ||
buf.advance(buf.capacity()); | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn read_buf_exact(&mut self, buf: BorrowedCursor<'_>) -> Result<()> { | ||
self.read_buf(buf) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not blocking, but one thing I was thinking of proposing as a path to make
RandomState
still work without libstd is allowing users to provide their own entropy function, and it would be nice if this were factored in a way that always took that path, rather than hard-coding in an insecure constant for the state.Since no one is going to rely explicitly on the (1, 2) state for these cases by design, I think it'd be better to just provide a random eight bytes that get sent through the path instead. That way everything goes through the "entropy" path of decoding the bytes, even if the bytes are static.