Auto-implement Rand for (normal, rusty rng) CoreRng types, fixes #3060 (#3064)

* Auto-implement Rand for (normal, rusty rng) CoreRng types, fixes #3060

* clippy

* cleanup

* clip

* doc

* more doc
This commit is contained in:
Dominik Maier 2025-03-11 11:51:31 +01:00 committed by GitHub
parent bf3c391ffa
commit 115672904e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 134 additions and 81 deletions

View File

@ -76,7 +76,7 @@ trait State:
impl<C, I, R, SC> State for StdState<C, I, R, SC>
where
C: Serialize + DeserializeOwned,
R: Rand,
R: Rand + Serialize + for<'de> Deserialize<'de>,
SC: Serialize + DeserializeOwned,
{
}

View File

@ -60,7 +60,8 @@ alloc = ["serde/alloc", "hashbrown", "postcard", "erased-serde/alloc", "ahash"]
## Provide the `#[derive(SerdeAny)]` macro.
derive = ["libafl_derive"]
## If set, libafl_bolt's `rand` implementations will implement `rand::Rng`
## If set, libafl_bolt's `rand` implementations will implement `rand_core::CoreRng`
## and, inversely, all seedable `rand_core::RngCore` types can be used as Rng for LibAFL.
rand_trait = ["rand_core"]
## Will build the `pyo3` bindings

View File

@ -8,7 +8,9 @@ use core::{
num::{NonZero, NonZeroUsize},
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
#[cfg(feature = "rand_trait")]
use rand_core::{RngCore, SeedableRng};
use serde::{Deserialize, Serialize};
#[cfg(feature = "alloc")]
pub mod loaded_dice;
@ -103,7 +105,7 @@ fn fast_bound_usize(rand: u64, n: usize) -> usize {
/// Ways to get random around here.
/// Please note that these are not cryptographically secure.
/// Or, even if some might be by accident, at least they are not seeded in a cryptographically secure fashion.
pub trait Rand: Debug + Serialize + DeserializeOwned {
pub trait Rand {
/// Sets the seed of this Rand
fn set_seed(&mut self, seed: u64);
@ -220,6 +222,20 @@ pub trait Rand: Debug + Serialize + DeserializeOwned {
}
}
#[cfg(feature = "rand_trait")]
impl<T> Rand for T
where
T: RngCore + SeedableRng + Serialize + for<'de> Deserialize<'de> + Debug,
{
fn set_seed(&mut self, seed: u64) {
*self = Self::seed_from_u64(seed);
}
fn next(&mut self) -> u64 {
self.next_u64()
}
}
macro_rules! impl_default_new {
($rand:ty) => {
impl Default for $rand {
@ -540,6 +556,92 @@ impl XkcdRand {
}
}
#[cfg(feature = "python")]
/// `Rand` Python bindings
pub mod pybind {
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use super::{Rand, StdRand, random_seed};
#[pyclass(unsendable, name = "StdRand")]
#[expect(clippy::unsafe_derive_deserialize)]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Python class for StdRand
pub struct PythonStdRand {
/// Rust wrapped StdRand object
pub inner: StdRand,
}
#[pymethods]
impl PythonStdRand {
#[staticmethod]
fn with_random_seed() -> Self {
Self {
inner: StdRand::with_seed(random_seed()),
}
}
#[staticmethod]
fn with_seed(seed: u64) -> Self {
Self {
inner: StdRand::with_seed(seed),
}
}
fn as_rand(slf: Py<Self>) -> PythonRand {
PythonRand::new_std(slf)
}
}
#[derive(Serialize, Deserialize, Debug)]
enum PythonRandWrapper {
Std(Py<PythonStdRand>),
}
/// Rand Trait binding
#[pyclass(unsendable, name = "Rand")]
#[expect(clippy::unsafe_derive_deserialize)]
#[derive(Serialize, Deserialize, Debug)]
pub struct PythonRand {
wrapper: PythonRandWrapper,
}
macro_rules! unwrap_me_mut {
($wrapper:expr, $name:ident, $body:block) => {
crate::unwrap_me_mut_body!($wrapper, $name, $body, PythonRandWrapper, { Std })
};
}
#[pymethods]
impl PythonRand {
#[staticmethod]
fn new_std(py_std_rand: Py<PythonStdRand>) -> Self {
Self {
wrapper: PythonRandWrapper::Std(py_std_rand),
}
}
}
impl Rand for PythonRand {
fn set_seed(&mut self, seed: u64) {
unwrap_me_mut!(self.wrapper, r, { r.set_seed(seed) });
}
#[inline]
fn next(&mut self) -> u64 {
unwrap_me_mut!(self.wrapper, r, { r.next() })
}
}
/// Register the classes to the python module
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PythonStdRand>()?;
m.add_class::<PythonRand>()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
@ -664,90 +766,40 @@ mod tests {
assert_eq!(v, u);
}
}
}
#[cfg(feature = "python")]
/// `Rand` Python bindings
pub mod pybind {
use pyo3::prelude::*;
#[test]
#[cfg(feature = "rand_trait")]
fn test_rand_trait() {
use rand_core::{RngCore, SeedableRng};
use serde::{Deserialize, Serialize};
use super::{Rand, StdRand, random_seed};
#[derive(Debug, Serialize, Deserialize)]
struct CountingRng(u64);
#[pyclass(unsendable, name = "StdRand")]
#[expect(clippy::unsafe_derive_deserialize)]
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Python class for StdRand
pub struct PythonStdRand {
/// Rust wrapped StdRand object
pub inner: StdRand,
impl RngCore for CountingRng {
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
#[pymethods]
impl PythonStdRand {
#[staticmethod]
fn with_random_seed() -> Self {
Self {
inner: StdRand::with_seed(random_seed()),
fn next_u64(&mut self) -> u64 {
self.0 += 1;
self.0
}
fn fill_bytes(&mut self, dst: &mut [u8]) {
rand_core::impls::fill_bytes_via_next(self, dst);
}
}
#[staticmethod]
fn with_seed(seed: u64) -> Self {
Self {
inner: StdRand::with_seed(seed),
impl SeedableRng for CountingRng {
type Seed = [u8; 8];
fn from_seed(seed: Self::Seed) -> Self {
Self(u64::from_le_bytes(seed))
}
}
fn as_rand(slf: Py<Self>) -> PythonRand {
PythonRand::new_std(slf)
}
}
#[derive(Serialize, Deserialize, Debug)]
enum PythonRandWrapper {
Std(Py<PythonStdRand>),
}
/// Rand Trait binding
#[pyclass(unsendable, name = "Rand")]
#[expect(clippy::unsafe_derive_deserialize)]
#[derive(Serialize, Deserialize, Debug)]
pub struct PythonRand {
wrapper: PythonRandWrapper,
}
macro_rules! unwrap_me_mut {
($wrapper:expr, $name:ident, $body:block) => {
crate::unwrap_me_mut_body!($wrapper, $name, $body, PythonRandWrapper, { Std })
};
}
#[pymethods]
impl PythonRand {
#[staticmethod]
fn new_std(py_std_rand: Py<PythonStdRand>) -> Self {
Self {
wrapper: PythonRandWrapper::Std(py_std_rand),
}
}
}
impl Rand for PythonRand {
fn set_seed(&mut self, seed: u64) {
unwrap_me_mut!(self.wrapper, r, { r.set_seed(seed) });
}
#[inline]
fn next(&mut self) -> u64 {
unwrap_me_mut!(self.wrapper, r, { r.next() })
}
}
/// Register the classes to the python module
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PythonStdRand>()?;
m.add_class::<PythonRand>()?;
Ok(())
// LibAFL's Rand trait is auto-implemented for all SeedableRng + RngCore types.
assert!(CountingRng(0).coinflip(0.1));
}
}

View File

@ -180,7 +180,6 @@ fn main() {
}
libfuzzer.compile("libfuzzer");
}
#[cfg(feature = "coverage")]
@ -222,7 +221,8 @@ fn main() {
cmplog.link_lib_modifier("+whole-archive");
}
cmplog.flag("-Wno-pointer-sign") // UNIX ONLY FLAGS
cmplog
.flag("-Wno-pointer-sign") // UNIX ONLY FLAGS
.flag("-Wno-sign-compare")
.define("CMP_MAP_SIZE", Some(&*format!("{cmp_map_size}")))
.define("CMPLOG_MAP_W", Some(&*format!("{cmplog_map_w}")))