diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml
index ed4fe610f8..d5331c1266 100644
--- a/libafl/Cargo.toml
+++ b/libafl/Cargo.toml
@@ -13,8 +13,9 @@ categories = ["development-tools::testing", "emulators", "embedded", "os", "no-s
[features]
default = ["std", "derive", "llmp_compression", "rand_trait", "fork", "prelude"]
-std = ["serde_json", "serde_json/std", "hostname", "nix", "serde/std", "bincode", "wait-timeout", "regex", "byteorder", "once_cell", "uuid", "tui_monitor", "ctor", "backtrace", "uds"] # print, env, launcher ... support
+std = ["serde_json", "serde_json/std", "hostname", "nix", "serde/std", "bincode", "wait-timeout", "regex", "byteorder", "once_cell", "uuid", "tui_monitor", "ctor", "backtrace", "uds", "input_conversion"] # print, env, launcher ... support
derive = ["libafl_derive"] # provide derive(SerdeAny) macro.
+input_conversion = ["inventory", "downcast-rs", "ctor"]
fork = [] # uses the fork() syscall to spawn children, instead of launching a new command, if supported by the OS (has no effect on Windows, no_std).
rand_trait = ["rand_core"] # If set, libafl's rand implementations will implement `rand::Rng`
introspection = [] # Include performance statistics of the fuzzing pipeline
@@ -94,6 +95,9 @@ z3 = { version = "0.11", features = ["static-link-z3"], optional = true } # for
pyo3 = { version = "0.17", optional = true, features = ["serde", "macros"] }
concat-idents = { version = "1.1.3", optional = true }
+inventory = { version = "0.3.2", optional = true }
+downcast-rs = { version = "1.2.0", optional = true }
+
# AGPL
# !!! this create requires nightly
grammartec = { version = "0.2", optional = true }
diff --git a/libafl/src/bolts/serdeany.rs b/libafl/src/bolts/serdeany.rs
index 60d6bee70a..1c717fec73 100644
--- a/libafl/src/bolts/serdeany.rs
+++ b/libafl/src/bolts/serdeany.rs
@@ -605,7 +605,7 @@ create_serde_registry_for_trait!(serdeany_registry, crate::bolts::serdeany::Serd
pub use serdeany_registry::*;
/// Register a `SerdeAny` type in the [`RegistryBuilder`]
-#[cfg(feature = "std")]
+#[cfg(feature = "ctor")]
#[macro_export]
macro_rules! register_at_startup {
($struct_type:ty) => {
@@ -619,7 +619,7 @@ macro_rules! register_at_startup {
}
/// Do nothing for `no_std`, you have to register it manually in `main()` with [`RegistryBuilder::register`]
-#[cfg(not(feature = "std"))]
+#[cfg(not(feature = "ctor"))]
#[macro_export]
macro_rules! register_at_startup {
($struct_type:ty) => {};
diff --git a/libafl/src/inputs/bytes.rs b/libafl/src/inputs/bytes.rs
index 9e3b8353ed..224243d0b0 100644
--- a/libafl/src/inputs/bytes.rs
+++ b/libafl/src/inputs/bytes.rs
@@ -1,16 +1,20 @@
//! The `BytesInput` is the "normal" input, a map of bytes, that can be sent directly to the client
//! (As opposed to other, more abstract, inputs, like an Grammar-Based AST Input)
-use alloc::{borrow::ToOwned, rc::Rc, string::String, vec::Vec};
+use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, vec::Vec};
use core::{cell::RefCell, convert::From, hash::Hasher};
#[cfg(feature = "std")]
use std::{fs::File, io::Read, path::Path};
use ahash::AHasher;
+#[cfg(feature = "input_conversion")]
+use postcard::{de_flavors::Slice, Deserializer};
use serde::{Deserialize, Serialize};
+#[cfg(feature = "input_conversion")]
+use crate::inputs::ConvertibleInput;
#[cfg(feature = "std")]
-use crate::{bolts::fs::write_file_atomic, Error};
+use crate::{bolts::fs::write_file_atomic, bolts::AsSlice, Error};
use crate::{
bolts::{ownedref::OwnedSlice, HasLen},
inputs::{HasBytesVec, HasTargetBytes, Input},
@@ -24,6 +28,8 @@ pub struct BytesInput {
}
impl Input for BytesInput {
+ const NAME: &'static str = "BytesInput";
+
#[cfg(feature = "std")]
/// Write this input to the file
fn to_file
(&self, path: P) -> Result<(), Error>
@@ -53,6 +59,24 @@ impl Input for BytesInput {
}
}
+/// Dynamic deserialisation of any input type that has target bytes
+#[cfg(feature = "input_conversion")]
+pub fn target_bytes_to_bytes Deserialize<'a>>(
+ buf: &[u8],
+) -> Result, <&mut Deserializer as serde::de::Deserializer>::Error>
+{
+ let orig: I = postcard::from_bytes(buf)?;
+ Ok(Box::new(BytesInput {
+ bytes: orig.target_bytes().as_slice().to_vec(),
+ }))
+}
+
+#[cfg(feature = "input_conversion")]
+inventory::submit! {
+ use crate::inputs::{GeneralizedInput, InputConversion};
+ InputConversion::new(GeneralizedInput::NAME, BytesInput::NAME, target_bytes_to_bytes::)
+}
+
/// Rc Ref-cell from Input
impl From for Rc> {
fn from(input: BytesInput) -> Self {
@@ -105,3 +129,33 @@ impl BytesInput {
Self { bytes }
}
}
+
+#[cfg(test)]
+mod test {
+ use alloc::vec::Vec;
+
+ use crate::{
+ bolts::AsSlice,
+ inputs::{BytesInput, GeneralizedInput, HasTargetBytes, Input, NopInput},
+ };
+
+ #[test]
+ fn deserialize_generalised_to_bytes() {
+ let generalised = GeneralizedInput::new(b"hello".to_vec());
+ let mut buf = Vec::new();
+ generalised.serialize_dynamic(&mut buf).unwrap();
+ let bytes = BytesInput::deserialize_dynamic(&buf).unwrap().unwrap();
+ assert_eq!(bytes.target_bytes().as_slice(), b"hello");
+ }
+
+ #[test]
+ fn failed_deserialize_from_nop() {
+ // note that NopInput implements HasTargetBytes, but because we have not submitted the
+ // conversion BytesInput cannot be converted from NopInput
+
+ let nop = NopInput {};
+ let mut buf = Vec::new();
+ nop.serialize_dynamic(&mut buf).unwrap();
+ assert!(BytesInput::deserialize_dynamic(&buf).unwrap().is_none());
+ }
+}
diff --git a/libafl/src/inputs/encoded.rs b/libafl/src/inputs/encoded.rs
index b73bb8cd3f..4c63acf61b 100644
--- a/libafl/src/inputs/encoded.rs
+++ b/libafl/src/inputs/encoded.rs
@@ -196,6 +196,8 @@ pub struct EncodedInput {
}
impl Input for EncodedInput {
+ const NAME: &'static str = "EncodedInput";
+
/// Generate a name for this input
#[must_use]
fn generate_name(&self, _idx: usize) -> String {
diff --git a/libafl/src/inputs/generalized.rs b/libafl/src/inputs/generalized.rs
index a32040b9c2..23fed59d6f 100644
--- a/libafl/src/inputs/generalized.rs
+++ b/libafl/src/inputs/generalized.rs
@@ -35,6 +35,8 @@ pub struct GeneralizedInput {
}
impl Input for GeneralizedInput {
+ const NAME: &'static str = "GeneralizedInput";
+
/// Generate a name for this input
fn generate_name(&self, _idx: usize) -> String {
let mut hasher = AHasher::new_with_keys(0, 0);
diff --git a/libafl/src/inputs/gramatron.rs b/libafl/src/inputs/gramatron.rs
index a6c73085e1..332379cdf0 100644
--- a/libafl/src/inputs/gramatron.rs
+++ b/libafl/src/inputs/gramatron.rs
@@ -38,6 +38,8 @@ pub struct GramatronInput {
}
impl Input for GramatronInput {
+ const NAME: &'static str = "GramatronInput";
+
/// Generate a name for this input
#[must_use]
fn generate_name(&self, _idx: usize) -> String {
diff --git a/libafl/src/inputs/mod.rs b/libafl/src/inputs/mod.rs
index aae307ea5f..955035fe63 100644
--- a/libafl/src/inputs/mod.rs
+++ b/libafl/src/inputs/mod.rs
@@ -15,15 +15,22 @@ pub use generalized::*;
#[cfg(feature = "nautilus")]
pub mod nautilus;
use alloc::{
+ boxed::Box,
string::{String, ToString},
vec::Vec,
};
-use core::{clone::Clone, fmt::Debug};
+use core::{
+ clone::Clone,
+ fmt::{Debug, Formatter},
+};
#[cfg(feature = "std")]
use std::{fs::File, hash::Hash, io::Read, path::Path};
+#[cfg(feature = "input_conversion")]
+use downcast_rs::{impl_downcast, Downcast};
#[cfg(feature = "nautilus")]
pub use nautilus::*;
+use postcard::{de_flavors::Slice, Deserializer};
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
@@ -33,6 +40,9 @@ use crate::{bolts::ownedref::OwnedSlice, Error};
/// An input for the target
#[cfg(not(feature = "std"))]
pub trait Input: Clone + Serialize + serde::de::DeserializeOwned + Debug {
+ /// Name for this input type
+ const NAME: &'static str;
+
/// Write this input to the file
fn to_file(&self, _path: P) -> Result<(), Error> {
Err(Error::not_implemented("Not supported in no_std"))
@@ -52,7 +62,12 @@ pub trait Input: Clone + Serialize + serde::de::DeserializeOwned + Debug {
/// An input for the target
#[cfg(feature = "std")]
-pub trait Input: Clone + Serialize + serde::de::DeserializeOwned + Debug {
+pub trait Input:
+ Clone + ConvertibleInput + Serialize + serde::de::DeserializeOwned + Debug
+{
+ /// Name for this input type
+ const NAME: &'static str;
+
/// Write this input to the file
fn to_file
(&self, path: P) -> Result<(), Error>
where
@@ -72,6 +87,20 @@ pub trait Input: Clone + Serialize + serde::de::DeserializeOwned + Debug {
Ok(postcard::from_bytes(&bytes)?)
}
+ /// Serializes this input to the dynamic serialisation format to pass between different fuzzers
+ fn serialize_dynamic(&self, buf: &mut Vec) -> Result<(), postcard::Error> {
+ buf.extend_from_slice(postcard::to_allocvec(Self::NAME)?.as_slice());
+ buf.extend_from_slice(postcard::to_allocvec(self)?.as_slice());
+ Ok(())
+ }
+
+ /// Deserializes this input type from the dynamic serialization format, if possible
+ fn deserialize_dynamic(
+ buf: &[u8],
+ ) -> Result