From 970526c198167ea1a9162011eadca95ee2c7478c Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Tue, 14 Mar 2023 08:56:40 -0700 Subject: [PATCH 1/2] Use system entropy to initialize hashmap keys --- block.md | 3 +- library/std/src/sys/postgres/common.rs | 5 - library/std/src/sys/postgres/mod.rs | 3 + library/std/src/sys/postgres/rand.rs | 141 +++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 library/std/src/sys/postgres/rand.rs diff --git a/block.md b/block.md index 885d9ac75..41d1a5d8b 100644 --- a/block.md +++ b/block.md @@ -3,6 +3,8 @@ Rust `std` modules with impacted functionality: - Technically available but in practice unusable (it is almost entirely `unsafe`) - backtrace - Support for capturing a stack backtrace of an OS thread - Backtraces are currently always disabled. +- collection - Collection types. + - `HashMap`'s default hasher (specifically `RandomState`) may panic if it fails to access randomness for its seed (note that this is the case for the `HashMap` in normal `std` as well). Users who hit this may be better off using `BTreeMap` instead. - env - Inspection and manipulation of the process’s environment. - May panic, return `Err("unsupported operation")`, or have arbitrary results. - fs - Filesystem manipulation operations. @@ -43,7 +45,6 @@ Other Rust `std` modules: - cell - Shareable mutable containers. - clone - The Clone trait for types that cannot be ‘implicitly copied’. - cmp - Utilities for comparing and ordering values. -- collection - Collection types. - convert - Traits for conversions between types. - default - The Default trait for types with a default value. - error - Interfaces for working with Errors. diff --git a/library/std/src/sys/postgres/common.rs b/library/std/src/sys/postgres/common.rs index 71dd6e9dd..b9aa4c269 100644 --- a/library/std/src/sys/postgres/common.rs +++ b/library/std/src/sys/postgres/common.rs @@ -96,8 +96,3 @@ pub fn abort_internal() -> ! { // these anyway, and aborting is at least a clean exit. core::intrinsics::abort(); } - -pub fn hashmap_random_keys() -> (u64, u64) { - // FIXME: Can this lead to HashDOS? - (1, 2) -} diff --git a/library/std/src/sys/postgres/mod.rs b/library/std/src/sys/postgres/mod.rs index dd5ce5f63..6c02dba40 100644 --- a/library/std/src/sys/postgres/mod.rs +++ b/library/std/src/sys/postgres/mod.rs @@ -19,6 +19,7 @@ pub mod os_str; pub mod path; pub mod pipe; pub mod process; +pub mod rand; pub mod stdio; pub mod thread; #[cfg(target_thread_local)] @@ -26,6 +27,8 @@ pub mod thread_local_dtor; pub mod thread_local_key; pub mod time; +pub use rand::hashmap_random_keys; + mod common; pub use common::*; diff --git a/library/std/src/sys/postgres/rand.rs b/library/std/src/sys/postgres/rand.rs new file mode 100644 index 000000000..9a74a8cd3 --- /dev/null +++ b/library/std/src/sys/postgres/rand.rs @@ -0,0 +1,141 @@ +/// Mostly based on `sys/unix/rand.rs`, although that impl can't really be used +/// because: +/// 1. We've killed `File`'s ability to actually read from the system. +/// 2. Our `errno()` function is a liar, and `real_errno_use_carefully()` must +/// be used instead. +/// +/// On the bright side, we don't support versions of Linux without `getrandom` +/// or versions of macOS without `getentropy`. This doesn't actually help much +/// in practice though, since the fallback `/dev/urandom` path is hit in cases +/// like early boot and whatnot. Note that we're single threaded so +/// `hashmap_random_keys` is not called multiple times, the cache in +/// `RandomState`'s thread local is enough for us. This does not simplify very +/// much for us, sadly. +/// +/// Technically we could also use `pg_strong_random` to implement this. This +/// would be less code, but we'd be screwed if it ever fired a PG error (we +/// can't use pgx-pg-sys's sjlj catching). The current impl of +/// `pg_strong_random` doesn't seem to ever do that, but given that it *is* a +/// fallible function, I'm not that comfortable with relying on that. + +pub fn hashmap_random_keys() -> (u64, u64) { + const KEY_LEN: usize = core::mem::size_of::(); + + let mut v = [0u8; KEY_LEN * 2]; + if !try_fill_bytes(&mut v) { + if !urandom_fill_bytes(&mut v) { + let syscall = if cfg!(target_os = "linux") { "getrandom" } else { "getentropy" }; + // Panic to inform the user that they've misconfigured something, + // rather than falling back to something insecure. + panic!("failed to initialize hashmap keys: could not use {syscall} or /dev/urandom"); + } + } + + let key1 = v[0..KEY_LEN].try_into().unwrap(); + let key2 = v[KEY_LEN..].try_into().unwrap(); + + (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2)) +} + +#[cfg(target_os = "linux")] +fn try_fill_bytes(v: &mut [u8]) -> bool { + use crate::sync::atomic::{AtomicBool, Ordering}; + use crate::sys::os::real_errno_use_carefully; + + let mut read = 0; + while read < v.len() { + let result = getrandom(&mut v[read..]); + if result == -1 { + let err = real_errno_use_carefully() as libc::c_int; + if err == libc::EINTR { + continue; + } else if err == libc::ENOSYS || err == libc::EPERM || err == libc::EAGAIN { + // Don't bother remembering, we only come here once in + // postgrestd anyway (no threads). + return false; + } + } else { + read += result as usize; + } + } + true +} + +#[cfg(target_os = "linux")] +fn getrandom(buf: &mut [u8]) -> libc::ssize_t { + use super::weak::syscall; + use crate::sync::atomic::{AtomicBool, Ordering}; + use crate::sys::os::real_errno_use_carefully; + + // A weak symbol allows interposition, e.g. for perf measurements that want to + // disable randomness for consistency. Otherwise, we'll try a raw syscall. + // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28) + syscall! { + fn getrandom( + buffer: *mut libc::c_void, + length: libc::size_t, + flags: libc::c_uint + ) -> libc::ssize_t + } + + // This provides the best quality random numbers available at the given moment + // without ever blocking, and is preferable to falling back to /dev/urandom. + static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true); + if GRND_INSECURE_AVAILABLE.load(Ordering::Relaxed) { + let ret = unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_INSECURE) }; + if ret == -1 && (real_errno_use_carefully() as libc::c_int) == libc::EINVAL { + GRND_INSECURE_AVAILABLE.store(false, Ordering::Relaxed); + } else { + return ret; + } + } + + unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_NONBLOCK) } +} + +#[cfg(target_os = "macos")] +fn try_fill_bytes(v: &mut [u8]) -> bool { + use crate::sys::os::real_errno_use_carefully; + extern "C" { + // Present on macOS 10.12+ + fn getentropy(p: *mut libc::c_void, sz: libc::size_t) -> libc::c_int; + } + for chunk in v.chunks_mut(256) { + if unsafe { getentropy(chunk.as_ptr(), chunk.len()) } == -1 { + return false; + } + } + true +} + +// Unfortunately even if we only support kernel versions that have getentropy, +// we may have to fall back to `/dev/urandom` when either seccomp has disabled +// the syscall, or if it's early boot and the kernel doesn't have (the +// relatively recent) `GRND_INSECURE` flag. +fn urandom_fill_bytes(v: &mut [u8]) -> bool { + use crate::sys::os::real_errno_use_carefully; + // Can't use `File` for any of this, because we've disabled `File`'s ability to do I/O. + let fd = + unsafe { libc::open(b"/dev/urandom\0".as_ptr().cast::(), libc::O_RDONLY, 0) }; + if fd < 0 { + return false; + } + + let mut read = 0; + while read < v.len() { + let remaining = &mut v[read..]; + let result = unsafe { libc::read(fd, remaining.as_mut_ptr().cast(), remaining.len()) }; + if result <= 0 { + if real_errno_use_carefully() == libc::EINTR { + continue; + } + break; + } else { + read += result as usize; + } + } + unsafe { + libc::close(fd); + } + true +} From 8cc78ca32ea1a05021b0b8a8fb040c611e796883 Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Tue, 14 Mar 2023 09:28:29 -0700 Subject: [PATCH 2/2] Fix macOS missing cast --- library/std/src/sys/postgres/rand.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/postgres/rand.rs b/library/std/src/sys/postgres/rand.rs index 9a74a8cd3..a9001d45b 100644 --- a/library/std/src/sys/postgres/rand.rs +++ b/library/std/src/sys/postgres/rand.rs @@ -101,7 +101,7 @@ fn try_fill_bytes(v: &mut [u8]) -> bool { fn getentropy(p: *mut libc::c_void, sz: libc::size_t) -> libc::c_int; } for chunk in v.chunks_mut(256) { - if unsafe { getentropy(chunk.as_ptr(), chunk.len()) } == -1 { + if unsafe { getentropy(chunk.as_mut_ptr().cast::(), chunk.len()) } == -1 { return false; } }