1use anyhow::{Error, Result, anyhow};
2use log::{debug, error};
3use nix::{ioctl_none, ioctl_read, ioctl_write_ptr};
4use std::{fs::File, os::fd::AsRawFd};
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7const IOC_MAGIC: u8 = b'R';
9
10const RNDGETENTCNT: u8 = 0x0;
14
15const RNDADDTOENTCNT: u8 = 0x1;
17
18const RNDADDENTROPY: u8 = 0x3;
23
24const RNDZAPENTCNT: u8 = 0x4;
26
27const RNDCLEARPOOL: u8 = 0x6;
29
30const RNDRESEEDCRNG: u8 = 0x7;
32
33const MAX_BUFFER_SIZE: usize = 2 * 1024;
35
36#[repr(C)]
37#[derive(Zeroize, ZeroizeOnDrop)]
38pub struct KernelRandPoolInfoHeader {
39 entropy_bits: i32,
40 buf_size_byte: i32,
41}
42
43#[repr(C)]
44#[derive(Zeroize, ZeroizeOnDrop)]
45pub struct KernelRandPoolInfo {
46 header: KernelRandPoolInfoHeader,
47 buf: [u8; MAX_BUFFER_SIZE],
48}
49
50ioctl_read!(rnd_get_ent_cnt, IOC_MAGIC, RNDGETENTCNT, i32);
51
52ioctl_write_ptr!(rnd_add_to_ent_cnt, IOC_MAGIC, RNDADDTOENTCNT, i32);
53
54ioctl_write_ptr!(
57 rnd_add_entropy,
58 IOC_MAGIC,
59 RNDADDENTROPY,
60 KernelRandPoolInfoHeader
61);
62
63ioctl_none!(rnd_zap_ent_cnt, IOC_MAGIC, RNDZAPENTCNT);
64ioctl_none!(rnd_clear_pool, IOC_MAGIC, RNDCLEARPOOL);
65ioctl_none!(rnd_reseed_crng, IOC_MAGIC, RNDRESEEDCRNG);
66
67pub fn get_ent_cnt() -> Result<i32> {
71 let random_file = File::create("/dev/random")?;
72 let fd = random_file.as_raw_fd();
73 let mut ent_cnt = 0;
74
75 let ret = unsafe { rnd_get_ent_cnt(fd, &mut ent_cnt) };
76 if let Ok(0) = ret {
77 Ok(ent_cnt)
78 } else {
79 error!("ioctl returned with error");
80 Err(anyhow!("Failed to fetch entropy level from kernel"))
81 }
82}
83
84pub fn add_to_ent_cnt(ent_cnt: i32) -> Result<()> {
88 let random_file = File::create("/dev/random")?;
89 let fd = random_file.as_raw_fd();
90
91 let ret = unsafe { rnd_add_to_ent_cnt(fd, &ent_cnt) };
92 if let Ok(0) = ret {
93 Ok(())
94 } else {
95 error!("ioctl returned with error");
96 Err(anyhow!("Failed to add to ent cnt"))
97 }
98}
99
100pub fn add_randomness_to_kernel(entropy: &[u8], ent_bits: u32) -> Result<()> {
104 let random_file = File::create("/dev/random")?;
105 let fd = random_file.as_raw_fd();
106
107 if usize::try_from(ent_bits)? > entropy.len() * 8 {
108 return Err(anyhow!("Do not claim more entropy than buffer length * 8!"));
109 }
110
111 if entropy.len() > MAX_BUFFER_SIZE {
112 return Err(anyhow!("This implementation currently can write up to {MAX_BUFFER_SIZE} Byte to kernel CRNG input pool"));
113 }
114
115 debug!(
116 "Write {} Byte to /dev/random, accounted with {} Bit entropy",
117 64, ent_bits
118 );
119
120 let mut pool_info = KernelRandPoolInfo {
121 header: KernelRandPoolInfoHeader {
122 entropy_bits: i32::try_from(ent_bits)?,
123 buf_size_byte: i32::try_from(entropy.len())?,
124 },
125 buf: [0; MAX_BUFFER_SIZE],
126 };
127 pool_info.buf[0..entropy.len()].copy_from_slice(entropy);
128
129 #[allow(clippy::ptr_as_ptr)]
130 let res = unsafe {
131 rnd_add_entropy(
132 fd,
133 std::ptr::addr_of!(pool_info) as *const KernelRandPoolInfoHeader,
134 )
135 };
136
137 if let Ok(0) = res {
138 Ok(())
139 } else {
140 error!("ioctl returned with error");
141 Err(anyhow!("Failed to add entropy to kernel"))
142 }
143}
144
145pub fn clear_entropy_count() -> Result<(), Error> {
149 let random_file = File::create("/dev/random")?;
150 let fd = random_file.as_raw_fd();
151
152 match unsafe { rnd_zap_ent_cnt(fd) } {
153 Ok(0) => {
154 debug!("Cleared kernel CRNG entropy count to 0");
155 Ok(())
156 }
157 _ => Err(anyhow!("Cannot clear CRNG entropy count to 0")),
158 }
159}
160
161pub fn clear_pool() -> Result<(), Error> {
165 let random_file = File::create("/dev/random")?;
166 let fd = random_file.as_raw_fd();
167
168 match unsafe { rnd_clear_pool(fd) } {
169 Ok(0) => {
170 debug!("Forcefully cleared kernel CRNG pool");
171 Ok(())
172 }
173 _ => Err(anyhow!("Cannot clear CRNG pool")),
174 }
175}
176
177pub fn force_kernel_crng_reseed() -> Result<(), Error> {
181 let random_file = File::create("/dev/random")?;
182 let fd = random_file.as_raw_fd();
183
184 match unsafe { rnd_reseed_crng(fd) } {
185 Ok(0) => {
186 debug!("Forcefully reseeded kernel CRNG");
187 Ok(())
188 }
189 _ => Err(anyhow!("Cannot reseed CRNG")),
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use crate::{
196 add_randomness_to_kernel, add_to_ent_cnt, clear_entropy_count, clear_pool,
197 force_kernel_crng_reseed, get_ent_cnt,
198 };
199 use nix::unistd::Uid;
200
201 #[test]
202 fn test_get_ent_cnt() {
203 assert!(get_ent_cnt().is_ok(), "failed to get entropy count");
204 }
205
206 #[test]
207 fn test_add_to_entropy_count() {
208 if Uid::effective().is_root() {
209 assert!(
210 add_to_ent_cnt(32).is_ok(),
211 "failed to add to CRNG entropy count"
212 );
213 }
214 }
215
216 #[test]
217 fn test_add_entropy() {
218 if Uid::effective().is_root() {
219 assert!(
220 add_randomness_to_kernel(&[0u8; 32], 256).is_ok(),
221 "failed to add randomness to kernel"
222 );
223 }
224 }
225
226 #[test]
227 fn test_clear_entropy_count() {
228 if Uid::effective().is_root() {
229 assert!(
230 clear_entropy_count().is_ok(),
231 "failed to clear CRNG entropy count"
232 );
233 }
234 }
235
236 #[test]
237 fn test_clear_pool() {
238 if Uid::effective().is_root() {
239 assert!(clear_pool().is_ok(), "failed to clear CRNG pool");
240 }
241 }
242
243 #[test]
244 fn test_reseed_crng() {
245 if Uid::effective().is_root() {
246 assert!(force_kernel_crng_reseed().is_ok(), "failed to reseed CRNG");
247 }
248 }
249}