424 lines
17 KiB
Rust
424 lines
17 KiB
Rust
//! A fuzzer using qemu in systemmode for binary-only coverage of kernels
|
|
//!
|
|
use core::time::Duration;
|
|
use std::{env, path::PathBuf, process, io::{Read, Write}, fs::{self, OpenOptions}};
|
|
|
|
use libafl::{
|
|
bolts::{
|
|
core_affinity::Cores,
|
|
current_nanos,
|
|
launcher::Launcher,
|
|
rands::StdRand,
|
|
shmem::{ShMemProvider, StdShMemProvider},
|
|
tuples::tuple_list,
|
|
AsSlice,
|
|
},
|
|
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
|
|
events::EventConfig,
|
|
executors::{ExitKind, TimeoutExecutor},
|
|
feedback_or,
|
|
feedback_or_fast,
|
|
feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback},
|
|
fuzzer::{Fuzzer, StdFuzzer},
|
|
inputs::{BytesInput, HasTargetBytes},
|
|
monitors::MultiMonitor,
|
|
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
|
observers::{VariableMapObserver},
|
|
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
|
stages::StdMutationalStage,
|
|
state::{HasCorpus, StdState},
|
|
Error,
|
|
prelude::{SimpleMonitor, SimpleEventManager, AsMutSlice, RandBytesGenerator, Generator}, Evaluator,
|
|
};
|
|
use libafl_qemu::{
|
|
edges, edges::QemuEdgeCoverageHelper, elf::EasyElf, emu::Emulator, GuestPhysAddr, QemuExecutor,
|
|
QemuHooks, Regs, QemuInstrumentationFilter, GuestAddr,
|
|
};
|
|
use crate::{
|
|
clock::{QemuClockObserver, ClockTimeFeedback, QemuClockIncreaseFeedback, ICOUNT_HISTORY},
|
|
qemustate::QemuStateRestoreHelper,
|
|
systemstate::{helpers::QemuSystemStateHelper, observers::QemuSystemStateObserver, feedbacks::DumpSystraceFeedback}, worst::{TimeMaximizerCorpusScheduler, ExecTimeIncFeedback},
|
|
};
|
|
|
|
pub static mut MAX_INPUT_SIZE: usize = 32;
|
|
/// Read ELF program headers to resolve physical load addresses.
|
|
fn virt2phys(vaddr: GuestAddr, tab: &EasyElf) -> GuestAddr {
|
|
let ret;
|
|
for i in &tab.goblin().program_headers {
|
|
if i.vm_range().contains(&vaddr.try_into().unwrap()) {
|
|
ret = vaddr - TryInto::<GuestAddr>::try_into(i.p_vaddr).unwrap()
|
|
+ TryInto::<GuestAddr>::try_into(i.p_paddr).unwrap();
|
|
return ret - (ret % 2);
|
|
}
|
|
}
|
|
return vaddr;
|
|
}
|
|
|
|
extern "C" {
|
|
static mut libafl_int_offset : u32;
|
|
}
|
|
|
|
pub fn fuzz() {
|
|
if let Ok(s) = env::var("FUZZ_SIZE") {
|
|
str::parse::<usize>(&s).expect("FUZZ_SIZE was not a number");
|
|
};
|
|
// Hardcoded parameters
|
|
let timeout = Duration::from_secs(3);
|
|
let broker_port = 1337;
|
|
let cores = Cores::from_cmdline("1").unwrap();
|
|
let corpus_dirs = [PathBuf::from("./corpus")];
|
|
let objective_dir = PathBuf::from("./crashes");
|
|
|
|
let mut elf_buffer = Vec::new();
|
|
let elf = EasyElf::from_file(
|
|
env::var("KERNEL").expect("KERNEL env not set"),
|
|
&mut elf_buffer,
|
|
)
|
|
.unwrap();
|
|
|
|
let input_addr = elf
|
|
.resolve_symbol(
|
|
&env::var("FUZZ_INPUT").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()),
|
|
0,
|
|
)
|
|
.expect("Symbol or env FUZZ_INPUT not found"); //as GuestPhysAddr;
|
|
let input_addr = virt2phys(input_addr,&elf) as GuestPhysAddr;
|
|
println!("FUZZ_INPUT @ {:#x}", input_addr);
|
|
|
|
let test_length_ptr = elf
|
|
.resolve_symbol("FUZZ_LENGTH", 0);
|
|
let test_length_ptr = Option::map_or(test_length_ptr, None, |x| Some(virt2phys(x,&elf) as u32));
|
|
|
|
let input_counter_ptr = elf
|
|
.resolve_symbol(&env::var("FUZZ_POINTER").unwrap_or_else(|_| "FUZZ_POINTER".to_owned()), 0);
|
|
let input_counter_ptr = Option::map_or(input_counter_ptr, None, |x| Some(virt2phys(x,&elf) as u32));
|
|
|
|
let main_addr = elf
|
|
.resolve_symbol(&env::var("FUZZ_MAIN").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()), 0)
|
|
.expect("Symbol main not found");
|
|
println!("main address = {:#x}", main_addr);
|
|
|
|
#[cfg(feature = "systemstate")]
|
|
let curr_tcb_pointer = elf // loads to the address specified in elf, without respecting program headers
|
|
.resolve_symbol("pxCurrentTCB", 0)
|
|
.expect("Symbol pxCurrentTCBC not found");
|
|
// let curr_tcb_pointer = virt2phys(curr_tcb_pointer,&elf);
|
|
#[cfg(feature = "systemstate")]
|
|
println!("TCB pointer at {:#x}", curr_tcb_pointer);
|
|
#[cfg(feature = "systemstate")]
|
|
let task_queue_addr = elf
|
|
.resolve_symbol("pxReadyTasksLists", 0)
|
|
.expect("Symbol pxReadyTasksLists not found");
|
|
// let task_queue_addr = virt2phys(task_queue_addr,&elf.goblin());
|
|
#[cfg(feature = "systemstate")]
|
|
println!("Task Queue at {:#x}", task_queue_addr);
|
|
#[cfg(feature = "systemstate")]
|
|
let svh = elf
|
|
.resolve_symbol("xPortPendSVHandler", 0)
|
|
.expect("Symbol xPortPendSVHandler not found");
|
|
// let svh=virt2phys(svh, &elf);
|
|
// let svh = elf
|
|
// .resolve_symbol("vPortEnterCritical", 0)
|
|
// .expect("Symbol vPortEnterCritical not found");
|
|
#[cfg(feature = "systemstate")]
|
|
let app_start = elf
|
|
.resolve_symbol("__APP_CODE_START__", 0)
|
|
.expect("Symbol __APP_CODE_START__ not found");
|
|
#[cfg(feature = "systemstate")]
|
|
let app_end = elf
|
|
.resolve_symbol("__APP_CODE_END__", 0)
|
|
.expect("Symbol __APP_CODE_END__ not found");
|
|
#[cfg(feature = "systemstate")]
|
|
let app_range = app_start..app_end;
|
|
|
|
let breakpoint = elf
|
|
.resolve_symbol(
|
|
&env::var("BREAKPOINT").unwrap_or_else(|_| "BREAKPOINT".to_owned()),
|
|
0,
|
|
)
|
|
.expect("Symbol or env BREAKPOINT not found");
|
|
println!("Breakpoint address = {:#x}", breakpoint);
|
|
unsafe {
|
|
libafl_int_offset = 0;
|
|
}
|
|
|
|
if let Ok(input_len) = env::var("FUZZ_INPUT_LEN") {
|
|
unsafe {MAX_INPUT_SIZE = str::parse::<usize>(&input_len).expect("FUZZ_INPUT_LEN was not a number");}
|
|
}
|
|
|
|
let mut run_client = |state: Option<_>, mut mgr, _core_id| {
|
|
// Initialize QEMU
|
|
let args: Vec<String> = env::args().collect();
|
|
let env: Vec<(String, String)> = env::vars().collect();
|
|
let emu = Emulator::new(&args, &env);
|
|
|
|
emu.set_breakpoint(main_addr);
|
|
unsafe {
|
|
emu.run();
|
|
}
|
|
emu.remove_breakpoint(main_addr);
|
|
|
|
emu.set_breakpoint(breakpoint); // BREAKPOINT
|
|
|
|
// The wrapped harness function, calling out to the LLVM-style harness
|
|
let mut harness = |input: &BytesInput| {
|
|
let target = input.target_bytes();
|
|
let mut buf = target.as_slice();
|
|
let len = buf.len();
|
|
unsafe {
|
|
if len > MAX_INPUT_SIZE {
|
|
buf = &buf[0..MAX_INPUT_SIZE];
|
|
// len = MAX_INPUT_SIZE;
|
|
}
|
|
|
|
emu.write_phys_mem(input_addr, buf);
|
|
if let Some(s) = test_length_ptr {
|
|
emu.write_phys_mem(s as u64, &len.to_le_bytes())
|
|
}
|
|
|
|
emu.run();
|
|
|
|
// If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash
|
|
let mut pcs = (0..emu.num_cpus())
|
|
.map(|i| emu.cpu_from_index(i))
|
|
.map(|cpu| -> Result<u32, String> { cpu.read_reg(Regs::Pc) });
|
|
match pcs
|
|
.find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0)))
|
|
{
|
|
Some(_) => ExitKind::Ok,
|
|
None => ExitKind::Crash,
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create an observation channel using the coverage map
|
|
let edges = unsafe { &mut edges::EDGES_MAP };
|
|
let edges_counter = unsafe { &mut edges::MAX_EDGES_NUM };
|
|
let edges_observer = VariableMapObserver::new("edges", edges, edges_counter);
|
|
|
|
// Create an observation channel to keep track of the execution time
|
|
let clock_time_observer = QemuClockObserver::new("clocktime");
|
|
|
|
let systemstate_observer = QemuSystemStateObserver::new();
|
|
|
|
// Feedback to rate the interestingness of an input
|
|
// This one is composed by two Feedbacks in OR
|
|
let mut feedback = feedback_or!(
|
|
// New maximization map feedback linked to the edges observer and the feedback state
|
|
MaxMapFeedback::new_tracking(&edges_observer, true, true),
|
|
// QemuClockIncreaseFeedback::default(),
|
|
// Time feedback, this one does not need a feedback state
|
|
ClockTimeFeedback::new_with_observer(&clock_time_observer),
|
|
// Feedback to reward any input which increses the execution time
|
|
ExecTimeIncFeedback::new()
|
|
);
|
|
#[cfg(feature = "systemstate")]
|
|
let mut feedback = feedback_or!(
|
|
DumpSystraceFeedback::with_dump(None),
|
|
feedback
|
|
);
|
|
|
|
// A feedback to choose if an input is a solution or not
|
|
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
|
|
|
|
// If not restarting, create a State from scratch
|
|
let mut state = state.unwrap_or_else(|| {
|
|
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(objective_dir.clone()).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()
|
|
});
|
|
|
|
// A minimization+queue policy to get testcasess from the corpus
|
|
let scheduler = TimeMaximizerCorpusScheduler::new(QueueScheduler::new());
|
|
|
|
// A fuzzer with feedbacks and a corpus scheduler
|
|
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
|
#[cfg(not(feature = "systemstate"))]
|
|
let qhelpers = tuple_list!(
|
|
QemuEdgeCoverageHelper::default(),
|
|
QemuStateRestoreHelper::new()
|
|
);
|
|
#[cfg(feature = "systemstate")]
|
|
let qhelpers = tuple_list!(
|
|
QemuEdgeCoverageHelper::default(),
|
|
QemuStateRestoreHelper::new(),
|
|
QemuSystemStateHelper::new(svh,curr_tcb_pointer,task_queue_addr,input_counter_ptr,app_range.clone())
|
|
);
|
|
let mut hooks = QemuHooks::new(&emu,qhelpers);
|
|
|
|
#[cfg(not(feature = "systemstate"))]
|
|
let observer_list = tuple_list!(edges_observer, clock_time_observer);
|
|
#[cfg(feature = "systemstate")]
|
|
let observer_list = tuple_list!(edges_observer, clock_time_observer, systemstate_observer);
|
|
|
|
// Create a QEMU in-process executor
|
|
let executor = QemuExecutor::new(
|
|
&mut hooks,
|
|
&mut harness,
|
|
observer_list,
|
|
&mut fuzzer,
|
|
&mut state,
|
|
&mut mgr,
|
|
)
|
|
.expect("Failed to create QemuExecutor");
|
|
|
|
// Wrap the executor to keep track of the timeout
|
|
let mut executor = TimeoutExecutor::new(executor, timeout);
|
|
|
|
// Setup an havoc mutator with a mutational stage
|
|
let mutator = StdScheduledMutator::new(havoc_mutations());
|
|
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
|
|
|
if env::var("DO_SHOWMAP").is_ok() {
|
|
let s = &env::var("DO_SHOWMAP").unwrap();
|
|
let show_input = if s=="-" {
|
|
let mut buf = Vec::<u8>::new();
|
|
std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin");
|
|
buf
|
|
} else if s=="$" {
|
|
env::var("SHOWMAP_TEXTINPUT").expect("SHOWMAP_TEXTINPUT not set").as_bytes().to_owned()
|
|
} else {
|
|
fs::read(s).expect("Input file for DO_SHOWMAP can not be read")
|
|
};
|
|
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(show_input))
|
|
.unwrap();
|
|
} else {
|
|
if let Ok(_) = env::var("SEED_RANDOM") {
|
|
unsafe {
|
|
let mut generator = RandBytesGenerator::new(MAX_INPUT_SIZE);
|
|
state
|
|
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 100)
|
|
.unwrap_or_else(|_| {
|
|
println!("Failed to load initial corpus at {:?}", &corpus_dirs);
|
|
process::exit(0);
|
|
});
|
|
}
|
|
}
|
|
else if let Ok(sf) = env::var("SEED_DIR") {
|
|
state
|
|
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[PathBuf::from(&sf)])
|
|
.unwrap_or_else(|_| {
|
|
println!("Failed to load initial corpus at {:?}", &corpus_dirs);
|
|
process::exit(0);
|
|
});
|
|
println!("We imported {} inputs from seedfile.", state.corpus().count());
|
|
} else if state.corpus().count() < 1 {
|
|
state
|
|
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs)
|
|
.unwrap_or_else(|_| {
|
|
println!("Failed to load initial corpus at {:?}", &corpus_dirs);
|
|
process::exit(0);
|
|
});
|
|
println!("We imported {} inputs from disk.", state.corpus().count());
|
|
}
|
|
|
|
match env::var("FUZZ_ITERS") {
|
|
Err(_) => {
|
|
fuzzer
|
|
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
|
|
.unwrap();
|
|
},
|
|
Ok(t) => {
|
|
println!("Iterations {}",t);
|
|
let num = str::parse::<u64>(&t).expect("FUZZ_ITERS was not a number");
|
|
if let Ok(_) = env::var("FUZZ_RANDOM") { unsafe {
|
|
println!("Random Fuzzing, ignore corpus");
|
|
let mut generator = RandBytesGenerator::new(MAX_INPUT_SIZE);
|
|
let target_duration = Duration::from_secs(num);
|
|
let start_time = std::time::Instant::now();
|
|
while start_time.elapsed() < target_duration {
|
|
let inp = generator.generate(&mut state).unwrap();
|
|
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap();
|
|
}
|
|
}} else {
|
|
fuzzer
|
|
.fuzz_loop_for_duration(&mut stages, &mut executor, &mut state, &mut mgr, Duration::from_secs(num))
|
|
.unwrap();
|
|
}
|
|
if let Ok(td) = env::var("TIME_DUMP") {
|
|
let mut file = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.append(false)
|
|
.open(td).expect("Could not open timedump");
|
|
unsafe {
|
|
for i in ICOUNT_HISTORY.iter() {
|
|
writeln!(file, "{}", i).expect("Write to dump failed");
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
#[cfg(not(feature = "singlecore"))]
|
|
Ok(())
|
|
};
|
|
|
|
// Special case where no fuzzing happens, but standard input is dumped
|
|
if let Ok(input_dump) = env::var("DUMP_SEED") {
|
|
// Initialize QEMU
|
|
let args: Vec<String> = env::args().collect();
|
|
let env: Vec<(String, String)> = env::vars().collect();
|
|
let emu = Emulator::new(&args, &env);
|
|
|
|
emu.set_breakpoint(main_addr);
|
|
unsafe {
|
|
emu.run();
|
|
|
|
let mut buf = [0u8].repeat(MAX_INPUT_SIZE);
|
|
emu.read_phys_mem(input_addr, buf.as_mut_slice());
|
|
|
|
let dir = env::var("SEED_DIR").map_or("./corpus".to_string(), |x| x);
|
|
let filename = if input_dump == "" {"input"} else {&input_dump};
|
|
println!("Dumping input to: {}/{}",&dir,filename);
|
|
fs::write(format!("{}/{}",&dir,filename), buf).expect("could not write input dump");
|
|
}
|
|
return
|
|
}
|
|
|
|
#[cfg(feature = "singlecore")]
|
|
{
|
|
let monitor = SimpleMonitor::new(|s| println!("{}", s));
|
|
let mgr = SimpleEventManager::new(monitor);
|
|
run_client(None, mgr, 0);
|
|
}
|
|
// else -> multicore
|
|
#[cfg(not(feature = "singlecore"))]
|
|
{
|
|
// The shared memory allocator
|
|
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
|
|
|
|
// The stats reporter for the broker
|
|
let monitor = MultiMonitor::new(|s| println!("{}", s));
|
|
|
|
// Build and run a Launcher
|
|
match Launcher::builder()
|
|
.shmem_provider(shmem_provider)
|
|
.broker_port(broker_port)
|
|
.configuration(EventConfig::from_build_id())
|
|
.monitor(monitor)
|
|
.run_client(&mut run_client)
|
|
.cores(&cores)
|
|
// .stdout_file(Some("/dev/null"))
|
|
.build()
|
|
.launch()
|
|
{
|
|
Ok(()) => (),
|
|
Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."),
|
|
Err(err) => panic!("Failed to run launcher: {:?}", err),
|
|
}
|
|
}
|
|
} |