diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 58ca7b6077..8904b329e9 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -49,6 +49,8 @@ jobs: run: cargo test - name: Test libafl no_std run: cd libafl && cargo test --no-default-features + - name: Test libafl_bolts no_std no_alloc + run: cd libafl_bolts && cargo test --no-default-features - name: Test libafl_targets no_std run: cd libafl_targets && cargo test --no-default-features @@ -285,6 +287,8 @@ jobs: run: cd ./libafl && cargo test --no-default-features - name: libafl armv6m-none-eabi (32 bit no_std) clippy run: cd ./libafl && cargo clippy --target thumbv6m-none-eabi --no-default-features + - name: Build no_std no_alloc bolts + run: cd ./libafl_bolts && cargo +nightly build -Zbuild-std=core --target aarch64-unknown-none --no-default-features -v --release && cd ../ build-docker: runs-on: ubuntu-latest diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index ff5bdac128..50c866561c 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -61,7 +61,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] } bytecount = "0.6.3" [dependencies] -libafl_bolts = { version = "0.10.1", path = "../libafl_bolts", default-features = false } +libafl_bolts = { version = "0.10.1", path = "../libafl_bolts", default-features = false, features = ["alloc"] } libafl_derive = { version = "0.10.1", path = "../libafl_derive", optional = true } rustversion = "1.0" diff --git a/libafl/build.rs b/libafl/build.rs index 05b7c894bc..420e392a43 100644 --- a/libafl/build.rs +++ b/libafl/build.rs @@ -1,7 +1,7 @@ #[rustversion::nightly] fn main() { println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rustc-cfg=unstable_feature"); + println!("cargo:rustc-cfg=nightly"); } #[rustversion::not(nightly)] diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index ab169b770a..53af2add39 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -5,11 +5,11 @@ Welcome to `LibAFL` #![allow(incomplete_features)] #![no_std] // For `type_eq` -#![cfg_attr(unstable_feature, feature(specialization))] +#![cfg_attr(nightly, feature(specialization))] // For `type_id` and owned things -#![cfg_attr(unstable_feature, feature(intrinsics))] +#![cfg_attr(nightly, feature(intrinsics))] // For `std::simd` -#![cfg_attr(unstable_feature, feature(portable_simd))] +#![cfg_attr(nightly, feature(portable_simd))] #![warn(clippy::cargo)] #![allow(ambiguous_glob_reexports)] #![deny(clippy::cargo_common_metadata)] diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 76801e4530..b9a087b986 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -21,9 +21,9 @@ pub mod concolic; pub mod value; // Rust is breaking this with 'error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `type_id`' and so we disable this component for the moment -//#[cfg(unstable_feature)] +//#[cfg(nightly)] //pub mod owned; -//#[cfg(unstable_feature)] +//#[cfg(nightly)] //pub use owned::*; use alloc::{ string::{String, ToString}, diff --git a/libafl_bolts/Cargo.toml b/libafl_bolts/Cargo.toml index 3cb5ccce8b..09ec7512a6 100644 --- a/libafl_bolts/Cargo.toml +++ b/libafl_bolts/Cargo.toml @@ -12,14 +12,15 @@ edition = "2021" categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"] [features] -default = ["std", "derive", "llmp_compression", "llmp_small_maps", "rand_trait", "prelude", "gzip", "serdeany_autoreg"] -std = ["serde_json", "serde_json/std", "hostname", "nix", "serde/std", "once_cell", "uuid", "byteorder", "backtrace", "uds", "serial_test"] # print, env, launcher ... support +default = ["std", "derive", "llmp_compression", "llmp_small_maps", "rand_trait", "prelude", "gzip", "serdeany_autoreg", "alloc"] +std = ["serde_json", "serde_json/std", "hostname", "nix", "serde/std", "once_cell", "uuid", "byteorder", "backtrace", "uds", "serial_test", "alloc"] # print, env, ... support +alloc = ["serde/alloc", "hashbrown", "postcard", "erased-serde/alloc", "ahash"] # Enables all features that allocate in no_std derive = ["libafl_derive"] # provide derive(SerdeAny) macro. rand_trait = ["rand_core"] # If set, libafl's rand implementations will implement `rand::Rng` -python = ["pyo3"] +python = ["pyo3", "std"] prelude = [] # Expose libafl::prelude for access without additional using directives cli = ["clap"] # expose libafl_bolts::cli for easy commandline parsing -qemu_cli = ["cli"] # Commandline flags for qemu-based fuzzers +qemu_cli = ["cli"] # Commandline flagr for qemu-based fuzzers frida_cli = ["cli"] # Commandline flags for frida-based fuzzers errors_backtrace = ["backtrace"] gzip = ["miniz_oxide"] # Enables gzip compression in certain parts of the lib @@ -28,10 +29,10 @@ gzip = ["miniz_oxide"] # Enables gzip compression in certain parts of the lib serdeany_autoreg = ["ctor"] # Automatically register all `#[derive(SerdeAny)]` types at startup. # LLMP features -llmp_bind_public = [] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default. -llmp_compression = ["gzip"] # llmp compression using GZip -llmp_debug = [] # Enables debug output for LLMP -llmp_small_maps = [] # reduces initial map size for llmp +llmp_bind_public = ["alloc"] # If set, llmp will bind to 0.0.0.0, allowing cross-device communication. Binds to localhost by default. +llmp_compression = ["alloc", "gzip"] # llmp compression using GZip +llmp_debug = ["alloc"] # Enables debug output for LLMP +llmp_small_maps = ["alloc"] # reduces initial map size for llmp [build-dependencies] rustversion = "1.0" @@ -45,13 +46,13 @@ libafl_derive = { version = "0.10.1", optional = true, path = "../libafl_derive" rustversion = "1.0" tuple_list = { version = "0.1.3" } -hashbrown = { version = "0.14", features = ["serde", "ahash"], default-features=false } # A faster hashmap, nostd compatible +hashbrown = { version = "0.14", features = ["serde", "ahash"], default-features=false, optional = true } # A faster hashmap, nostd compatible xxhash-rust = { version = "0.8.5", features = ["xxh3"] } # xxh3 hashing for rust -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } # serialization lib -erased-serde = { version = "0.3.21", default-features = false, features = ["alloc"] } # erased serde -postcard = { version = "1.0", features = ["alloc"] } # no_std compatible serde serialization format +serde = { version = "1.0", default-features = false, features = ["derive"] } # serialization lib +erased-serde = { version = "0.3.21", default-features = false, optional = true } # erased serde +postcard = { version = "1.0", features = ["alloc"], optional = true } # no_std compatible serde serialization format num_enum = { version = "0.5.7", default-features = false } -ahash = { version = "0.8", default-features=false } # The hash function already used in hashbrown +ahash = { version = "0.8", default-features=false, optional = true } # The hash function already used in hashbrown backtrace = {version = "0.3", optional = true} # Used to get the stacktrace in StacktraceObserver ctor = { optional = true, version = "0.2" } diff --git a/libafl_bolts/build.rs b/libafl_bolts/build.rs index 19496e79a0..c4fe7f650f 100644 --- a/libafl_bolts/build.rs +++ b/libafl_bolts/build.rs @@ -1,7 +1,7 @@ #[rustversion::nightly] fn main() { println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rustc-cfg=unstable_feature"); + println!("cargo:rustc-cfg=nightly"); } #[rustversion::not(nightly)] diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index 8fc7e114d1..1f27b00c17 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -7,11 +7,13 @@ Welcome to `LibAFL` #![allow(incomplete_features)] #![no_std] // For `type_eq` -#![cfg_attr(unstable_feature, feature(specialization))] +#![cfg_attr(nightly, feature(specialization))] // For `type_id` and owned things -#![cfg_attr(unstable_feature, feature(intrinsics))] +#![cfg_attr(nightly, feature(intrinsics))] // For `std::simd` -#![cfg_attr(unstable_feature, feature(portable_simd))] +#![cfg_attr(nightly, feature(portable_simd))] +// For `core::error` +#![cfg_attr(nightly, feature(error_in_core))] #![warn(clippy::cargo)] #![allow(ambiguous_glob_reexports)] #![deny(clippy::cargo_common_metadata)] @@ -78,6 +80,7 @@ Welcome to `LibAFL` #[cfg(feature = "std")] #[macro_use] extern crate std; +#[cfg(feature = "alloc")] #[macro_use] #[doc(hidden)] pub extern crate alloc; @@ -98,10 +101,11 @@ pub mod launcher {} #[allow(unused_imports)] #[macro_use] extern crate libafl_derive; +#[cfg(feature = "alloc")] use alloc::string::{FromUtf8Error, String}; use core::{ array::TryFromSliceError, - fmt, + fmt::{self, Display}, num::{ParseIntError, TryFromIntError}, }; #[cfg(feature = "std")] @@ -110,6 +114,21 @@ use std::{env::VarError, io}; #[cfg(feature = "libafl_derive")] pub use libafl_derive::SerdeAny; +/// We need some sort of "[`String`]" for errors in `no_alloc`... +/// We can only support `'static` without allocator, so let's do that. +#[cfg(not(feature = "alloc"))] +type String = &'static str; + +/// We also need a non-allocating format... +/// This one simply returns the `fmt` string. +/// Good enough for simple errors, for anything else, use the `alloc` feature. +#[cfg(not(feature = "alloc"))] +macro_rules! format { + ($fmt:literal) => {{ + $fmt + }}; +} + /// We need fixed names for many parts of this lib. pub trait Named { /// Provide the name of this element. @@ -276,7 +295,7 @@ impl Error { } } -impl fmt::Display for Error { +impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Serialize(s, b) => { @@ -339,6 +358,7 @@ impl fmt::Display for Error { } /// Stringify the postcard serializer error +#[cfg(feature = "alloc")] impl From for Error { fn from(err: postcard::Error) -> Self { Self::serialize(format!("{err:?}")) @@ -368,7 +388,9 @@ impl From for Error { } } +#[cfg(feature = "alloc")] impl From for Error { + #[allow(unused_variables)] fn from(err: FromUtf8Error) -> Self { Self::unknown(format!("Could not convert byte / utf-8: {err:?}")) } @@ -376,24 +398,28 @@ impl From for Error { #[cfg(feature = "std")] impl From for Error { + #[allow(unused_variables)] fn from(err: VarError) -> Self { Self::empty(format!("Could not get env var: {err:?}")) } } impl From for Error { + #[allow(unused_variables)] fn from(err: ParseIntError) -> Self { Self::unknown(format!("Failed to parse Int: {err:?}")) } } impl From for Error { + #[allow(unused_variables)] fn from(err: TryFromIntError) -> Self { Self::illegal_state(format!("Expected conversion failed: {err:?}")) } } impl From for Error { + #[allow(unused_variables)] fn from(err: TryFromSliceError) -> Self { Self::illegal_argument(format!("Could not convert slice: {err:?}")) } @@ -401,6 +427,7 @@ impl From for Error { #[cfg(windows)] impl From for Error { + #[allow(unused_variables)] fn from(err: windows::core::Error) -> Self { Self::unknown(format!("Windows API error: {err:?}")) } @@ -422,9 +449,12 @@ impl From for Error { } } -#[cfg(feature = "std")] +#[cfg(all(not(nightly), feature = "std"))] impl std::error::Error for Error {} +#[cfg(nightly)] +impl core::error::Error for Error {} + /// The purpose of this module is to alleviate imports of many components by adding a glob import. #[cfg(feature = "prelude")] pub mod prelude { @@ -439,6 +469,7 @@ pub unsafe extern "C" fn external_current_millis() -> u64 { 1000 } +#[cfg(feature = "alloc")] pub mod anymap; #[cfg(feature = "std")] pub mod build_id; @@ -454,18 +485,22 @@ pub mod core_affinity; pub mod cpu; #[cfg(feature = "std")] pub mod fs; +#[cfg(feature = "alloc")] pub mod llmp; #[cfg(all(feature = "std", unix))] pub mod minibsod; pub mod os; +#[cfg(feature = "alloc")] pub mod ownedref; pub mod rands; +#[cfg(feature = "alloc")] pub mod serdeany; pub mod shmem; #[cfg(feature = "std")] pub mod staterestore; pub mod tuples; +#[cfg(feature = "alloc")] use alloc::vec::Vec; use core::{iter::Iterator, ops::AddAssign, time}; #[cfg(feature = "std")] @@ -499,6 +534,7 @@ pub trait AsMutSlice { fn as_mut_slice(&mut self) -> &mut [Self::Entry]; } +#[cfg(feature = "alloc")] impl AsSlice for Vec { type Entry = T; @@ -507,6 +543,7 @@ impl AsSlice for Vec { } } +#[cfg(feature = "alloc")] impl AsMutSlice for Vec { type Entry = T; @@ -653,6 +690,7 @@ pub fn current_milliseconds() -> u64 { } /// Format a `Duration` into a HMS string +#[cfg(feature = "alloc")] #[must_use] pub fn format_duration_hms(duration: &time::Duration) -> String { let secs = duration.as_secs(); @@ -802,9 +840,9 @@ pub mod bolts_prelude { pub use super::minibsod::*; #[cfg(feature = "std")] pub use super::staterestore::*; - pub use super::{ - anymap::*, cpu::*, llmp::*, os::*, ownedref::*, rands::*, serdeany::*, shmem::*, tuples::*, - }; + #[cfg(feature = "alloc")] + pub use super::{anymap::*, llmp::*, ownedref::*, rands::*, serdeany::*, shmem::*, tuples::*}; + pub use super::{cpu::*, os::*, rands::*}; } #[cfg(feature = "python")] diff --git a/libafl_bolts/src/os/unix_signals.rs b/libafl_bolts/src/os/unix_signals.rs index affcaaae9f..6f4eb87d48 100644 --- a/libafl_bolts/src/os/unix_signals.rs +++ b/libafl_bolts/src/os/unix_signals.rs @@ -1,12 +1,16 @@ //! Signal handling for unix +#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] use core::{ cell::UnsafeCell, - fmt::{self, Display, Formatter}, - mem, ptr, - ptr::{addr_of_mut, write_volatile}, + ptr::{self, addr_of_mut, write_volatile}, sync::atomic::{compiler_fence, Ordering}, }; +use core::{ + fmt::{self, Display, Formatter}, + mem, +}; #[cfg(feature = "std")] use std::ffi::CString; @@ -241,11 +245,15 @@ use libc::ssize_t; )))] pub use libc::ucontext_t; use libc::{ - c_int, malloc, sigaction, sigaddset, sigaltstack, sigemptyset, stack_t, SA_NODEFER, SA_ONSTACK, - SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, - SIGQUIT, SIGSEGV, SIGTERM, SIGTRAP, SIGUSR2, + c_int, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGQUIT, + SIGSEGV, SIGTERM, SIGTRAP, SIGUSR2, }; pub use libc::{c_void, siginfo_t}; +#[cfg(feature = "alloc")] +use libc::{ + malloc, sigaction, sigaddset, sigaltstack, sigemptyset, stack_t, SA_NODEFER, SA_ONSTACK, + SA_SIGINFO, +}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use crate::Error; @@ -335,6 +343,7 @@ impl Display for Signal { } /// A trait for `LibAFL` signal handling +#[cfg(feature = "alloc")] pub trait Handler { /// Handle a signal fn handle(&mut self, signal: Signal, info: siginfo_t, _context: &mut ucontext_t); @@ -342,18 +351,24 @@ pub trait Handler { fn signals(&self) -> Vec; } +#[cfg(feature = "alloc")] struct HandlerHolder { handler: UnsafeCell<*mut dyn Handler>, } +#[cfg(feature = "alloc")] unsafe impl Send for HandlerHolder {} /// Let's get 8 mb for now. +#[cfg(feature = "alloc")] const SIGNAL_STACK_SIZE: usize = 2 << 22; + /// To be able to handle SIGSEGV when the stack is exhausted, we need our own little stack space. +#[cfg(feature = "alloc")] static mut SIGNAL_STACK_PTR: *mut c_void = ptr::null_mut(); /// Keep track of which handler is registered for which signal +#[cfg(feature = "alloc")] static mut SIGNAL_HANDLERS: [Option; 32] = [ // We cannot use [None; 32] because it requires Copy. Ugly, but I don't think there's an // alternative. @@ -362,9 +377,11 @@ static mut SIGNAL_HANDLERS: [Option; 32] = [ ]; /// Internal function that is being called whenever a signal we are registered for arrives. +/// /// # Safety /// This should be somewhat safe to call for signals previously registered, /// unless the signal handlers registered using [`setup_signal_handler()`] are broken. +#[cfg(feature = "alloc")] unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: *mut c_void) { let signal = &Signal::try_from(sig).unwrap(); let handler = { @@ -385,6 +402,7 @@ unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: *mut c_void) { /// /// The signal handlers will be called on any signal. They should (tm) be async safe. /// A lot can go south in signal handling. Be sure you know what you are doing. +#[cfg(feature = "alloc")] pub unsafe fn setup_signal_handler(handler: &mut T) -> Result<(), Error> { // First, set up our own stack to be used during segfault handling. (and specify `SA_ONSTACK` in `sigaction`) if SIGNAL_STACK_PTR.is_null() { diff --git a/libafl_bolts/src/os/windows_exceptions.rs b/libafl_bolts/src/os/windows_exceptions.rs index 6737da85c9..900d5b9a07 100644 --- a/libafl_bolts/src/os/windows_exceptions.rs +++ b/libafl_bolts/src/os/windows_exceptions.rs @@ -1,5 +1,6 @@ //! Exception handling for Windows +#[cfg(feature = "alloc")] use alloc::vec::Vec; use core::{ cell::UnsafeCell, @@ -282,6 +283,7 @@ pub static EXCEPTION_CODES_MAPPING: [ExceptionCode; 47] = [ ExceptionCode::Other, ]; +#[cfg(feature = "alloc")] pub trait Handler { /// Handle an exception fn handle( @@ -366,6 +368,7 @@ unsafe extern "C" fn handle_signal(_signum: i32) { /// Setup Win32 exception handlers in a somewhat rusty way. /// # Safety /// Exception handlers are usually ugly, handle with care! +#[cfg(feature = "alloc")] pub unsafe fn setup_exception_handler(handler: &mut T) -> Result<(), Error> { let exceptions = handler.exceptions(); let mut catch_assertions = false; diff --git a/libafl_bolts/src/shmem.rs b/libafl_bolts/src/shmem.rs index 9cceaaee8a..a1c7724ccc 100644 --- a/libafl_bolts/src/shmem.rs +++ b/libafl_bolts/src/shmem.rs @@ -1,12 +1,13 @@ //! A generic shared memory region to be used by any functions (queues or feedbacks //! too.) +#[cfg(feature = "alloc")] use alloc::{rc::Rc, string::ToString}; -use core::{ - cell::RefCell, - fmt::{self, Debug, Display}, - mem::ManuallyDrop, -}; +use core::fmt::Debug; +#[cfg(feature = "alloc")] +use core::fmt::Display; +#[cfg(feature = "alloc")] +use core::{cell::RefCell, fmt, mem::ManuallyDrop}; #[cfg(feature = "std")] use std::env; #[cfg(all(unix, feature = "std"))] @@ -105,6 +106,7 @@ impl ShMemId { } /// Create a new id from an int + #[cfg(feature = "alloc")] #[must_use] pub fn from_int(val: i32) -> Self { Self::from_string(&val.to_string()) @@ -140,6 +142,7 @@ impl ShMemId { } /// Returns a `str` representation of this [`ShMemId`] + #[cfg(feature = "alloc")] #[must_use] pub fn as_str(&self) -> &str { alloc::str::from_utf8(&self.id[..self.null_pos()]).unwrap() @@ -152,12 +155,14 @@ impl AsSlice for ShMemId { } } +#[cfg(feature = "alloc")] impl From for i32 { fn from(id: ShMemId) -> i32 { id.as_str().parse().unwrap() } } +#[cfg(feature = "alloc")] impl Display for ShMemId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) @@ -302,12 +307,14 @@ pub trait ShMemProvider: Clone + Default + Debug { /// A Reference Counted shared map, /// that can use internal mutability. /// Useful if the `ShMemProvider` needs to keep local state. +#[cfg(feature = "alloc")] #[derive(Debug, Clone)] pub struct RcShMem { internal: ManuallyDrop, provider: Rc>, } +#[cfg(feature = "alloc")] impl ShMem for RcShMem where T: ShMemProvider + Debug, @@ -321,6 +328,7 @@ where } } +#[cfg(feature = "alloc")] impl AsSlice for RcShMem where T: ShMemProvider + Debug, @@ -331,6 +339,7 @@ where } } +#[cfg(feature = "alloc")] impl AsMutSlice for RcShMem where T: ShMemProvider + Debug, @@ -341,6 +350,7 @@ where } } +#[cfg(feature = "alloc")] impl Drop for RcShMem { fn drop(&mut self) { self.provider.borrow_mut().release_shmem(&mut self.internal); diff --git a/libafl_bolts/src/tuples.rs b/libafl_bolts/src/tuples.rs index 5d79c7a8ce..fa956f156b 100644 --- a/libafl_bolts/src/tuples.rs +++ b/libafl_bolts/src/tuples.rs @@ -526,14 +526,28 @@ impl PlusOne for (Head, Tail) where #[cfg(test)] mod test { - use crate::{ownedref::OwnedMutSlice, tuples::type_eq}; + #[cfg(feature = "alloc")] + use crate::ownedref::OwnedMutSlice; + use crate::tuples::type_eq; - /// An alias for equality testing - type OwnedMutSliceAlias<'a> = OwnedMutSlice<'a, u8>; + #[test] + #[allow(unused_qualifications)] // for type name tests + fn test_type_eq_simple() { + // test eq + assert!(type_eq::()); + + // test neq + assert!(!type_eq::()); + } #[test] + #[cfg(feature = "alloc")] #[allow(unused_qualifications)] // for type name tests fn test_type_eq() { + // An alias for equality testing + type OwnedMutSliceAlias<'a> = OwnedMutSlice<'a, u8>; + + // A function for lifetime testing #[allow(clippy::extra_unused_lifetimes)] fn test_lifetimes<'a, 'b>() { assert!(type_eq::, OwnedMutSlice<'b, u8>>()); @@ -545,12 +559,6 @@ mod test { assert!(type_eq::, OwnedMutSliceAlias>()); test_lifetimes(); - // test eq - assert!(type_eq::()); - - // test neq - assert!(!type_eq::()); - // test weirder lifetime things assert!(type_eq::, OwnedMutSlice>()); assert!(!type_eq::, OwnedMutSlice>());