diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index ae73618122..48be983f95 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -14,6 +14,14 @@ jobs: run: cargo build --verbose - name: Test run: cargo test --verbose + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Windows Build + run: cargo build --verbose + - name: Windows Test + run: cargo test --verbose all-features: runs-on: ubuntu-latest steps: diff --git a/fuzzers/libfuzzer_libmozjpeg/build.rs b/fuzzers/libfuzzer_libmozjpeg/build.rs index ee0062f3e3..96f09a9db0 100644 --- a/fuzzers/libfuzzer_libmozjpeg/build.rs +++ b/fuzzers/libfuzzer_libmozjpeg/build.rs @@ -1,12 +1,19 @@ // build.rs -use std::env; -use std::path::Path; -use std::process::Command; +use std::{ + env, + path::Path, + process::{exit, Command}, +}; const LIBMOZJPEG_URL: &str = "https://github.com/mozilla/mozjpeg/archive/v4.0.3.tar.gz"; fn main() { + if cfg!(windows) { + println!("cargo:warning=Skipping libmozjpeg example on Windows"); + exit(0); + } + let out_dir = env::var_os("OUT_DIR").unwrap(); let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); let out_dir = out_dir.to_string_lossy().to_string(); diff --git a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs index 58ec07983a..7e7e18dd89 100644 --- a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs @@ -3,6 +3,7 @@ use std::{env, path::PathBuf}; +#[cfg(unix)] use libafl::{ bolts::{shmem::UnixShMem, tuples::tuple_list}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler}, @@ -22,6 +23,7 @@ use libafl::{ }; /// We will interact with a C++ target, so use external c functionality +#[cfg(unix)] extern "C" { /// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32; @@ -35,6 +37,7 @@ extern "C" { } /// The wrapped harness function, calling out to the LLVM-style harness +#[cfg(unix)] fn harness(_executor: &E, buf: &[u8]) -> ExitKind where E: Executor, @@ -65,7 +68,14 @@ pub fn main() { .expect("An error occurred while fuzzing"); } +/// Not supported on windows right now +#[cfg(windows)] +fn fuzz(_corpus_dirs: Vec, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> { + todo!("Example not supported on Windows"); +} + /// The actual fuzzer +#[cfg(unix)] fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { // 'While the stats are state, they are usually used in the broker - which is likely never restarted let stats = SimpleStats::new(|s| println!("{}", s)); diff --git a/fuzzers/libfuzzer_libpng/build.rs b/fuzzers/libfuzzer_libpng/build.rs index 1b270dbd71..49f3cfba94 100644 --- a/fuzzers/libfuzzer_libpng/build.rs +++ b/fuzzers/libfuzzer_libpng/build.rs @@ -1,13 +1,20 @@ // build.rs -use std::env; -use std::path::Path; -use std::process::Command; +use std::{ + env, + path::Path, + process::{exit, Command}, +}; const LIBPNG_URL: &str = "https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz"; fn main() { + if cfg!(windows) { + println!("cargo:warning=Skipping libpng example on Windows"); + exit(0); + } + let out_dir = env::var_os("OUT_DIR").unwrap(); let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); let out_dir = out_dir.to_string_lossy().to_string(); diff --git a/fuzzers/libfuzzer_libpng/src/fuzzer.rs b/fuzzers/libfuzzer_libpng/src/fuzzer.rs index 35123a5611..90bb14ce5a 100644 --- a/fuzzers/libfuzzer_libpng/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libpng/src/fuzzer.rs @@ -3,6 +3,7 @@ use std::{env, path::PathBuf}; +#[cfg(unix)] use libafl::{ bolts::{shmem::UnixShMem, tuples::tuple_list}, corpus::{ @@ -24,6 +25,7 @@ use libafl::{ }; /// We will interact with a C++ target, so use external c functionality +#[cfg(unix)] extern "C" { /// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32; @@ -37,6 +39,7 @@ extern "C" { } /// The wrapped harness function, calling out to the LLVM-style harness +#[cfg(unix)] fn harness(_executor: &E, buf: &[u8]) -> ExitKind where E: Executor, @@ -67,7 +70,14 @@ pub fn main() { .expect("An error occurred while fuzzing"); } +/// Not supported on windows right now +#[cfg(windows)] +fn fuzz(_corpus_dirs: Vec, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> { + todo!("Example not supported on Windows"); +} + /// The actual fuzzer +#[cfg(unix)] fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { // 'While the stats are state, they are usually used in the broker - which is likely never restarted let stats = SimpleStats::new(|s| println!("{}", s)); diff --git a/libafl/examples/llmp_test/main.rs b/libafl/examples/llmp_test/main.rs index 5093dd468f..4dadc2ebf4 100644 --- a/libafl/examples/llmp_test/main.rs +++ b/libafl/examples/llmp_test/main.rs @@ -3,17 +3,21 @@ This shows how llmp can be used directly, without libafl abstractions */ extern crate alloc; +#[cfg(all(unix, feature = "std"))] use core::{convert::TryInto, time::Duration}; +#[cfg(all(unix, feature = "std"))] use std::{thread, time}; +#[cfg(all(unix, feature = "std"))] use libafl::{ bolts::{llmp, shmem::UnixShMem}, Error, }; -const TAG_SIMPLE_U32_V1: u32 = 0x51300321; -const TAG_MATH_RESULT_V1: u32 = 0x77474331; +const _TAG_SIMPLE_U32_V1: u32 = 0x51300321; +const _TAG_MATH_RESULT_V1: u32 = 0x77474331; +#[cfg(all(unix, feature = "std"))] fn adder_loop(port: u16) -> ! { let mut client = llmp::LlmpClient::::create_attach_to_tcp(port).unwrap(); let mut last_result: u32 = 0; @@ -27,7 +31,7 @@ fn adder_loop(port: u16) -> ! { }; msg_counter += 1; match tag { - TAG_SIMPLE_U32_V1 => { + _TAG_SIMPLE_U32_V1 => { current_result = current_result.wrapping_add(u32::from_le_bytes(buf.try_into().unwrap())); } @@ -42,7 +46,7 @@ fn adder_loop(port: u16) -> ! { ); client - .send_buf(TAG_MATH_RESULT_V1, ¤t_result.to_le_bytes()) + .send_buf(_TAG_MATH_RESULT_V1, ¤t_result.to_le_bytes()) .unwrap(); last_result = current_result; } @@ -51,13 +55,14 @@ fn adder_loop(port: u16) -> ! { } } +#[cfg(all(unix, feature = "std"))] fn broker_message_hook( client_id: u32, tag: llmp::Tag, message: &[u8], ) -> Result { match tag { - TAG_SIMPLE_U32_V1 => { + _TAG_SIMPLE_U32_V1 => { println!( "Client {:?} sent message: {:?}", client_id, @@ -65,7 +70,7 @@ fn broker_message_hook( ); Ok(llmp::LlmpMsgHookResult::ForwardToClients) } - TAG_MATH_RESULT_V1 => { + _TAG_MATH_RESULT_V1 => { println!( "Adder Client has this current result: {:?}", u32::from_le_bytes(message.try_into().unwrap()) @@ -79,6 +84,12 @@ fn broker_message_hook( } } +#[cfg(not(unix))] +fn main() { + todo!("LLMP is not yet supported on this platform."); +} + +#[cfg(unix)] fn main() { /* The main node has a broker, and a few worker threads */ @@ -108,7 +119,7 @@ fn main() { loop { counter = counter.wrapping_add(1); client - .send_buf(TAG_SIMPLE_U32_V1, &counter.to_le_bytes()) + .send_buf(_TAG_SIMPLE_U32_V1, &counter.to_le_bytes()) .unwrap(); println!("CTR Client writing {}", counter); thread::sleep(Duration::from_secs(1)) diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index bc6931aa9d..aad249cf40 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -74,7 +74,6 @@ use std::{ #[cfg(all(feature = "std", unix))] use nix::{ cmsg_space, - mem::zeroed, sys::{ socket::{recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags}, uio::IoVec, @@ -83,7 +82,9 @@ use nix::{ #[cfg(all(feature = "std", unix))] use std::{ ffi::CStr, + mem::zeroed, os::unix::{ + self, net::{UnixListener, UnixStream}, {io::AsRawFd, prelude::RawFd}, }, @@ -1330,6 +1331,7 @@ where /// Called from an interrupt: Sets broker `shutting_down` flag to `true`. /// Currently only supported on `std` unix systems. + #[cfg(all(feature = "std", unix))] fn shutdown(&mut self) { unsafe { ptr::write_volatile(&mut self.shutting_down, true) }; compiler_fence(Ordering::SeqCst); @@ -1928,22 +1930,20 @@ where } #[cfg(test)] +#[cfg(all(unix, feature = "std"))] mod tests { - #[cfg(feature = "std")] use std::{thread::sleep, time::Duration}; - #[cfg(feature = "std")] use super::{ LlmpClient, LlmpConnection::{self, IsBroker, IsClient}, LlmpMsgHookResult::ForwardToClients, Tag, }; - #[cfg(feature = "std")] + use crate::bolts::shmem::UnixShMem; - #[cfg(feature = "std")] #[test] pub fn llmp_connection() { let mut broker = match LlmpConnection::::on_port(1337).unwrap() { diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index 10a5ab7c2b..95c88c04b1 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -1,12 +1,10 @@ //! A generic sharememory region to be used by any functions (queues or feedbacks // too.) -#[cfg(feature = "std")] -#[cfg(unix)] +#[cfg(all(feature = "std", unix))] pub use unix_shmem::UnixShMem; -#[cfg(feature = "std")] -#[cfg(windows)] +#[cfg(all(windows, feature = "std"))] pub use shmem::Win32ShMem; use alloc::string::{String, ToString}; @@ -465,10 +463,10 @@ pub mod shmem { #[cfg(test)] mod tests { - #[cfg(feature = "std")] + #[cfg(all(unix, feature = "std"))] use super::{ShMem, UnixShMem}; - #[cfg(feature = "std")] + #[cfg(all(unix, feature = "std"))] #[test] fn test_str_conversions() { let mut shm_str: [u8; 20] = [0; 20]; diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 2ac79533dc..7219338e32 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -6,11 +6,13 @@ use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "std")] use crate::bolts::llmp::LlmpReceiver; -#[cfg(feature = "std")] -use std::{env, process::Command}; +#[cfg(all(feature = "std", windows))] +use crate::utils::startable_self; -#[cfg(feature = "std")] -#[cfg(unix)] +#[cfg(all(feature = "std", unix))] +use crate::utils::{fork, ForkResult}; + +#[cfg(all(feature = "std", unix))] use crate::bolts::shmem::UnixShMem; use crate::{ bolts::{ @@ -513,7 +515,7 @@ where let mut mgr; // We start ourself as child process to actually fuzz - if std::env::var(_ENV_FUZZER_SENDER).is_err() { + let (sender, mut receiver) = if std::env::var(_ENV_FUZZER_SENDER).is_err() { #[cfg(target_os = "android")] { let path = std::env::current_dir()?; @@ -549,22 +551,32 @@ where // Client->parent loop loop { dbg!("Spawning next client (id {})", ctr); - Command::new(env::current_exe()?) - .current_dir(env::current_dir()?) - .args(env::args()) - .status()?; + + // On Unix, we fork (todo: measure if that is actually faster.) + #[cfg(unix)] + let _ = match unsafe { fork() }? { + ForkResult::Parent(handle) => handle.status(), + ForkResult::Child => break (sender, receiver), + }; + + // On windows, we spawn ourself again + #[cfg(windows)] + startable_self()?.status()?; + ctr += 1; } } - } + } else { + // We are the newly started fuzzing instance, first, connect to our own restore map. + // A sender and a receiver for single communication + ( + LlmpSender::::on_existing_from_env(_ENV_FUZZER_SENDER)?, + LlmpReceiver::::on_existing_from_env(_ENV_FUZZER_RECEIVER)?, + ) + }; println!("We're a client, let's fuzz :)"); - // We are the fuzzing instance, first, connect to our own restore map. - // A sender and a receiver for single communication - let mut receiver = LlmpReceiver::::on_existing_from_env(_ENV_FUZZER_RECEIVER)?; - let sender = LlmpSender::::on_existing_from_env(_ENV_FUZZER_SENDER)?; - // If we're restarting, deserialize the old state. let (state, mut mgr) = match receiver.recv_buf()? { None => { diff --git a/libafl/src/utils.rs b/libafl/src/utils.rs index 405baca7e2..c1f655d6a1 100644 --- a/libafl/src/utils.rs +++ b/libafl/src/utils.rs @@ -4,8 +4,19 @@ use core::{cell::RefCell, debug_assert, fmt::Debug, time}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use xxhash_rust::xxh3::xxh3_64_with_seed; +#[cfg(unix)] +use alloc::string::ToString; +#[cfg(unix)] +use libc::pid_t; + +use crate::Error; + #[cfg(feature = "std")] -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + env, + process::Command, + time::{SystemTime, UNIX_EPOCH}, +}; pub trait AsSlice { /// Convert to a slice @@ -378,6 +389,53 @@ impl XKCDRand { } } +/// Child Process Handle +#[cfg(unix)] +pub struct ChildHandle { + pid: pid_t, +} + +#[cfg(unix)] +impl ChildHandle { + /// Block until the child exited and the status code becomes available + pub fn status(&self) -> i32 { + let mut status = -1; + unsafe { + libc::waitpid(self.pid, &mut status, 0); + } + status + } +} + +#[cfg(unix)] +/// The ForkResult +pub enum ForkResult { + Parent(ChildHandle), + Child, +} + +/// Unix has forks. +#[cfg(unix)] +pub unsafe fn fork() -> Result { + let pid = libc::fork(); + if pid < 0 { + Err(Error::Unknown("Fork failed".to_string())) + } else if pid == 0 { + Ok(ForkResult::Child) + } else { + Ok(ForkResult::Parent(ChildHandle { pid })) + } +} + +/// Executes the current process from the beginning, as subprocess. +/// use `start_self.status()?` to wait for the child +#[cfg(feature = "std")] +pub fn startable_self() -> Result { + let mut startable = Command::new(env::current_exe()?); + startable.current_dir(env::current_dir()?).args(env::args()); + Ok(startable) +} + #[cfg(test)] mod tests { //use xxhash_rust::xxh3::xxh3_64_with_seed;