Created
October 31, 2022 17:09
-
-
Save lrvick/ef7670e1e94853d283ac9e35926d201a to your computer and use it in GitHub Desktop.
Seeding the Linux Kernel Entropy pool using the ioctl RNDADDENTROPY interface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use libc::{ | |
c_int, | |
}; | |
use std::{ | |
mem::{size_of, align_of}, | |
fs::{read_to_string}, | |
fmt, | |
io::Read, | |
fs::File, | |
}; | |
struct SystemError { | |
message: String, | |
} | |
impl fmt::Display for SystemError { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
write!(f, "{} {}", boot_time(), self.message) | |
} | |
} | |
// Log dmesg formatted log to console | |
fn dmesg(message: String){ | |
println!("{} {}", boot_time(), message); | |
} | |
// Dmesg formatted seconds since boot | |
fn boot_time() -> String { | |
use libc::{clock_gettime, timespec, CLOCK_BOOTTIME}; | |
let mut t = timespec { tv_sec: 0, tv_nsec: 0 }; | |
unsafe { clock_gettime(CLOCK_BOOTTIME, &mut t as *mut timespec); } | |
format!("[ {: >4}.{}]", t.tv_sec, t.tv_nsec / 1000).to_string() | |
} | |
// Get the maximum size of the entropy pool | |
fn get_random_max() -> Result<usize, SystemError> { | |
let ps_path = "/proc/sys/kernel/random/poolsize"; | |
let size_s = read_to_string(ps_path).unwrap_or_else(|_| String::new()); | |
let size_s = size_s.strip_suffix("\n").unwrap_or(&size_s); | |
if size_s.is_empty(){ | |
return Err(SystemError { | |
message: String::from("Failed to read kernel random poolsize"), | |
}) | |
}; | |
match size_s.parse::<usize>() { | |
Ok(size) => Ok(size), | |
Err(_) => Err(SystemError { | |
message: String::from("Failed to parse kernel random poolsize"), | |
}), | |
} | |
} | |
// Get the maximum size of the entropy pool | |
fn get_random_avail() -> Result<usize, SystemError> { | |
let ps_path = "/proc/sys/kernel/random/entropy_avail"; | |
let size_s = read_to_string(ps_path).unwrap_or_else(|_| String::new()); | |
let size_s = size_s.strip_suffix("\n").unwrap_or(&size_s); | |
if size_s.is_empty(){ | |
return Err(SystemError { | |
message: String::from("Failed to read kernel random entropy_avail"), | |
}) | |
}; | |
match size_s.parse::<usize>() { | |
Ok(size) => Ok(size), | |
Err(_) => Err(SystemError { | |
message: String::from("Failed to parse kernel random poolsize"), | |
}), | |
} | |
} | |
const ENTROPY_IOCTL_BASE: u8 = b'R'; | |
const ENTROPY_SETOPTIONS: u8 = 0x03; | |
pub unsafe fn rndaddentropy( | |
fd: c_int, | |
data: *const RandPoolInfo, | |
) -> c_int { | |
use libc::ioctl; | |
#[cfg(target_env = "musl")] | |
type IoType = ::libc::c_int; | |
#[cfg(not(target_env = "musl"))] | |
type IoType = ::libc::c_ulong; | |
const WRITE: u8 = 1; | |
const SIZEBITS: u8 = 14; | |
const DIRBITS: u8 = 2; | |
const NRBITS: IoType = 8; | |
const TYPEBITS: IoType = 8; | |
const NRSHIFT: IoType = 0; | |
const TYPESHIFT: IoType = NRSHIFT + NRBITS as IoType; | |
const SIZESHIFT: IoType = TYPESHIFT + TYPEBITS as IoType; | |
const DIRSHIFT: IoType = SIZESHIFT + SIZEBITS as IoType; | |
const NRMASK: IoType = (1 << NRBITS) - 1; | |
const TYPEMASK: IoType = (1 << TYPEBITS) - 1; | |
const SIZEMASK: IoType = (1 << SIZEBITS) - 1; | |
const DIRMASK: IoType = (1 << DIRBITS) - 1; | |
let result = ioctl( | |
fd, | |
( ((WRITE as IoType & DIRMASK) << DIRSHIFT) | |
| ((ENTROPY_IOCTL_BASE as IoType & TYPEMASK) << TYPESHIFT ) | |
| ((ENTROPY_SETOPTIONS as IoType & NRMASK) << NRSHIFT) | |
| ((size_of::<RandPoolInfo>() as IoType & SIZEMASK) << SIZESHIFT) | |
) as IoType, | |
data, | |
); | |
result as c_int | |
} | |
pub struct RandPoolInfo { | |
entropy_count: i32, | |
buf_size: i32, | |
buf: [u8; 0], | |
} | |
fn write_entropy(random_fd: &mut File, data: &mut Vec<u8>) -> bool { | |
use std::alloc::{alloc, dealloc, Layout}; | |
use std::os::fd::AsRawFd; | |
assert!(!data.is_empty()); | |
if data.is_empty() { | |
return false; | |
} | |
unsafe { | |
let info_size = size_of::<RandPoolInfo>(); | |
let layout = | |
Layout::from_size_align(data.len() + info_size, align_of::<u8>()).unwrap(); | |
let ptr = alloc(layout); | |
let r_p_info = ptr as *mut RandPoolInfo; | |
(*r_p_info).entropy_count = (data.len() * 8) as i32; | |
(*r_p_info).buf_size = data.len() as i32; | |
let ptr_data = ptr.add(info_size); | |
for (index, value) in data.iter().enumerate() { | |
*ptr_data.add(index) = *value; | |
} | |
let result = rndaddentropy( | |
random_fd.as_raw_fd(), | |
ptr as *const RandPoolInfo | |
); | |
dealloc(ptr, layout); | |
if result !=0 { | |
false | |
} else { | |
true | |
} | |
} | |
} | |
fn seed_entropy( | |
max: usize, | |
source: fn(usize) -> Result<Vec<u8>, SystemError>, | |
) -> Result<usize, SystemError> { | |
let pool_size = match get_random_avail() { | |
Ok(file) => file, | |
Err(e) => { return Err(e) }, | |
}; | |
let sample_size = max - pool_size; | |
let mut entropy_sample = match source(sample_size) { | |
Ok(sample)=> sample, | |
Err(e)=> { return Err(e) }, | |
}; | |
use std::fs::OpenOptions; | |
let mut random_fd = match OpenOptions::new() | |
.read(true) | |
.write(true) | |
.open("/dev/urandom") | |
{ | |
Ok(file) => file, | |
Err(_) => { | |
return Err(SystemError { | |
message: String::from("Failed to open /dev/urandom"), | |
}); | |
}, | |
}; | |
if !write_entropy(&mut random_fd, &mut entropy_sample) { | |
return Err(SystemError { | |
message: String::from("Failed to write to /dev/urandom"), | |
}); | |
} | |
Ok(entropy_sample.len()) | |
} | |
fn getrandom_kernel(size: usize) -> Result<Vec<u8>, SystemError> { | |
use std::fs::OpenOptions; | |
let mut dest = Vec::with_capacity(size); | |
let mut random_fd = match OpenOptions::new() | |
.read(true) | |
.open("/dev/urandom") | |
{ | |
Ok(file) => file, | |
Err(_) => { | |
return Err(SystemError { | |
message: String::from("Failed to open /dev/urandom"), | |
}); | |
}, | |
}; | |
while dest.len() < size { | |
let mut buf = [0u8; 256]; | |
match random_fd.read_exact(&mut buf) { | |
Ok(_) => (), | |
Err(_) => { | |
return Err(SystemError { | |
message: String::from("Failed to read from /dev/urandom"), | |
}); | |
}, | |
}; | |
dest.extend_from_slice(&buf); | |
} | |
Ok(dest) | |
} | |
fn main() { | |
match seed_entropy(4096, getrandom_kernel) { | |
Ok(size)=> dmesg(format!("Seeded kernel with entropy: {}", size)), | |
Err(e)=> eprintln!("{}", e) | |
}; | |
match getrandom_kernel(256) { | |
Ok(_)=> dmesg(format!("Got entropy sample from Kernel: {}", 256)), | |
Err(e)=> { eprintln!("{}",e) }, | |
}; | |
match getrandom_kernel(256) { | |
Ok(_)=> dmesg(format!("Got entropy sample from Kernel: {}", 256)), | |
Err(e)=> { eprintln!("{}",e) }, | |
}; | |
match getrandom_kernel(256) { | |
Ok(_)=> dmesg(format!("Got entropy sample from Kernel: {}", 256)), | |
Err(e)=> { eprintln!("{}",e) }, | |
}; | |
match get_random_max() { | |
Ok(size)=> dmesg(format!("Kernel entropy pool size: {}", size)), | |
Err(e)=> eprintln!("{}", e) | |
}; | |
match get_random_avail() { | |
Ok(size)=> dmesg(format!("Kernel entropy available: {}", size)), | |
Err(e)=> eprintln!("{}", e) | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment