about summary refs log tree commit diff
path: root/library/std/src/sys/random
diff options
context:
space:
mode:
authorjoboet <jonasboettiger@icloud.com>2024-08-15 13:28:02 +0200
committerjoboet <jonasboettiger@icloud.com>2024-09-23 10:29:51 +0200
commit5c1c72572479afe98734d5f78fa862abe662c41a (patch)
tree4f43788d776b0b1dfc423ef8d9852ece3960f9b3 /library/std/src/sys/random
parent702987f75b74f789ba227ee04a3d7bb1680c2309 (diff)
downloadrust-5c1c72572479afe98734d5f78fa862abe662c41a.tar.gz
rust-5c1c72572479afe98734d5f78fa862abe662c41a.zip
std: implement the `random` feature
Implements the ACP https://github.com/rust-lang/libs-team/issues/393.
Diffstat (limited to 'library/std/src/sys/random')
-rw-r--r--library/std/src/sys/random/apple.rs22
-rw-r--r--library/std/src/sys/random/espidf.rs9
-rw-r--r--library/std/src/sys/random/fuchsia.rs13
-rw-r--r--library/std/src/sys/random/hermit.rs7
-rw-r--r--library/std/src/sys/random/horizon.rs7
-rw-r--r--library/std/src/sys/random/linux.rs170
-rw-r--r--library/std/src/sys/random/mod.rs98
-rw-r--r--library/std/src/sys/random/netbsd.rs19
-rw-r--r--library/std/src/sys/random/redox.rs12
-rw-r--r--library/std/src/sys/random/sgx.rs67
-rw-r--r--library/std/src/sys/random/solid.rs8
-rw-r--r--library/std/src/sys/random/teeos.rs7
-rw-r--r--library/std/src/sys/random/uefi.rs27
-rw-r--r--library/std/src/sys/random/unix.rs33
-rw-r--r--library/std/src/sys/random/unix_legacy.rs20
-rw-r--r--library/std/src/sys/random/unsupported.rs15
-rw-r--r--library/std/src/sys/random/vxworks.rs25
-rw-r--r--library/std/src/sys/random/wasi.rs5
-rw-r--r--library/std/src/sys/random/windows.rs20
-rw-r--r--library/std/src/sys/random/zkvm.rs21
20 files changed, 605 insertions, 0 deletions
diff --git a/library/std/src/sys/random/apple.rs b/library/std/src/sys/random/apple.rs
new file mode 100644
index 00000000000..09b6d0d51ab
--- /dev/null
+++ b/library/std/src/sys/random/apple.rs
@@ -0,0 +1,22 @@
+//! Random data on non-macOS Apple platforms.
+//!
+//! Apple recommends the usage of `getentropy` in their security documentation[^1]
+//! and mark it as being available in iOS 10.0, but we cannot use it on non-macOS
+//! platforms as Apple in their *infinite wisdom* decided to consider this API
+//! private, meaning its use will lead to App Store rejections (see #102643).
+//!
+//! Thus, we need to do the next best thing:
+//!
+//! Both `CCRandomGenerateBytes` and `SecRandomCopyBytes` simply call into
+//! `CCRandomCopyBytes` with `kCCRandomDefault`. `CCRandomCopyBytes` manages a
+//! CSPRNG which is seeded from the kernel's CSPRNG and which runs on its own
+//! thread accessed via GCD (this is so wasteful...). Both are available on
+//! iOS, but we use `CCRandomGenerateBytes` because it is accessible via
+//! `libSystem` (libc) while the other needs to link to `Security.framework`.
+//!
+//! [^1]: <https://support.apple.com/en-gb/guide/security/seca0c73a75b/web>
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let ret = unsafe { libc::CCRandomGenerateBytes(bytes.as_mut_ptr().cast(), bytes.len()) };
+    assert_eq!(ret, libc::kCCSuccess, "failed to generate random data");
+}
diff --git a/library/std/src/sys/random/espidf.rs b/library/std/src/sys/random/espidf.rs
new file mode 100644
index 00000000000..fd52cb5559c
--- /dev/null
+++ b/library/std/src/sys/random/espidf.rs
@@ -0,0 +1,9 @@
+use crate::ffi::c_void;
+
+extern "C" {
+    fn esp_fill_random(buf: *mut c_void, len: usize);
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe { esp_fill_random(bytes.as_mut_ptr().cast(), bytes.len()) }
+}
diff --git a/library/std/src/sys/random/fuchsia.rs b/library/std/src/sys/random/fuchsia.rs
new file mode 100644
index 00000000000..77d72b3c5b7
--- /dev/null
+++ b/library/std/src/sys/random/fuchsia.rs
@@ -0,0 +1,13 @@
+//! Random data generation using the Zircon kernel.
+//!
+//! Fuchsia, as always, is quite nice and provides exactly the API we need:
+//! <https://fuchsia.dev/reference/syscalls/cprng_draw>.
+
+#[link(name = "zircon")]
+extern "C" {
+    fn zx_cprng_draw(buffer: *mut u8, len: usize);
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe { zx_cprng_draw(bytes.as_mut_ptr(), bytes.len()) }
+}
diff --git a/library/std/src/sys/random/hermit.rs b/library/std/src/sys/random/hermit.rs
new file mode 100644
index 00000000000..92c0550d2d5
--- /dev/null
+++ b/library/std/src/sys/random/hermit.rs
@@ -0,0 +1,7 @@
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !bytes.is_empty() {
+        let res = unsafe { hermit_abi::read_entropy(bytes.as_mut_ptr(), bytes.len(), 0) };
+        assert_ne!(res, -1, "failed to generate random data");
+        bytes = &mut bytes[res as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/horizon.rs b/library/std/src/sys/random/horizon.rs
new file mode 100644
index 00000000000..0be2eae20a7
--- /dev/null
+++ b/library/std/src/sys/random/horizon.rs
@@ -0,0 +1,7 @@
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !bytes.is_empty() {
+        let r = unsafe { libc::getrandom(bytes.as_mut_ptr().cast(), bytes.len(), 0) };
+        assert_ne!(r, -1, "failed to generate random data");
+        bytes = &mut bytes[r as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/linux.rs b/library/std/src/sys/random/linux.rs
new file mode 100644
index 00000000000..4ede0af6494
--- /dev/null
+++ b/library/std/src/sys/random/linux.rs
@@ -0,0 +1,170 @@
+//! Random data generation with the Linux kernel.
+//!
+//! The first interface random data interface to be introduced on Linux were
+//! the `/dev/random` and `/dev/urandom` special files. As paths can become
+//! unreachable when inside a chroot and when the file descriptors are exhausted,
+//! this was not enough to provide userspace with a reliable source of randomness,
+//! so when the OpenBSD 5.6 introduced the `getentropy` syscall, Linux 3.17 got
+//! its very own `getrandom`  syscall to match.[^1] Unfortunately, even if our
+//! minimum supported version were high enough, we still couldn't rely on the
+//! syscall being available, as it is blocked in `seccomp` by default.
+//!
+//! The question is therefore which of the random sources to use. Historically,
+//! the kernel contained two pools: the blocking and non-blocking pool. The
+//! blocking pool used entropy estimation to limit the amount of available
+//! bytes, while the non-blocking pool, once initialized using the blocking
+//! pool, uses a CPRNG to return an unlimited number of random bytes. With a
+//! strong enough CPRNG however, the entropy estimation didn't contribute that
+//! much towards security while being an excellent vector for DoS attacs. Thus,
+//! the blocking pool was removed in kernel version 5.6.[^2] That patch did not
+//! magically increase the quality of the non-blocking pool, however, so we can
+//! safely consider it strong enough even in older kernel versions and use it
+//! unconditionally.
+//!
+//! One additional consideration to make is that the non-blocking pool is not
+//! always initialized during early boot. We want the best quality of randomness
+//! for the output of `DefaultRandomSource` so we simply wait until it is
+//! initialized. When `HashMap` keys however, this represents a potential source
+//! of deadlocks, as the additional entropy may only be generated once the
+//! program makes forward progress. In that case, we just use the best random
+//! data the system has available at the time.
+//!
+//! So in conclusion, we always want the output of the non-blocking pool, but
+//! may need to wait until it is initalized. The default behaviour of `getrandom`
+//! is to wait until the non-blocking pool is initialized and then draw from there,
+//! so if `getrandom` is available, we use its default to generate the bytes. For
+//! `HashMap`, however, we need to specify the `GRND_INSECURE` flags, but that
+//! is only available starting with kernel version 5.6. Thus, if we detect that
+//! the flag is unsupported, we try `GRND_NONBLOCK` instead, which will only
+//! succeed if the pool is initialized. If it isn't, we fall back to the file
+//! access method.
+//!
+//! The behaviour of `/dev/urandom` is inverse to that of `getrandom`: it always
+//! yields data, even when the pool is not initialized. For generating `HashMap`
+//! keys, this is not important, so we can use it directly. For secure data
+//! however, we need to wait until initialization, which we can do by `poll`ing
+//! `/dev/random`.
+//!
+//! TLDR: our fallback strategies are:
+//!
+//! Secure data                                 | `HashMap` keys
+//! --------------------------------------------|------------------
+//! getrandom(0)                                | getrandom(GRND_INSECURE)
+//! poll("/dev/random") && read("/dev/urandom") | getrandom(GRND_NONBLOCK)
+//!                                             | read("/dev/urandom")
+//!
+//! [^1]: <https://lwn.net/Articles/606141/>
+//! [^2]: <https://lwn.net/Articles/808575/>
+//!
+// FIXME(in 2040 or so): once the minimum kernel version is 5.6, remove the
+// `GRND_NONBLOCK` fallback and use `/dev/random` instead of `/dev/urandom`
+// when secure data is required.
+
+use crate::fs::File;
+use crate::io::Read;
+use crate::os::fd::AsRawFd;
+use crate::sync::atomic::AtomicBool;
+use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
+use crate::sync::OnceLock;
+use crate::sys::pal::os::errno;
+use crate::sys::pal::weak::syscall;
+
+fn getrandom(mut bytes: &mut [u8], insecure: bool) {
+    // 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
+    }
+
+    static GETRANDOM_AVAILABLE: AtomicBool = AtomicBool::new(true);
+    static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true);
+    static URANDOM_READY: AtomicBool = AtomicBool::new(false);
+    static DEVICE: OnceLock<File> = OnceLock::new();
+
+    if GETRANDOM_AVAILABLE.load(Relaxed) {
+        loop {
+            if bytes.is_empty() {
+                return;
+            }
+
+            let flags = if insecure {
+                if GRND_INSECURE_AVAILABLE.load(Relaxed) {
+                    libc::GRND_INSECURE
+                } else {
+                    libc::GRND_NONBLOCK
+                }
+            } else {
+                0
+            };
+
+            let ret = unsafe { getrandom(bytes.as_mut_ptr().cast(), bytes.len(), flags) };
+            if ret != -1 {
+                bytes = &mut bytes[ret as usize..];
+            } else {
+                match errno() {
+                    libc::EINTR => continue,
+                    // `GRND_INSECURE` is not available, try
+                    // `GRND_NONBLOCK`.
+                    libc::EINVAL if flags == libc::GRND_INSECURE => {
+                        GRND_INSECURE_AVAILABLE.store(false, Relaxed);
+                        continue;
+                    }
+                    // The pool is not initialized yet, fall back to
+                    // /dev/urandom for now.
+                    libc::EAGAIN if flags == libc::GRND_NONBLOCK => break,
+                    // `getrandom` is unavailable or blocked by seccomp.
+                    // Don't try it again and fall back to /dev/urandom.
+                    libc::ENOSYS | libc::EPERM => {
+                        GETRANDOM_AVAILABLE.store(false, Relaxed);
+                        break;
+                    }
+                    _ => panic!("failed to generate random data"),
+                }
+            }
+        }
+    }
+
+    // When we want cryptographic strength, we need to wait for the CPRNG-pool
+    // to become initialized. Do this by polling `/dev/random` until it is ready.
+    if !insecure {
+        if !URANDOM_READY.load(Acquire) {
+            let random = File::open("/dev/random").expect("failed to open /dev/random");
+            let mut fd = libc::pollfd { fd: random.as_raw_fd(), events: libc::POLLIN, revents: 0 };
+
+            while !URANDOM_READY.load(Acquire) {
+                let ret = unsafe { libc::poll(&mut fd, 1, -1) };
+                match ret {
+                    1 => {
+                        assert_eq!(fd.revents, libc::POLLIN);
+                        URANDOM_READY.store(true, Release);
+                        break;
+                    }
+                    -1 if errno() == libc::EINTR => continue,
+                    _ => panic!("poll(\"/dev/random\") failed"),
+                }
+            }
+        }
+    }
+
+    DEVICE
+        .get_or_try_init(|| File::open("/dev/urandom"))
+        .and_then(|mut dev| dev.read_exact(bytes))
+        .expect("failed to generate random data");
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    getrandom(bytes, false);
+}
+
+pub fn hashmap_random_keys() -> (u64, u64) {
+    let mut bytes = [0; 16];
+    getrandom(&mut bytes, true);
+    let k1 = u64::from_ne_bytes(bytes[..8].try_into().unwrap());
+    let k2 = u64::from_ne_bytes(bytes[8..].try_into().unwrap());
+    (k1, k2)
+}
diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs
new file mode 100644
index 00000000000..d39e78bb7b3
--- /dev/null
+++ b/library/std/src/sys/random/mod.rs
@@ -0,0 +1,98 @@
+cfg_if::cfg_if! {
+    // Tier 1
+    if #[cfg(any(target_os = "linux", target_os = "android"))] {
+        mod linux;
+        pub use linux::{fill_bytes, hashmap_random_keys};
+    } else if #[cfg(target_os = "windows")] {
+        mod windows;
+        pub use windows::fill_bytes;
+    } else if #[cfg(any(
+        target_os = "openbsd",
+        target_os = "freebsd",
+        target_os = "macos",
+        all(target_os = "netbsd", netbsd10),
+        target_os = "dragonfly",
+        target_os = "illumos",
+        target_os = "solaris",
+        target_os = "emscripten",
+        target_os = "vita",
+        target_os = "haiku",
+    ))] {
+        mod unix;
+        pub use unix::fill_bytes;
+    // Others, in alphabetical ordering.
+    } else if #[cfg(all(target_vendor = "apple", not(target_os = "macos")))] {
+        mod apple;
+        pub use apple::fill_bytes;
+    } else if #[cfg(target_os = "espidf")] {
+        mod espidf;
+        pub use espidf::fill_bytes;
+    } else if #[cfg(target_os = "fuchsia")] {
+        mod fuchsia;
+        pub use fuchsia::fill_bytes;
+    } else if #[cfg(target_os = "hermit")] {
+        mod hermit;
+        pub use hermit::fill_bytes;
+    } else if #[cfg(target_os = "horizon")] {
+        // FIXME: add getentropy to shim-3ds
+        mod horizon;
+        pub use horizon::fill_bytes;
+    } else if #[cfg(any(
+        target_os = "hurd",
+        target_os = "l4re",
+        target_os = "nto",
+    ))] {
+        mod unix_legacy;
+        pub use unix_legacy::fill_bytes;
+    } else if #[cfg(all(target_os = "netbsd", not(netbsd10)))] {
+        // FIXME: remove once NetBSD 10 is the minimum
+        mod netbsd;
+        pub use netbsd::fill_bytes;
+    } else if #[cfg(target_os = "redox")] {
+        mod redox;
+        pub use redox::fill_bytes;
+    } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
+        mod sgx;
+        pub use sgx::fill_bytes;
+    } else if #[cfg(target_os = "solid_asp3")] {
+        mod solid;
+        pub use solid::fill_bytes;
+    } else if #[cfg(target_os = "teeos")] {
+        mod teeos;
+        pub use teeos::fill_bytes;
+    } else if #[cfg(target_os = "uefi")] {
+        mod uefi;
+        pub use uefi::fill_bytes;
+    } else if #[cfg(target_os = "vxworks")] {
+        mod vxworks;
+        pub use vxworks::fill_bytes;
+    } else if #[cfg(target_os = "wasi")] {
+        mod wasi;
+        pub use wasi::fill_bytes;
+    } else if #[cfg(target_os = "zkvm")] {
+        mod zkvm;
+        pub use zkvm::fill_bytes;
+    } else if #[cfg(any(
+        all(target_family = "wasm", target_os = "unknown"),
+        target_os = "xous",
+    ))] {
+        // FIXME: finally remove std support for wasm32-unknown-unknown
+        // FIXME: add random data generation to xous
+        mod unsupported;
+        pub use unsupported::{fill_bytes, hashmap_random_keys};
+    }
+}
+
+#[cfg(not(any(
+    target_os = "linux",
+    target_os = "android",
+    all(target_family = "wasm", target_os = "unknown"),
+    target_os = "xous",
+)))]
+pub fn hashmap_random_keys() -> (u64, u64) {
+    let mut buf = [0; 16];
+    fill_bytes(&mut buf);
+    let k1 = u64::from_ne_bytes(buf[..8].try_into().unwrap());
+    let k2 = u64::from_ne_bytes(buf[8..].try_into().unwrap());
+    (k1, k2)
+}
diff --git a/library/std/src/sys/random/netbsd.rs b/library/std/src/sys/random/netbsd.rs
new file mode 100644
index 00000000000..2c5d9c72f30
--- /dev/null
+++ b/library/std/src/sys/random/netbsd.rs
@@ -0,0 +1,19 @@
+use crate::ptr;
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let mib = [libc::CTL_KERN, libc::KERN_ARND];
+    for chunk in bytes.chunks_mut(256) {
+        let mut len = chunk.len();
+        let ret = unsafe {
+            libc::sysctl(
+                mib.as_ptr(),
+                mib.len() as libc::c_uint,
+                chunk.as_mut_ptr().cast(),
+                &mut len,
+                ptr::null(),
+                0,
+            )
+        };
+        assert!(ret != -1 && len == chunk.len(), "failed to generate random data");
+    }
+}
diff --git a/library/std/src/sys/random/redox.rs b/library/std/src/sys/random/redox.rs
new file mode 100644
index 00000000000..b004335a351
--- /dev/null
+++ b/library/std/src/sys/random/redox.rs
@@ -0,0 +1,12 @@
+use crate::fs::File;
+use crate::io::Read;
+use crate::sync::OnceLock;
+
+static SCHEME: OnceLock<File> = OnceLock::new();
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    SCHEME
+        .get_or_try_init(|| File::open("/scheme/rand"))
+        .and_then(|mut scheme| scheme.read_exact(bytes))
+        .expect("failed to generate random data");
+}
diff --git a/library/std/src/sys/random/sgx.rs b/library/std/src/sys/random/sgx.rs
new file mode 100644
index 00000000000..c3647a8df22
--- /dev/null
+++ b/library/std/src/sys/random/sgx.rs
@@ -0,0 +1,67 @@
+use crate::arch::x86_64::{_rdrand16_step, _rdrand32_step, _rdrand64_step};
+
+const RETRIES: u32 = 10;
+
+fn fail() -> ! {
+    panic!("failed to generate random data");
+}
+
+fn rdrand64() -> u64 {
+    unsafe {
+        let mut ret: u64 = 0;
+        for _ in 0..RETRIES {
+            if _rdrand64_step(&mut ret) == 1 {
+                return ret;
+            }
+        }
+
+        fail();
+    }
+}
+
+fn rdrand32() -> u32 {
+    unsafe {
+        let mut ret: u32 = 0;
+        for _ in 0..RETRIES {
+            if _rdrand32_step(&mut ret) == 1 {
+                return ret;
+            }
+        }
+
+        fail();
+    }
+}
+
+fn rdrand16() -> u16 {
+    unsafe {
+        let mut ret: u16 = 0;
+        for _ in 0..RETRIES {
+            if _rdrand16_step(&mut ret) == 1 {
+                return ret;
+            }
+        }
+
+        fail();
+    }
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let mut chunks = bytes.array_chunks_mut();
+    for chunk in &mut chunks {
+        *chunk = rdrand64().to_ne_bytes();
+    }
+
+    let mut chunks = chunks.into_remainder().array_chunks_mut();
+    for chunk in &mut chunks {
+        *chunk = rdrand32().to_ne_bytes();
+    }
+
+    let mut chunks = chunks.into_remainder().array_chunks_mut();
+    for chunk in &mut chunks {
+        *chunk = rdrand16().to_ne_bytes();
+    }
+
+    if let [byte] = chunks.into_remainder() {
+        *byte = rdrand16() as u8;
+    }
+}
diff --git a/library/std/src/sys/random/solid.rs b/library/std/src/sys/random/solid.rs
new file mode 100644
index 00000000000..545771150e2
--- /dev/null
+++ b/library/std/src/sys/random/solid.rs
@@ -0,0 +1,8 @@
+use crate::sys::pal::abi;
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe {
+        let result = abi::SOLID_RNG_SampleRandomBytes(bytes.as_mut_ptr(), bytes.len());
+        assert_eq!(result, 0, "failed to generate random data");
+    }
+}
diff --git a/library/std/src/sys/random/teeos.rs b/library/std/src/sys/random/teeos.rs
new file mode 100644
index 00000000000..fd6b24e19e9
--- /dev/null
+++ b/library/std/src/sys/random/teeos.rs
@@ -0,0 +1,7 @@
+extern "C" {
+    fn TEE_GenerateRandom(randomBuffer: *mut core::ffi::c_void, randomBufferLen: libc::size_t);
+}
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe { TEE_GenerateRandom(bytes.as_mut_ptr().cast(), bytes.len()) }
+}
diff --git a/library/std/src/sys/random/uefi.rs b/library/std/src/sys/random/uefi.rs
new file mode 100644
index 00000000000..a4d29e66f38
--- /dev/null
+++ b/library/std/src/sys/random/uefi.rs
@@ -0,0 +1,27 @@
+use r_efi::protocols::rng;
+
+use crate::sys::pal::helpers;
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let handles =
+        helpers::locate_handles(rng::PROTOCOL_GUID).expect("failed to generate random data");
+    for handle in handles {
+        if let Ok(protocol) = helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
+            let r = unsafe {
+                ((*protocol.as_ptr()).get_rng)(
+                    protocol.as_ptr(),
+                    crate::ptr::null_mut(),
+                    bytes.len(),
+                    bytes.as_mut_ptr(),
+                )
+            };
+            if r.is_error() {
+                continue;
+            } else {
+                return;
+            }
+        }
+    }
+
+    panic!("failed to generate random data");
+}
diff --git a/library/std/src/sys/random/unix.rs b/library/std/src/sys/random/unix.rs
new file mode 100644
index 00000000000..a56847e5541
--- /dev/null
+++ b/library/std/src/sys/random/unix.rs
@@ -0,0 +1,33 @@
+//! Random data generation through `getentropy`.
+//!
+//! Since issue 8 (2024), the POSIX specification mandates the existence of the
+//! `getentropy` function, which fills a slice of up to `GETENTROPY_MAX` bytes
+//! (256 on all known platforms) with random data. Luckily, this function has
+//! already been available on quite some BSDs before that, having appeared with
+//! OpenBSD 5.7 and spread from there:
+//!
+//! platform   | version | man-page
+//! -----------|---------|----------
+//! OpenBSD    | 5.6     | <https://man.openbsd.org/getentropy.2>
+//! FreeBSD    | 12.0    | <https://man.freebsd.org/cgi/man.cgi?query=getentropy&manpath=FreeBSD+15.0-CURRENT>
+//! macOS      | 10.12   | <https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/man/man2/getentropy.2#L4>
+//! NetBSD     | 10.0    | <https://man.netbsd.org/getentropy.3>
+//! DragonFly  | 6.1     | <https://man.dragonflybsd.org/?command=getentropy&section=3>
+//! Illumos    | ?       | <https://www.illumos.org/man/3C/getentropy>
+//! Solaris    | ?       | <https://docs.oracle.com/cd/E88353_01/html/E37841/getentropy-2.html>
+//!
+//! As it is standardized we use it whereever possible, even when `getrandom` is
+//! also available. NetBSD even warns that "Applications should avoid getrandom
+//! and use getentropy(2) instead; getrandom may be removed from a later
+//! release."[^1].
+//!
+//! [^1]: <https://man.netbsd.org/getrandom.2>
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    // GETENTROPY_MAX isn't defined yet on most platforms, but it's mandated
+    // to be at least 256, so just use that as limit.
+    for chunk in bytes.chunks_mut(256) {
+        let r = unsafe { libc::getentropy(chunk.as_mut_ptr().cast(), chunk.len()) };
+        assert_ne!(r, -1, "failed to generate random data");
+    }
+}
diff --git a/library/std/src/sys/random/unix_legacy.rs b/library/std/src/sys/random/unix_legacy.rs
new file mode 100644
index 00000000000..dd6be43c173
--- /dev/null
+++ b/library/std/src/sys/random/unix_legacy.rs
@@ -0,0 +1,20 @@
+//! Random data from `/dev/urandom`
+//!
+//! Before `getentropy` was standardized in 2024, UNIX didn't have a standardized
+//! way of getting random data, so systems just followed the precedent set by
+//! Linux and exposed random devices at `/dev/random` and `/dev/urandom`. Thus,
+//! for the few systems that do not support `getentropy` yet, we just read from
+//! the file.
+
+use crate::fs::File;
+use crate::io::Read;
+use crate::sync::OnceLock;
+
+static DEVICE: OnceLock<File> = OnceLock::new();
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    DEVICE
+        .get_or_try_init(|| File::open("/dev/urandom"))
+        .and_then(|mut dev| dev.read_exact(bytes))
+        .expect("failed to generate random data");
+}
diff --git a/library/std/src/sys/random/unsupported.rs b/library/std/src/sys/random/unsupported.rs
new file mode 100644
index 00000000000..d68ce4a9e87
--- /dev/null
+++ b/library/std/src/sys/random/unsupported.rs
@@ -0,0 +1,15 @@
+use crate::ptr;
+
+pub fn fill_bytes(_: &mut [u8]) {
+    panic!("this target does not support random data generation");
+}
+
+pub fn hashmap_random_keys() -> (u64, u64) {
+    // Use allocation addresses for a bit of randomness. This isn't
+    // particularily secure, but there isn't really an alternative.
+    let stack = 0u8;
+    let heap = Box::new(0u8);
+    let k1 = ptr::from_ref(&stack).addr() as u64;
+    let k2 = ptr::from_ref(&*heap).addr() as u64;
+    (k1, k2)
+}
diff --git a/library/std/src/sys/random/vxworks.rs b/library/std/src/sys/random/vxworks.rs
new file mode 100644
index 00000000000..d549ccebdb2
--- /dev/null
+++ b/library/std/src/sys/random/vxworks.rs
@@ -0,0 +1,25 @@
+use crate::sync::atomic::AtomicBool;
+use crate::sync::atomic::Ordering::Relaxed;
+
+static RNG_INIT: AtomicBool = AtomicBool::new(false);
+
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !RNG_INIT.load(Relaxed) {
+        let ret = unsafe { libc::randSecure() };
+        if ret < 0 {
+            panic!("failed to generate random data");
+        } else if ret > 0 {
+            RNG_INIT.store(true, Relaxed);
+            break;
+        }
+
+        unsafe { libc::usleep(10) };
+    }
+
+    while !bytes.is_empty() {
+        let len = bytes.len().try_into().unwrap_or(libc::c_int::MAX);
+        let ret = unsafe { libc::randABytes(bytes.as_mut_ptr(), len) };
+        assert!(ret >= 0, "failed to generate random data");
+        bytes = &mut bytes[len as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/wasi.rs b/library/std/src/sys/random/wasi.rs
new file mode 100644
index 00000000000..d41da3751fc
--- /dev/null
+++ b/library/std/src/sys/random/wasi.rs
@@ -0,0 +1,5 @@
+pub fn fill_bytes(bytes: &mut [u8]) {
+    unsafe {
+        wasi::random_get(bytes.as_mut_ptr(), bytes.len()).expect("failed to generate random data")
+    }
+}
diff --git a/library/std/src/sys/random/windows.rs b/library/std/src/sys/random/windows.rs
new file mode 100644
index 00000000000..7566000f9e6
--- /dev/null
+++ b/library/std/src/sys/random/windows.rs
@@ -0,0 +1,20 @@
+use crate::sys::c;
+
+#[cfg(not(target_vendor = "win7"))]
+#[inline]
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let ret = unsafe { c::ProcessPrng(bytes.as_mut_ptr(), bytes.len()) };
+    // ProcessPrng is documented as always returning `TRUE`.
+    // https://learn.microsoft.com/en-us/windows/win32/seccng/processprng#return-value
+    debug_assert_eq!(ret, c::TRUE);
+}
+
+#[cfg(target_vendor = "win7")]
+pub fn fill_bytes(mut bytes: &mut [u8]) {
+    while !bytes.is_empty() {
+        let len = bytes.len().try_into().unwrap_or(u32::MAX);
+        let ret = unsafe { c::RtlGenRandom(bytes.as_mut_ptr().cast(), len) };
+        assert_ne!(ret, 0, "failed to generate random data");
+        bytes = &mut bytes[len as usize..];
+    }
+}
diff --git a/library/std/src/sys/random/zkvm.rs b/library/std/src/sys/random/zkvm.rs
new file mode 100644
index 00000000000..3011942f6b2
--- /dev/null
+++ b/library/std/src/sys/random/zkvm.rs
@@ -0,0 +1,21 @@
+use crate::sys::pal::abi;
+
+pub fn fill_bytes(bytes: &mut [u8]) {
+    let (pre, words, post) = unsafe { bytes.align_to_mut::<u32>() };
+    if !words.is_empty() {
+        unsafe {
+            abi::sys_rand(words.as_mut_ptr(), words.len());
+        }
+    }
+
+    let mut buf = [0u32; 2];
+    let len = (pre.len() + post.len() + size_of::<u32>() - 1) / size_of::<u32>();
+    if len != 0 {
+        unsafe { abi::sys_rand(buf.as_mut_ptr(), len) };
+    }
+
+    let buf = buf.map(u32::to_ne_bytes);
+    let buf = buf.as_flattened();
+    pre.copy_from_slice(&buf[..pre.len()]);
+    post.copy_from_slice(&buf[pre.len()..pre.len() + post.len()]);
+}