use anyhow::{Error, Result, anyhow};
use log::{debug, error};
use std::{fs::File, os::fd::AsRawFd};
use crate::ioctl_defs;
pub fn get_ent_cnt() -> Result<i32> {
let random_file = File::create("/dev/random")?;
let fd = random_file.as_raw_fd();
let mut ent_cnt = 0;
let ret = unsafe { ioctl_defs::rnd_get_ent_cnt(fd, &mut ent_cnt) };
if let Ok(0) = ret {
Ok(ent_cnt)
} else {
error!("ioctl returned with error");
Err(anyhow!("Failed to fetch entropy level from kernel"))
}
}
pub fn add_to_ent_cnt(ent_cnt: i32) -> Result<()> {
let random_file = File::create("/dev/random")?;
let fd = random_file.as_raw_fd();
let ret = unsafe { ioctl_defs::rnd_add_to_ent_cnt(fd, &ent_cnt) };
if let Ok(0) = ret {
Ok(())
} else {
error!("ioctl returned with error");
Err(anyhow!("Failed to add to ent cnt"))
}
}
pub fn add_randomness_to_kernel(entropy: &[u8], ent_bits: u32) -> Result<()> {
let random_file = File::create("/dev/random")?;
let fd = random_file.as_raw_fd();
if usize::try_from(ent_bits)? > entropy.len() * 8 {
return Err(anyhow!("Do not claim more entropy than buffer length * 8!"));
}
if entropy.len() > ioctl_defs::MAX_BUFFER_SIZE {
return Err(anyhow!(
"This implementation currently can write up to {} Byte to kernel CRNG input pool", ioctl_defs::MAX_BUFFER_SIZE
));
}
debug!(
"Write {} Byte to /dev/random, accounted with {} Bit entropy",
64, ent_bits
);
let mut pool_info = ioctl_defs::KernelRandPoolInfo {
header: ioctl_defs::KernelRandPoolInfoHeader {
entropy_bits: i32::try_from(ent_bits)?,
buf_size_byte: i32::try_from(entropy.len())?,
},
buf: [0; ioctl_defs::MAX_BUFFER_SIZE],
};
pool_info.buf[0..entropy.len()].copy_from_slice(entropy);
#[allow(clippy::ptr_as_ptr)]
let res = unsafe {
ioctl_defs::rnd_add_entropy(
fd,
std::ptr::addr_of!(pool_info) as *const ioctl_defs::KernelRandPoolInfoHeader,
)
};
if let Ok(0) = res {
Ok(())
} else {
error!("ioctl returned with error");
Err(anyhow!("Failed to add entropy to kernel"))
}
}
pub fn clear_entropy_count() -> Result<(), Error> {
let random_file = File::create("/dev/random")?;
let fd = random_file.as_raw_fd();
match unsafe { ioctl_defs::rnd_zap_ent_cnt(fd) } {
Ok(0) => {
debug!("Cleared kernel CRNG entropy count to 0");
Ok(())
}
_ => Err(anyhow!("Cannot clear CRNG entropy count to 0")),
}
}
pub fn clear_pool() -> Result<(), Error> {
let random_file = File::create("/dev/random")?;
let fd = random_file.as_raw_fd();
match unsafe { ioctl_defs::rnd_clear_pool(fd) } {
Ok(0) => {
debug!("Forcefully cleared kernel CRNG pool");
Ok(())
}
_ => Err(anyhow!("Cannot clear CRNG pool")),
}
}
pub fn force_kernel_crng_reseed() -> Result<(), Error> {
let random_file = File::create("/dev/random")?;
let fd = random_file.as_raw_fd();
match unsafe { ioctl_defs::rnd_reseed_crng(fd) } {
Ok(0) => {
debug!("Forcefully reseeded kernel CRNG");
Ok(())
}
_ => Err(anyhow!("Cannot reseed CRNG")),
}
}
#[cfg(test)]
mod tests {
use crate::ioctl_defs;
use crate::ioctl::{
add_randomness_to_kernel, add_to_ent_cnt, clear_entropy_count, clear_pool,
force_kernel_crng_reseed, get_ent_cnt
};
use nix::unistd::Uid;
#[test]
fn test_get_ent_cnt() {
assert!(get_ent_cnt().is_ok(), "failed to get entropy count");
}
#[test]
fn test_add_to_entropy_count() {
if !Uid::effective().is_root() {
println!("Skipping test: requires root privileges");
return;
}
assert!(
add_to_ent_cnt(32).is_ok(),
"failed to add to CRNG entropy count"
);
}
#[test]
fn test_add_randomness_multiple_buffer_sizes() {
if !Uid::effective().is_root() {
println!("Skipping test: requires root privileges");
return;
}
let test_sizes = [
1, 64, 512, 1024, ioctl_defs::MAX_BUFFER_SIZE, ];
for size in test_sizes {
let buffer = vec![0x55; size]; let entropy_bits = u32::try_from(size * 8).unwrap();
let result = add_randomness_to_kernel(&buffer, entropy_bits);
assert!(
result.is_ok(),
"Failed to add randomness with buffer size {size}: {result:?}"
);
}
let oversized_buffer = vec![0x55; ioctl_defs::MAX_BUFFER_SIZE + 1];
let result = add_randomness_to_kernel(&oversized_buffer, 8);
assert!(
result.is_err(),
"{}", format!("Expected error for buffer size larger than {}", ioctl_defs::MAX_BUFFER_SIZE)
);
}
#[test]
fn test_add_entropy() {
if !Uid::effective().is_root() {
println!("Skipping test: requires root privileges");
return;
}
assert!(
add_randomness_to_kernel(&[0u8; 32], 256).is_ok(),
"failed to add randomness to kernel"
);
}
#[test]
fn test_clear_entropy_count() {
if !Uid::effective().is_root() {
println!("Skipping test: requires root privileges");
return;
}
assert!(
clear_entropy_count().is_ok(),
"failed to clear CRNG entropy count"
);
}
#[test]
fn test_clear_pool() {
if Uid::effective().is_root() {
assert!(clear_pool().is_ok(), "failed to clear CRNG pool");
}
}
#[test]
fn test_reseed_crng() {
if !Uid::effective().is_root() {
println!("Skipping test: requires root privileges");
return;
}
assert!(force_kernel_crng_reseed().is_ok(), "failed to reseed CRNG");
}
}