Skip to content

Commit

Permalink
Multipart Input support (#1617)
Browse files Browse the repository at this point in the history
* initial commit: multipart

* document + wrap up baby fuzzer

* oops

* core

* add from method, option to iter

* improve example; use minmap; fix initial_mut

* bindings

* clippy, again

* moar clippy

* fmt

* drop rand dep because we don't need it, actually

* docfix

* ok actually fix docs pls
  • Loading branch information
addisoncrump authored Jan 4, 2024
1 parent 75fcd47 commit 99fd69a
Show file tree
Hide file tree
Showing 12 changed files with 801 additions and 54 deletions.
1 change: 1 addition & 0 deletions fuzzers/baby_fuzzer_multi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libpng-*
24 changes: 24 additions & 0 deletions fuzzers/baby_fuzzer_multi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "baby_fuzzer_multi"
version = "0.10.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>", "Addison Crump <me@addisoncrump.info>"]
edition = "2021"

[features]
default = ["std"]
tui = []
std = []

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3
debug = true

[dependencies]
libafl = { path = "../../libafl/", features = ["multipart_inputs"] }
libafl_bolts = { path = "../../libafl_bolts/" }
10 changes: 10 additions & 0 deletions fuzzers/baby_fuzzer_multi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Baby fuzzer multi

This is a minimalistic example about how to create a libafl based fuzzer for targets with multipart inputs.

It runs on a single core until a crash occurs and then exits.

The tested program is a simple Rust function without any instrumentation.
For real fuzzing, you will want to add some sort to add coverage or other feedback.

You can run this example using `cargo run`, and you can enable the TUI feature by running `cargo run --features tui`.
164 changes: 164 additions & 0 deletions fuzzers/baby_fuzzer_multi/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#[cfg(windows)]
use std::ptr::write_volatile;
use std::{path::PathBuf, ptr::write};

#[cfg(feature = "tui")]
use libafl::monitors::tui::{ui::TuiUI, TuiMonitor};
#[cfg(not(feature = "tui"))]
use libafl::monitors::SimpleMonitor;
use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::{inprocess::InProcessExecutor, ExitKind},
feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, MinMapFeedback},
fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes, MultipartInput},
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
observers::StdMapObserver,
schedulers::QueueScheduler,
stages::mutational::StdMutationalStage,
state::StdState,
Evaluator,
};
use libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list, AsSlice};

/// Coverage map with explicit assignments due to the lack of instrumentation
static mut SIGNALS: [u8; 128] = [0; 128];
static mut SIGNALS_PTR: *mut u8 = unsafe { SIGNALS.as_mut_ptr() };

/// "Coverage" map for count, just to help things along
static mut LAST_COUNT: [usize; 1] = [usize::MAX];
static mut LAST_COUNT_PTR: *mut usize = unsafe { LAST_COUNT.as_mut_ptr() };

/// Assign a signal to the signals map
fn signals_set(idx: usize) {
unsafe { write(SIGNALS_PTR.add(idx), 1) };
}

/// Assign a count to the count "map"
fn count_set(count: usize) {
unsafe { LAST_COUNT[0] = count };
}

#[allow(clippy::similar_names, clippy::manual_assert)]
pub fn main() {
// The closure that we want to fuzz
let mut harness = |input: &MultipartInput<BytesInput>| {
let mut count = input.parts().len();
for (i, input) in input.parts().iter().enumerate() {
let target = input.target_bytes();
let buf = target.as_slice();
signals_set(i * 8);
if !buf.is_empty() && buf[0] == b'a' {
signals_set(1 + i * 8);
if buf.len() > 1 && buf[1] == b'b' {
signals_set(2 + i * 8);
if buf.len() > 2 && buf[2] == b'c' {
count -= 1;
}
}
}
}
if count == 0 {
#[cfg(unix)]
panic!("Artificial bug triggered =)");

// panic!() raises a STATUS_STACK_BUFFER_OVERRUN exception which cannot be caught by the exception handler.
// Here we make it raise STATUS_ACCESS_VIOLATION instead.
// Extending the windows exception handler is a TODO. Maybe we can refer to what winafl code does.
// https://github.com/googleprojectzero/winafl/blob/ea5f6b85572980bb2cf636910f622f36906940aa/winafl.c#L728
#[cfg(windows)]
unsafe {
write_volatile(0 as *mut u32, 0);
}
}

// without this, artificial bug is not found
// maybe interesting to try to auto-derive this, researchers! :)
count_set(count);

ExitKind::Ok
};

// Create an observation channel using the signals map
let signals_observer =
unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
let mut count_observer =
unsafe { StdMapObserver::from_mut_ptr("count", LAST_COUNT_PTR, LAST_COUNT.len()) };
*count_observer.initial_mut() = usize::MAX; // we are minimising!

// Feedback to rate the interestingness of an input
let signals_feedback = MaxMapFeedback::new(&signals_observer);
let count_feedback = MinMapFeedback::new(&count_observer);

let mut feedback = feedback_or_fast!(count_feedback, signals_feedback);

// A feedback to choose if an input is a solution or not
let mut objective = CrashFeedback::new();

// create a State from scratch
let mut state = StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(),
// States of the feedbacks.
// The feedbacks can report the data that should persist in the State.
&mut feedback,
// Same for objective feedbacks
&mut objective,
)
.unwrap();

// The Monitor trait define how the fuzzer stats are displayed to the user
#[cfg(not(feature = "tui"))]
let mon = SimpleMonitor::new(|s| println!("{s}"));
#[cfg(feature = "tui")]
let ui = TuiUI::with_version(String::from("Baby Fuzzer"), String::from("0.0.1"), false);
#[cfg(feature = "tui")]
let mon = TuiMonitor::new(ui);

// The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus
let mut mgr = SimpleEventManager::new(mon);

// A queue policy to get testcasess from the corpus
let scheduler = QueueScheduler::new();

// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

// Create the executor for an in-process function with just one observer
let mut executor = InProcessExecutor::new(
&mut harness,
tuple_list!(signals_observer, count_observer),
&mut fuzzer,
&mut state,
&mut mgr,
)
.expect("Failed to create the Executor");

// a generator here is not generalisable
let initial = MultipartInput::from([
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
("part", BytesInput::new(vec![b'h', b'e', b'l', b'l', b'o'])),
]);

fuzzer
.evaluate_input(&mut state, &mut executor, &mut mgr, initial)
.unwrap();

// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));

fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
}
4 changes: 4 additions & 0 deletions libafl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ tui_monitor = ["ratatui", "crossterm"]
## Enables `StringClassificationStage` and associated mutators, which allow for mutations which preserve the Unicode property data
unicode = ["libafl_bolts/alloc", "ahash/std", "serde/rc", "bitvec", "reqwest", "zip"]

## Enable multi-part input formats and mutators
multipart_inputs = ["arrayvec", "rand_trait"]

#! ## LibAFL-Bolts Features

Expand Down Expand Up @@ -186,6 +188,8 @@ libcasr = { version = "2.7", optional = true }

bitvec = { version = "1.0", optional = true, features = ["serde"] } # used for string range storage

arrayvec = { version = "0.7.4", optional = true, default-features = false } # used for fixed-len collects

# optional-dev deps (change when target.'cfg(accessible(::std))'.test-dependencies will be stable)
serial_test = { version = "2", optional = true, default-features = false, features = ["logging"] }

Expand Down
6 changes: 6 additions & 0 deletions libafl/src/inputs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ pub use gramatron::*;
pub mod generalized;
pub use generalized::*;

#[cfg(feature = "multipart_inputs")]
pub mod multi;
#[cfg(feature = "multipart_inputs")]
pub use multi::*;

#[cfg(feature = "nautilus")]
pub mod nautilus;

use alloc::{
boxed::Box,
string::{String, ToString},
Expand Down
Loading

0 comments on commit 99fd69a

Please sign in to comment.