diff --git a/fuzzers/FRET/Cargo.toml b/fuzzers/FRET/Cargo.toml index 28f1bfc1f3..b522cc320e 100644 --- a/fuzzers/FRET/Cargo.toml +++ b/fuzzers/FRET/Cargo.toml @@ -40,3 +40,5 @@ hashbrown = { version = "0.14.0", features = ["serde"] } # A faster hashmap, no petgraph = { version="0.6.0", features = ["serde-1"] } ron = "0.7" # write serialized data - including hashmaps rand = "0.5" +clap = { version = "4.4.11", features = ["derive"] } +csv = "1.3.0" diff --git a/fuzzers/FRET/benchmark/Snakefile b/fuzzers/FRET/benchmark/Snakefile index 030f5f7818..0ce963b712 100644 --- a/fuzzers/FRET/benchmark/Snakefile +++ b/fuzzers/FRET/benchmark/Snakefile @@ -2,7 +2,7 @@ import csv import os def_flags="--no-default-features --features std,snapshot_restore,singlecore,restarting,run_until_saturation" remote="timedump_253048_1873f6_all/" -RUNTIME=10 +RUNTIME=120 TARGET_REPS_A=2 TARGET_REPS_B=2 NUM_NODES=2 @@ -157,19 +157,21 @@ rule run_bench: bkp=line['return_function'] script=""" mkdir -p $(dirname {output[0]}) - export KERNEL=$(pwd)/{input[0]} - export FUZZ_MAIN={fuzz_main} - export FUZZ_INPUT={fuzz_input} - export FUZZ_INPUT_LEN={fuzz_len} - export BREAKPOINT={bkp} - export SEED_RANDOM={wildcards.num} + # export KERNEL=$(pwd)/{input[0]} + # export FUZZ_MAIN={fuzz_main} + # export FUZZ_INPUT={fuzz_input} + # export FUZZ_INPUT_LEN={fuzz_len} + # export BREAKPOINT={bkp} + # export SEED_RANDOM={wildcards.num} export TIME_DUMP=$(pwd)/{output[0]} export CASE_DUMP=$(pwd)/{output[0]}.case export TRACE_DUMP=$(pwd)/{output[0]}.trace - export FUZZ_ITERS={RUNTIME} + # export FUZZ_ITERS={RUNTIME} export FUZZER=$(pwd)/{input[1]}/debug/fret set +e - ../fuzzer.sh > {output[1]} 2>&1 + # ../fuzzer.sh > {output[1]} 2>&1 + echo $(pwd)/{input[1]}/debug/fret -k {input[0]} -c ./target_symbols.csv fuzz -t {RUNTIME} -s {wildcards.num} + $(pwd)/{input[1]}/debug/fret -k {input[0]} -c ./target_symbols.csv fuzz -t {RUNTIME} -s {wildcards.num} > {output[1]} 2>&1 exit 0 """ if wildcards.fuzzer.find('random') >= 0: diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs index 986474e8fc..dc1e2dccfa 100644 --- a/fuzzers/FRET/src/fuzzer.rs +++ b/fuzzers/FRET/src/fuzzer.rs @@ -1,7 +1,7 @@ //! A fuzzer using qemu in systemmode for binary-only coverage of kernels //! use core::time::Duration; -use std::{env, path::PathBuf, process::{self, abort}, io::{Read, Write}, fs::{self, OpenOptions}, cmp::{min, max}, mem::transmute_copy, collections::btree_map::Range, ptr::addr_of_mut}; +use std::{env, path::PathBuf, process::{self, abort}, io::{Read, Write}, fs::{self, OpenOptions}, cmp::{min, max}, mem::transmute_copy, collections::btree_map::Range, ptr::addr_of_mut, ffi::OsStr}; use libafl_bolts::{ core_affinity::Cores, @@ -43,6 +43,8 @@ use crate::{ mutational::{MINIMUM_INTER_ARRIVAL_TIME}, }; use std::time::{SystemTime, UNIX_EPOCH}; +use clap::{Parser, Subcommand}; +use csv::Reader; pub static mut RNG_SEED: u64 = 1; @@ -82,7 +84,73 @@ extern "C" { } +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Kernel Image + #[arg(short, long, value_name = "FILE")] + kernel: PathBuf, + + /// Sets a custom config file + #[arg(short, long, value_name = "FILE")] + config: PathBuf, + + #[command(subcommand)] + command: Commands, +} +#[derive(Subcommand)] +enum Commands { + /// run a single input + Showmap { + /// take this input + #[arg(short, long)] + input: PathBuf, + }, + /// start fuzzing campaign + Fuzz { + /// disable heuristic + #[arg(short, long)] + random: bool, + /// seed for randomness + #[arg(short, long)] + seed: Option, + /// runtime in seconds + #[arg(short, long)] + time: Option, + } +} + +fn env_from_config(kernel : &PathBuf, path : &PathBuf) { + let is_csv = path.as_path().extension().map_or(false, |x| x=="csv"); + if !is_csv { + let lines = std::fs::read_to_string(path).expect("Config file not found"); + let lines = lines.lines().filter( + |x| x.len()>0 + ); + for l in lines { + let pair = l.split_once('=').expect("Non VAR=VAL line in config"); + std::env::set_var(pair.0, pair.1); + } + } else { + let mut reader = csv::Reader::from_path(path).expect("CSV read from config failed"); + let p = kernel.as_path(); + let stem = p.file_stem().expect("Kernel filename error").to_str().unwrap(); + for r in reader.records() { + let rec = r.expect("CSV entry error"); + if stem == &rec[0] { + std::env::set_var("FUZZ_MAIN", &rec[1]); + std::env::set_var("FUZZ_INPUT", &rec[2]); + std::env::set_var("FUZZ_INPUT_LEN", &rec[3]); + std::env::set_var("BREAKPOINT", &rec[4]); + break; + } + } + } +} + pub fn fuzz() { + let cli = Cli::parse(); + env_from_config(&cli.kernel, &cli.config); unsafe {FUZZ_START_TIMESTAMP = SystemTime::now();} let mut starttime = std::time::Instant::now(); if let Ok(s) = env::var("FUZZ_SIZE") { @@ -97,7 +165,7 @@ pub fn fuzz() { let mut elf_buffer = Vec::new(); let elf = EasyElf::from_file( - env::var("KERNEL").expect("KERNEL env not set"), + &cli.kernel, &mut elf_buffer, ) .unwrap(); @@ -168,7 +236,27 @@ pub fn fuzz() { let mut run_client = |state: Option<_>, mut mgr, _core_id| { // Initialize QEMU - let args: Vec = env::args().collect(); + let args: Vec = vec![ + "target/debug/fret", + "-icount", + "shift=4,align=off,sleep=off", + "-machine", + "mps2-an385", + "-monitor", + "null", + "-kernel", + &cli.kernel.as_os_str().to_str().expect("kernel path is not a string"), + "-serial", + "null", + "-nographic", + "-S", + "-semihosting", + "--semihosting-config", + "enable=on,target=native", + "-snapshot", + "-drive", + "if=none,format=qcow2,file=dummy.qcow2", + ].into_iter().map(String::from).collect(); let env: Vec<(String, String)> = env::vars().collect(); let emu = Emulator::new(&args, &env).expect("Emulator creation failed"); @@ -372,8 +460,8 @@ pub fn fuzz() { #[cfg(not(feature = "fuzz_int"))] let mut stages = tuple_list!(StdMutationalStage::new(mutator)); - if env::var("DO_SHOWMAP").is_ok() { - let s = &env::var("DO_SHOWMAP").unwrap(); + if let Commands::Showmap { input } = cli.command { + let s = input.as_os_str(); let show_input = if s=="-" { let mut buf = Vec::::new(); std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin"); @@ -398,10 +486,10 @@ pub fn fuzz() { } } } - } else { - if let Ok(_) = env::var("SEED_RANDOM") { + } else if let Commands::Fuzz { random, time, seed } = cli.command { + if let Some(se) = seed { unsafe { - let mut rng = StdRng::seed_from_u64(RNG_SEED); + let mut rng = StdRng::seed_from_u64(se); for i in 0..100 { let inp = BytesInput::new(vec![rng.gen::(); MAX_INPUT_SIZE]); fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap(); @@ -426,15 +514,15 @@ pub fn fuzz() { println!("We imported {} inputs from disk.", state.corpus().count()); } - match env::var("FUZZ_ITERS") { - Err(_) => { + match time { + None => { fuzzer .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) .unwrap(); }, - Ok(t) => { + Some(t) => { println!("Iterations {}",t); - let num = str::parse::(&t).expect("FUZZ_ITERS was not a number"); + let num = t; if let Ok(s) = env::var("FUZZ_RANDOM") { unsafe { if s.contains("watersv2_int") { println!("V2");