#![allow(unsafe_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Error {
NotImplemented,
#[allow(dead_code)] Other(i32),
}
#[cfg(all(
target_os = "linux",
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv64",
)
))]
pub(super) fn try_getrandom(buf: &mut [u8]) -> Result<(), Error> {
const ENOSYS: i32 = 38;
const EINTR: i32 = 4;
let mut filled = 0usize;
while filled < buf.len() {
let ret = unsafe {
getrandom_syscall(
buf[filled..].as_mut_ptr(),
buf.len() - filled,
0, )
};
if ret < 0 {
let errno = -ret as i32;
if errno == EINTR {
continue;
}
if errno == ENOSYS {
return Err(Error::NotImplemented);
}
return Err(Error::Other(errno));
}
filled += ret as usize;
}
Ok(())
}
#[cfg(all(
target_os = "linux",
not(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv64",
))
))]
pub(super) fn try_getrandom(_buf: &mut [u8]) -> Result<(), Error> {
Err(Error::NotImplemented)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
unsafe fn getrandom_syscall(buf: *mut u8, len: usize, flags: u32) -> isize {
let ret: isize;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") 318isize => ret,
in("rdi") buf,
in("rsi") len,
in("rdx") flags as usize,
lateout("rcx") _,
lateout("r11") _,
options(nostack, preserves_flags),
);
}
ret
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
unsafe fn getrandom_syscall(buf: *mut u8, len: usize, flags: u32) -> isize {
let ret: isize;
unsafe {
core::arch::asm!(
"svc #0",
in("x8") 278isize,
inlateout("x0") buf as isize => ret,
in("x1") len,
in("x2") flags as usize,
options(nostack, preserves_flags),
);
}
ret
}
#[cfg(all(target_os = "linux", target_arch = "arm"))]
unsafe fn getrandom_syscall(buf: *mut u8, len: usize, flags: u32) -> isize {
let ret: isize;
unsafe {
core::arch::asm!(
"svc #0",
in("r7") 384isize,
inlateout("r0") buf as isize => ret,
in("r1") len,
in("r2") flags as usize,
options(nostack, preserves_flags),
);
}
ret
}
#[cfg(all(target_os = "linux", target_arch = "riscv64"))]
unsafe fn getrandom_syscall(buf: *mut u8, len: usize, flags: u32) -> isize {
let ret: isize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") 278isize,
inlateout("a0") buf as isize => ret,
in("a1") len,
in("a2") flags as usize,
options(nostack, preserves_flags),
);
}
ret
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(
target_os = "linux",
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv64",
)
))]
#[test]
fn fills_a_small_buffer() {
let mut buf = [0u8; 32];
try_getrandom(&mut buf).expect("getrandom should succeed on a supported Linux arch");
assert!(buf.iter().any(|&b| b != 0));
}
#[cfg(all(
target_os = "linux",
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv64",
)
))]
#[test]
fn fills_a_large_buffer() {
let mut buf: alloc::boxed::Box<[u8; 4096]> = alloc::boxed::Box::new([0u8; 4096]);
try_getrandom(&mut buf[..]).expect("getrandom should succeed");
let nonzero = buf.iter().filter(|&&b| b != 0).count();
assert!(
nonzero > 4096 * 9 / 10,
"suspiciously many zeros: {nonzero}/4096"
);
}
}