linux_crng_ioctl/
lib.rs

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
7/* numbers and comments taken from: include/uapi/linux/random.h */
8const IOC_MAGIC: u8 = b'R';
9
10/* ioctl()'s for the random number generator */
11
12/* Get the entropy count. */
13const RNDGETENTCNT: u8 = 0x0;
14
15/* Add to (or subtract from) the entropy count.  (Superuser only.) */
16const RNDADDTOENTCNT: u8 = 0x1;
17
18/* Get the contents of the entropy pool.  (Superuser only.) (Removed in 2.6.9-rc2.) */
19// const RNDGETPOOL: u8 = 0x2;
20
21/* Add to (or subtract from) the entropy count.  (Superuser only.) */
22const RNDADDENTROPY: u8 = 0x3;
23
24/* Clear entropy count to 0.  (Superuser only.) */
25const RNDZAPENTCNT: u8 = 0x4;
26
27/* Clear the entropy pool and associated counters.  (Superuser only.) */
28const RNDCLEARPOOL: u8 = 0x6;
29
30/* Reseed CRNG.  (Superuser only.) */
31const RNDRESEEDCRNG: u8 = 0x7;
32
33/* Max input size for writing entropy to kernel */
34const 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
54// we need the header struct size to match 2*sizeof(i32) in order to match the kernel
55// ioctl magic
56ioctl_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
67///
68/// # Errors
69/// - access to kernel fails or no more fds available
70pub 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
84///
85/// # Errors
86/// - access to kernel fails or no more fds available
87pub 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
100///
101/// # Errors
102/// - access to kernel fails or no more fds available
103pub 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
145///
146/// # Errors
147/// - access to kernel fails or no more fds available
148pub 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
161///
162/// # Errors
163/// - access to kernel fails or no more fds available
164pub 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
177///
178/// # Errors
179/// - access to kernel fails or no more fds available
180pub 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}