Skip to main content

vdso_rng/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3extern crate alloc;
4
5#[cfg(not(miri))]
6mod auxv;
7mod config;
8mod pool;
9mod utils;
10#[cfg_attr(miri, path = "vdso_miri.rs")]
11mod vdso;
12use core::ffi::c_uint;
13use linux_raw_sys::errno;
14pub use pool::Pool;
15use pool::Ptr;
16
17/// Errors that may occur during vdso getrandom operations.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Error {
20    /// The shared state block pool has been poisoned.
21    /// This should not happen with safe usage.
22    PoolPoisoned,
23    /// The operation is not supported on this platform.
24    /// We failed to find the vdso symbol.
25    NotSupported,
26    /// Allocation failure occurred while trying to acquire a new random state.
27    AllocationFailure,
28    /// Normal errno as if it is returned from a system call.
29    Errno(i32),
30}
31
32impl core::fmt::Display for Error {
33    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34        match self {
35            Error::NotSupported => write!(f, "Operation not supported on this platform"),
36            Error::AllocationFailure => write!(f, "Failed to allocate memory"),
37            Error::Errno(e) => write!(f, "System call failed with error code: {e}"),
38            Error::PoolPoisoned => write!(f, "Memory pool has been poisoned"),
39        }
40    }
41}
42
43impl core::error::Error for Error {}
44
45/// A local state for `vDSO`-based `getrandom` operations.
46///
47/// This state is rented from a shared [`Pool`] and used to fill buffers with random bytes.
48///
49/// ```rust
50/// use vdso_rng::{Pool, LocalState};
51///
52/// let pool = Pool::new().expect("Failed to create shared pool");
53/// let mut local_state = LocalState::new(&pool).expect("Failed to create local state");
54///
55/// let mut buf = [0u8; 64];
56/// local_state.fill(&mut buf, 0).expect("Failed to fill buffer");
57///
58/// assert!(buf.iter().any(|&x| x != 0), "Buffer should not be empty");
59/// ```
60///
61/// Typically, [`LocalState`] is intended to be used in a thread-local fashion. A thread rents
62/// a state once and reuses it for multiple `getrandom` calls. Since acquiring a new state incurs
63/// synchronization overhead, reusing the state within the same thread is strongly recommended.
64///
65/// On drop, the state is returned to the pool for reuse.
66///
67/// ## Safety
68/// - **Not async-signal-safe**: [`LocalState`] is not safe to use within signal handlers. Reentrant usage
69///   (e.g., invoking [`LocalState::fill`] from a signal handler that interrupts a [`LocalState::fill`]) can lead to secret leakage or corruption.
70/// - **Reentrancy detection**: While Rust's borrowing rules typically prevent such misuse,
71///   it may occur in the presence of undefined behavior. Under debug builds, reentrancy is explicitly checked
72///   and will panic if detected.
73pub struct LocalState<'a> {
74    state: Ptr,
75    pool: &'a Pool,
76    #[cfg(debug_assertions)]
77    inflight: bool,
78}
79
80impl<'a> LocalState<'a> {
81    /// Create a new local state from the given pool. The pool must outlive the local state.
82    pub fn new(pool: &'a Pool) -> Result<Self, Error> {
83        let state = pool.get()?;
84        Ok(Self {
85            state,
86            pool,
87            #[cfg(debug_assertions)]
88            inflight: false,
89        })
90    }
91    /// Fill the provided buffer with random bytes. This method may not fill the entire buffer
92    /// due to interrupts or low entropy conditions.
93    pub fn try_fill(&mut self, buf: &mut [u8], flag: c_uint) -> Result<usize, Error> {
94        let function = self.pool.config.function;
95        let state = self.state.0.as_ptr();
96        let state_length = self.pool.config.params.size_of_opaque_states as usize;
97        let buffer_len = buf.len();
98        #[cfg(debug_assertions)]
99        {
100            debug_assert!(
101                !self.inflight,
102                "LocalState is already in use, reentrancy detected"
103            );
104            self.inflight = true;
105            core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
106        }
107        unsafe {
108            let result = function(
109                buf.as_mut_ptr() as *mut _,
110                buffer_len,
111                flag,
112                state,
113                state_length,
114            );
115            #[cfg(debug_assertions)]
116            {
117                // Make sure all random bytes are written before moving on
118                core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
119                self.inflight = false;
120            }
121            if result < 0 {
122                return Err(Error::Errno(-result));
123            }
124            Ok(result as usize)
125        }
126    }
127
128    /// Fill the provided buffer with random bytes. This method will block until the buffer is filled.
129    /// It is implemented as a loop wrapping around [`LocalState::try_fill`].
130    pub fn fill(&mut self, mut buf: &mut [u8], flag: c_uint) -> Result<(), Error> {
131        while !buf.is_empty() {
132            match self.try_fill(buf, flag) {
133                Ok(filled) => {
134                    buf = &mut buf[filled..];
135                    continue;
136                }
137                Err(Error::Errno(e)) if e == errno::EAGAIN as i32 || e == errno::EINTR as i32 => {
138                    continue;
139                }
140                Err(e) => {
141                    return Err(e);
142                }
143            }
144        }
145        Ok(())
146    }
147}
148
149impl<'a> Drop for LocalState<'a> {
150    fn drop(&mut self) {
151        let state = self.state;
152        self.pool.recycle(state);
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    extern crate std;
159    use std::cell::RefCell;
160
161    use alloc::vec::Vec;
162
163    use super::*;
164    #[test]
165    fn get_local_state() {
166        let pool = Pool::new().expect("Failed to create shared pool");
167        _ = LocalState::new(&pool).expect("Failed to create local state");
168    }
169
170    #[test]
171    fn fill_local_state() {
172        let pool = Pool::new().expect("Failed to create shared pool");
173        let mut local_state = LocalState::new(&pool).unwrap();
174        let mut buf = [0u8; 64];
175        let res = local_state.fill(&mut buf, 0);
176        assert!(res.is_ok(), "Failed to fill local state: {:?}", res);
177        assert!(buf.iter().any(|&x| x != 0), "Buffer should not be empty");
178    }
179
180    #[test]
181    fn multi_local_state() {
182        let pool = Pool::new().expect("Failed to create shared pool");
183        let mut states = Vec::new();
184        for _ in 0..128 {
185            let local_state = LocalState::new(&pool).unwrap();
186            states.push(local_state);
187        }
188        for state in states.iter_mut() {
189            let mut buf = [0u8; 64];
190            let res = state.fill(&mut buf, 0);
191            assert!(res.is_ok(), "Failed to fill local state: {:?}", res);
192            assert!(buf.iter().any(|&x| x != 0), "Buffer should not be empty");
193        }
194    }
195
196    #[test]
197    fn parallel_local_state() {
198        let pool = Pool::new().expect("Failed to create shared pool");
199        std::thread::scope(|scope| {
200            let pool = &pool;
201            for _ in 0..16 {
202                scope.spawn(|| {
203                    for _ in 0..16 {
204                        let mut local_state = LocalState::new(pool).unwrap();
205                        let mut buf = [0u8; 64];
206                        let res = local_state.fill(&mut buf, 0);
207                        assert!(res.is_ok(), "Failed to fill local state: {:?}", res);
208                        assert!(buf.iter().any(|&x| x != 0), "Buffer should not be empty");
209                    }
210                });
211            }
212        });
213    }
214
215    #[test]
216    fn global_state_test() {
217        fn global_pool() -> &'static Pool {
218            static GLOBAL_STATE: std::sync::LazyLock<Pool> =
219                std::sync::LazyLock::new(|| Pool::new().expect("Failed to create global pool"));
220            &GLOBAL_STATE
221        }
222        fn fill(buf: &mut [u8], flag: c_uint) -> Result<(), Error> {
223            std::thread_local! {
224                static LOCAL_STATE: RefCell<LocalState<'static>> = RefCell::new(LocalState::new(global_pool()).expect("Failed to create local state"));
225            }
226            LOCAL_STATE.with(|local_state| {
227                let mut state = local_state.borrow_mut();
228                state.fill(buf, flag)
229            })
230        }
231
232        std::thread::scope(|scope| {
233            for _ in 0..16 {
234                scope.spawn(|| {
235                    for _ in 0..16 {
236                        let mut buf = [0u8; 64];
237                        let res = fill(&mut buf, 0);
238                        assert!(res.is_ok(), "Failed to fill global state: {:?}", res);
239                        assert!(buf.iter().any(|&x| x != 0), "Buffer should not be empty");
240                    }
241                });
242            }
243        });
244    }
245}