diff --git a/fuzzers/FRET/.gitignore b/fuzzers/FRET/.gitignore new file mode 100644 index 0000000000..b511ae114b --- /dev/null +++ b/fuzzers/FRET/.gitignore @@ -0,0 +1 @@ +*.qcow2 diff --git a/fuzzers/FRET/Cargo.toml b/fuzzers/FRET/Cargo.toml new file mode 100644 index 0000000000..e4fde35f39 --- /dev/null +++ b/fuzzers/FRET/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "qemu_systemmode" +version = "0.8.2" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2021" + +[features] +default = ["std", "snapshot_fast"] +std = [] +snapshot_restore = [] +snapshot_fast = [ "snapshot_restore" ] + +[profile.release] +lto = true +codegen-units = 1 +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } +serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib +hashbrown = { version = "0.12", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible diff --git a/fuzzers/FRET/README.md b/fuzzers/FRET/README.md new file mode 100644 index 0000000000..14098dc09c --- /dev/null +++ b/fuzzers/FRET/README.md @@ -0,0 +1,26 @@ +# Qemu systemmode with launcher + +This folder contains an example fuzzer for the qemu systemmode, using LLMP for fast multi-process fuzzing and crash detection. + +## Build + +To build this example, run + +```bash +cargo build --release +cd example; sh build.sh; cd .. +``` + +This will build the the fuzzer (src/fuzzer.rs) and a small example binary based on FreeRTOS, which can run under a qemu emulation target. + +## Run + +Since the instrumentation is based on snapshtos QEMU needs a virtual drive (even if it is unused...). +Create on and then run the fuzzer: +```bash +# create an image +qemu-img create -f qcow2 dummy.qcow2 32M +# run the fuzzer +KERNEL=./example/example.elf target/release/qemu_systemmode -icount shift=auto,align=off,sleep=off -machine mps2-an385 -monitor null -kernel ./example/example.elf -serial null -nographic -snapshot -drive if=none,format=qcow2,file=dummy.qcow2 -S +``` +Currently the ``KERNEL`` variable is needed because the fuzzer does not parse QEMUs arguments to find the binary. \ No newline at end of file diff --git a/fuzzers/FRET/example/build.sh b/fuzzers/FRET/example/build.sh new file mode 100755 index 0000000000..ceb387b19a --- /dev/null +++ b/fuzzers/FRET/example/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +arm-none-eabi-gcc -ggdb -ffreestanding -nostartfiles -lgcc -T mps2_m3.ld -mcpu=cortex-m3 main.c startup.c -o example.elf \ No newline at end of file diff --git a/fuzzers/FRET/example/main.c b/fuzzers/FRET/example/main.c new file mode 100644 index 0000000000..9ed2fc8be5 --- /dev/null +++ b/fuzzers/FRET/example/main.c @@ -0,0 +1,38 @@ +int BREAKPOINT() { + for (;;) + { + } +} + +int LLVMFuzzerTestOneInput(unsigned int* Data, unsigned int Size) { + //if (Data[3] == 0) {while(1){}} // cause a timeout + for (int i=0; i 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to crash + for (int j=i+1; jData[i]) { + int tmp = Data[i]; + Data[i]=Data[j]; + Data[j]=tmp; + if (Data[i] <= 100) {j--;} + } + } + } + return BREAKPOINT(); +} + unsigned int FUZZ_INPUT[] = { + 101,201,700,230,860, + 234,980,200,340,678, + 230,134,900,236,900, + 123,800,123,658,607, + 246,804,567,568,207, + 407,246,678,457,892, + 834,456,878,246,699, + 854,234,844,290,125, + 324,560,852,928,910, + 790,853,345,234,586, + }; + +int main() { + LLVMFuzzerTestOneInput(FUZZ_INPUT, 50); +} diff --git a/fuzzers/FRET/example/mps2_m3.ld b/fuzzers/FRET/example/mps2_m3.ld new file mode 100644 index 0000000000..adfc15ed78 --- /dev/null +++ b/fuzzers/FRET/example/mps2_m3.ld @@ -0,0 +1,143 @@ +/* + * FreeRTOS V202112.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +MEMORY +{ + RAM (xrw) : ORIGIN = 0x00000000, LENGTH = 4M + /* Originally */ + /* FLASH (xr) : ORIGIN = 0x00000000, LENGTH = 4M */ + /* RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4M */ +} +ENTRY(Reset_Handler) + +_Min_Heap_Size = 0x300000 ; /* Required amount of heap. */ +_Min_Stack_Size = 0x4000 ; /* Required amount of stack. */ +M_VECTOR_RAM_SIZE = (16 + 48) * 4; +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + + .isr_vector : + { + __vector_table = .; + KEEP(*(.isr_vector)) + . = ALIGN(4); + } > RAM /* FLASH */ + + .text : + { + . = ALIGN(4); + *(.text*) + KEEP (*(.init)) + KEEP (*(.fini)) + KEEP(*(.eh_frame)) + *(.rodata*) + . = ALIGN(4); + _etext = .; + } > RAM /* FLASH */ + + .ARM.extab : + { + . = ALIGN(4); + *(.ARM.extab* .gnu.linkonce.armextab.*) + . = ALIGN(4); + } >RAM /* FLASH */ + + .ARM : + { + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); + } >RAM /* FLASH */ + + .interrupts_ram : + { + . = ALIGN(4); + __VECTOR_RAM__ = .; + __interrupts_ram_start__ = .; + . += M_VECTOR_RAM_SIZE; + . = ALIGN(4); + __interrupts_ram_end = .; + } > RAM + + _sidata = LOADADDR(.data); + + .data : /* AT ( _sidata ) */ + { + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > RAM /* RAM AT > FLASH */ + + .uninitialized (NOLOAD): + { + . = ALIGN(32); + __uninitialized_start = .; + *(.uninitialized) + KEEP(*(.keep.uninitialized)) + . = ALIGN(32); + __uninitialized_end = .; + } > RAM + + .bss : + { + . = ALIGN(4); + _sbss = .; + __bss_start__ = _sbss; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + __bss_end__ = _ebss; + } >RAM + + .heap : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + _heap_bottom = .; + . = . + _Min_Heap_Size; + _heap_top = .; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + /* Set stack top to end of RAM, and stack limit move down by + * size of stack_dummy section */ + __StackTop = ORIGIN(RAM) + LENGTH(RAM); + __StackLimit = __StackTop - _Min_Stack_Size; + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= _heap_top, "region RAM overflowed with stack") +} + diff --git a/fuzzers/FRET/example/startup.c b/fuzzers/FRET/example/startup.c new file mode 100644 index 0000000000..3b3acb56b8 --- /dev/null +++ b/fuzzers/FRET/example/startup.c @@ -0,0 +1,114 @@ +/* + * FreeRTOS V202112.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +typedef unsigned int uint32_t; + +extern int main(); + +extern uint32_t _estack, _sidata, _sdata, _edata, _sbss, _ebss; + +/* Prevent optimization so gcc does not replace code with memcpy */ +__attribute__( ( optimize( "O0" ) ) ) +__attribute__( ( naked ) ) +void Reset_Handler( void ) +{ + /* set stack pointer */ + __asm volatile ( "ldr r0, =_estack" ); + __asm volatile ( "mov sp, r0" ); + + /* copy .data section from flash to RAM */ + // Not needed for this example, see linker script + // for( uint32_t * src = &_sidata, * dest = &_sdata; dest < &_edata; ) + // { + // *dest++ = *src++; + // } + + /* zero out .bss section */ + for( uint32_t * dest = &_sbss; dest < &_ebss; ) + { + *dest++ = 0; + } + + /* jump to board initialisation */ + void _start( void ); + _start(); +} + +const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) = +{ + ( uint32_t * ) &_estack, + ( uint32_t * ) &Reset_Handler, /* Reset -15 */ + 0, /* NMI_Handler -14 */ + 0, /* HardFault_Handler -13 */ + 0, /* MemManage_Handler -12 */ + 0, /* BusFault_Handler -11 */ + 0, /* UsageFault_Handler -10 */ + 0, /* reserved */ + 0, /* reserved */ + 0, /* reserved */ + 0, /* reserved -6 */ + 0, /* SVC_Handler -5 */ + 0, /* DebugMon_Handler -4 */ + 0, /* reserved */ + 0, /* PendSV handler -2 */ + 0, /* SysTick_Handler -1 */ + 0, /* uart0 receive 0 */ + 0, /* uart0 transmit */ + 0, /* uart1 receive */ + 0, /* uart1 transmit */ + 0, /* uart 2 receive */ + 0, /* uart 2 transmit */ + 0, /* GPIO 0 combined interrupt */ + 0, /* GPIO 2 combined interrupt */ + 0, /* Timer 0 */ + 0, /* Timer 1 */ + 0, /* Dial Timer */ + 0, /* SPI0 SPI1 */ + 0, /* uart overflow 1, 2,3 */ + 0, /* Ethernet 13 */ +}; + +__attribute__( ( naked ) ) void exit(__attribute__((unused)) int status ) +{ + /* Force qemu to exit using ARM Semihosting */ + __asm volatile ( + "mov r1, r0\n" + "cmp r1, #0\n" + "bne .notclean\n" + "ldr r1, =0x20026\n" /* ADP_Stopped_ApplicationExit, a clean exit */ + ".notclean:\n" + "movs r0, #0x18\n" /* SYS_EXIT */ + "bkpt 0xab\n" + "end: b end\n" + ); +} + +void _start( void ) +{ + main( ); + exit( 0 ); +} + diff --git a/fuzzers/FRET/src/clock.rs b/fuzzers/FRET/src/clock.rs new file mode 100644 index 0000000000..efb2e59843 --- /dev/null +++ b/fuzzers/FRET/src/clock.rs @@ -0,0 +1,303 @@ +use hashbrown::{hash_map::Entry, HashMap}; +use libafl::{ + bolts::{ + current_nanos, + rands::StdRand, + tuples::{tuple_list}, + }, + executors::{ExitKind}, + fuzzer::{StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + observers::{Observer,VariableMapObserver}, + state::{StdState, HasNamedMetadata}, + Error, + observers::ObserversTuple, prelude::UsesInput, +}; +use serde::{Deserialize, Serialize}; +use std::{cell::UnsafeCell, cmp::max}; +use libafl::bolts::tuples::Named; + +use libafl_qemu::{ + emu, + emu::Emulator, + executor::QemuExecutor, + helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, +}; +use libafl::events::EventFirer; +use libafl::state::HasClientPerfMonitor; +use libafl::inputs::Input; +use libafl::feedbacks::Feedback; +use libafl::SerdeAny; +use libafl::state::HasMetadata; +use libafl::corpus::testcase::Testcase; +use core::{fmt::Debug, time::Duration}; +// use libafl::feedbacks::FeedbackState; +// use libafl::state::HasFeedbackStates; +use libafl::bolts::tuples::MatchName; + +//========== Metadata +#[derive(Debug, SerdeAny, Serialize, Deserialize)] +pub struct QemuIcountMetadata { + runtime: u64, +} + +/// Metadata for [`QemuClockIncreaseFeedback`] +#[derive(Debug, Serialize, Deserialize, SerdeAny)] +pub struct MaxIcountMetadata { + pub max_icount_seen: u64, + pub name: String, +} + +// impl FeedbackState for MaxIcountMetadata +// { +// fn reset(&mut self) -> Result<(), Error> { +// self.max_icount_seen = 0; +// Ok(()) +// } +// } + +impl Named for MaxIcountMetadata +{ + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl MaxIcountMetadata +{ + /// Create new `MaxIcountMetadata` + #[must_use] + pub fn new(name: &'static str) -> Self { + Self { + max_icount_seen: 0, + name: name.to_string(), + } + } +} + +impl Default for MaxIcountMetadata { + fn default() -> Self { + Self::new("MaxClock") + } +} + +//========== Observer + +/// A simple observer, just overlooking the runtime of the target. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct QemuClockObserver { + name: String, + start_tick: u64, + end_tick: u64, +} + +impl QemuClockObserver { + /// Creates a new [`QemuClockObserver`] with the given name. + #[must_use] + pub fn new(name: &'static str) -> Self { + Self { + name: name.to_string(), + start_tick: 0, + end_tick: 0, + } + } + + /// Gets the runtime for the last execution of this target. + #[must_use] + pub fn last_runtime(&self) -> u64 { + // println!("Number of Ticks: {} <- {} {}",self.end_tick - self.start_tick, self.end_tick, self.start_tick); + self.end_tick - self.start_tick + } +} + +impl Observer for QemuClockObserver +where + S: UsesInput, +{ + fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { + // Only remember the pre-run ticks if presistent mode ist used + #[cfg(not(feature = "snapshot_restore"))] + unsafe { + self.start_tick=emu::icount_get_raw(); + self.end_tick=self.start_tick; + } + // unsafe { + // println!("clock pre {}",emu::icount_get_raw()); + // } + Ok(()) + } + + fn post_exec(&mut self, _state: &mut S, _input: &S::Input, _exit_kind: &ExitKind) -> Result<(), Error> { + unsafe { self.end_tick = emu::icount_get_raw() }; + self.last_runtime(); + // println!("clock post {}", self.end_tick); + Ok(()) + } +} + +impl Named for QemuClockObserver { + #[inline] + fn name(&self) -> &str { + &self.name + } +} + +impl Default for QemuClockObserver { + fn default() -> Self { + Self { + name: String::from("clock"), + start_tick: 0, + end_tick: 0, + } + } +} + +//========== Feedback +/// Nop feedback that annotates execution time in the new testcase, if any +/// for this Feedback, the testcase is never interesting (use with an OR) +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ClockFeedback { + exec_time: Option, + name: String, +} + +impl Feedback for ClockFeedback +where + S: UsesInput + HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + // TODO Replace with match_name_type when stable + let observer = observers.match_name::(self.name()).unwrap(); + self.exec_time = Some(observer.last_runtime()); + Ok(false) + } + + /// Append to the testcase the generated metadata in case of a new corpus item + #[inline] + fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase) -> Result<(), Error> { + *testcase.exec_time_mut() = match self.exec_time { + Some(s) => Some(Duration::from_nanos(s << 3)), //emulated time is << 3, real time more like * 360 + None => None, + }; + self.exec_time = None; + Ok(()) + } + + /// Discard the stored metadata in case that the testcase is not added to the corpus + #[inline] + fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { + self.exec_time = None; + Ok(()) + } +} + +impl Named for ClockFeedback { + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl ClockFeedback { + /// Creates a new [`ClockFeedback`], deciding if the value of a [`TimeObserver`] with the given `name` of a run is interesting. + #[must_use] + pub fn new(name: &'static str) -> Self { + Self { + exec_time: None, + name: name.to_string(), + } + } + + /// Creates a new [`ClockFeedback`], deciding if the given [`TimeObserver`] value of a run is interesting. + #[must_use] + pub fn new_with_observer(observer: &QemuClockObserver) -> Self { + Self { + exec_time: None, + name: observer.name().to_string(), + } + } +} + +/// A [`Feedback`] rewarding increasing the execution cycles on Qemu. +#[derive(Debug)] +pub struct QemuClockIncreaseFeedback { + name: String, +} + +impl Feedback for QemuClockIncreaseFeedback +where + S: UsesInput + HasNamedMetadata + HasClientPerfMonitor + Debug, +{ + fn is_interesting( + &mut self, + state: &mut S, + _manager: &mut EM, + _input: &S::Input, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = _observers.match_name::("clock") + .expect("QemuClockObserver not found"); + let clock_state = state + .named_metadata_mut() + .get_mut::(&self.name) + .unwrap(); + if observer.last_runtime() > clock_state.max_icount_seen { + // println!("Clock improving {}",observer.last_runtime()); + clock_state.max_icount_seen = observer.last_runtime(); + return Ok(true); + } + Ok(false) + } + + /// Append to the testcase the generated metadata in case of a new corpus item + #[inline] + fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase) -> Result<(), Error> { + // testcase.metadata_mut().insert(QemuIcountMetadata{runtime: self.last_runtime}); + Ok(()) + } + + /// Discard the stored metadata in case that the testcase is not added to the corpus + #[inline] + fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { + Ok(()) + } + +} + +impl Named for QemuClockIncreaseFeedback { + #[inline] + fn name(&self) -> &str { + &self.name + } +} + +impl QemuClockIncreaseFeedback { + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new(name: &'static str) -> Self { + Self {name: String::from(name)} + } +} + +impl Default for QemuClockIncreaseFeedback { + fn default() -> Self { + Self::new("MaxClock") + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs new file mode 100644 index 0000000000..2726a50675 --- /dev/null +++ b/fuzzers/FRET/src/fuzzer.rs @@ -0,0 +1,250 @@ +//! A fuzzer using qemu in systemmode for binary-only coverage of kernels +//! +use core::time::Duration; +use std::{env, path::PathBuf, process}; + +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, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + mutators::scheduled::{havoc_mutations, StdScheduledMutator}, + observers::{TimeObserver, VariableMapObserver}, + schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, + stages::StdMutationalStage, + state::{HasCorpus, StdState}, + Error, + //prelude::{SimpleMonitor, SimpleEventManager}, +}; +use libafl_qemu::{ + edges, edges::QemuEdgeCoverageHelper, elf::EasyElf, emu::Emulator, GuestPhysAddr, QemuExecutor, + QemuHooks, Regs, +}; +use crate::{clock::QemuClockObserver, qemustate::QemuStateRestoreHelper}; + +pub static mut MAX_INPUT_SIZE: usize = 50; + +pub fn fuzz() { + if let Ok(s) = env::var("FUZZ_SIZE") { + str::parse::(&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; + println!("FUZZ_INPUT @ {:#x}", input_addr); + + let main_addr = elf + .resolve_symbol("main", 0) + .expect("Symbol main not found"); + println!("main address = {:#x}", main_addr); + + 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); + + let mut run_client = |state: Option<_>, mut mgr, _core_id| { + // Initialize QEMU + let args: Vec = 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 + + // let saved_cpu_states: Vec<_> = (0..emu.num_cpus()) + // .map(|i| emu.cpu_from_index(i).save_state()) + // .collect(); + + // emu.save_snapshot("start", true); + + // let snap = emu.create_fast_snapshot(true); + + // 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); + + 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 { cpu.read_reg(Regs::Pc) }); + let _ret = match pcs + .find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0))) + { + Some(_) => ExitKind::Ok, + None => ExitKind::Crash, + }; + + // OPTION 1: restore only the CPU state (registers et. al) + // for (i, s) in saved_cpu_states.iter().enumerate() { + // emu.cpu_from_index(i).restore_state(s); + // } + + // OPTION 2: restore a slow vanilla QEMU snapshot + // emu.load_snapshot("start", true); + + // OPTION 3: restore a fast devices+mem snapshot + // emu.restore_fast_snapshot(snap); + + _ret + } + }; + + // 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 time_observer = TimeObserver::new("time"); + + // 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), + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); + + // 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 = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut hooks = QemuHooks::new(&emu, tuple_list!(QemuEdgeCoverageHelper::default(),QemuStateRestoreHelper::new())); + + // Create a QEMU in-process executor + let executor = QemuExecutor::new( + &mut hooks, + &mut harness, + tuple_list!(edges_observer, time_observer, QemuClockObserver::default()), + &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); + + 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()); + } + + // Setup an havoc mutator with a mutational stage + 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) + .unwrap(); + Ok(()) + }; + + // 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)); + + // let monitor = SimpleMonitor::new(|s| println!("{}", s)); + // let mut mgr = SimpleEventManager::new(monitor); + // run_client(None, mgr, 0); + + // 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), + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/main.rs b/fuzzers/FRET/src/main.rs new file mode 100644 index 0000000000..ec434fb3e8 --- /dev/null +++ b/fuzzers/FRET/src/main.rs @@ -0,0 +1,17 @@ +//! A libfuzzer-like fuzzer using qemu for binary-only coverage +#[cfg(target_os = "linux")] +mod fuzzer; +#[cfg(target_os = "linux")] +mod clock; +#[cfg(target_os = "linux")] +mod qemustate; + +#[cfg(target_os = "linux")] +pub fn main() { + fuzzer::fuzz(); +} + +#[cfg(not(target_os = "linux"))] +pub fn main() { + panic!("qemu-user and libafl_qemu is only supported on linux!"); +} diff --git a/fuzzers/FRET/src/qemustate.rs b/fuzzers/FRET/src/qemustate.rs new file mode 100644 index 0000000000..a5c1816e8f --- /dev/null +++ b/fuzzers/FRET/src/qemustate.rs @@ -0,0 +1,96 @@ +use libafl::prelude::UsesInput; +use libafl_qemu::CPUArchState; +use libafl_qemu::Emulator; +use libafl_qemu::FastSnapshot; +use libafl_qemu::QemuExecutor; +use libafl_qemu::QemuHelper; +use libafl_qemu::QemuHelperTuple; +use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; +use libafl_qemu::QemuHooks; + +use libafl_qemu::{ + emu, +}; +// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html +#[derive(Debug)] +pub struct QemuStateRestoreHelper { + has_snapshot: bool, + use_snapshot: bool, + saved_cpu_states: Vec, + fastsnap: Option +} + +impl QemuStateRestoreHelper { + #[must_use] + pub fn new() -> Self { + Self { + has_snapshot: false, + use_snapshot: true, + saved_cpu_states: vec![], + fastsnap: None + } + } +} + +impl Default for QemuStateRestoreHelper { + fn default() -> Self { + Self::new() + } +} + +impl QemuHelper for QemuStateRestoreHelper +where + S: UsesInput, +{ + const HOOKS_DO_SIDE_EFFECTS: bool = true; + + fn init_hooks(&self, _hooks: &QemuHooks<'_, QT, S>) + where + QT: QemuHelperTuple, + { + } + + fn first_exec(&self, _hooks: &QemuHooks<'_, QT, S>) + where + QT: QemuHelperTuple, + { + } + + fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input) { + // unsafe { println!("snapshot post {}",emu::icount_get_raw()) }; + } + + fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) { + // only restore in pre-exec, to preserve the post-execution state for inspection + #[cfg(feature = "snapshot_restore")] + { + #[cfg(feature = "snapshot_fast")] + match self.fastsnap { + Some(s) => emulator.restore_fast_snapshot(s), + None => {self.fastsnap = Some(emulator.create_fast_snapshot(true));}, + } + #[cfg(not(feature = "snapshot_fast"))] + if !self.has_snapshot { + emulator.save_snapshot("Start", true); + self.has_snapshot = true; + } + else + { + emulator.load_snapshot("Start", true); + } + } + #[cfg(not(feature = "snapshot_restore"))] + if !self.has_snapshot { + self.saved_cpu_states = (0..emulator.num_cpus()) + .map(|i| emulator.cpu_from_index(i).save_state()) + .collect(); + self.has_snapshot = true; + } else { + for (i, s) in self.saved_cpu_states.iter().enumerate() { + emulator.cpu_from_index(i).restore_state(s); + } + } + + // unsafe { println!("snapshot pre {}",emu::icount_get_raw()) }; + } +} \ No newline at end of file diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index c3fb065acd..cbf249ad32 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -283,6 +283,8 @@ extern "C" { fn libafl_save_qemu_snapshot(name: *const u8, sync: bool); fn libafl_load_qemu_snapshot(name: *const u8, sync: bool); + + pub fn icount_get_raw() -> u64; } #[cfg(emulation_mode = "systemmode")]