diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 1c8278abf7..b7d53a5d6f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -65,7 +65,7 @@ jobs: - name: Install and cache deps uses: awalsh128/cache-apt-pkgs-action@v1.1.0 with: - packages: llvm llvm-dev clang ninja-build clang-format-13 shellcheck libgtk-3-dev gcc-arm-linux-gnueabi g++-arm-linux-gnueabi + packages: llvm llvm-dev clang ninja-build clang-format-13 shellcheck libgtk-3-dev gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libslirp-dev - name: get clang version run: command -v llvm-config && clang -v - name: Install cargo-hack diff --git a/fuzzers/qemu_systemmode/.gitignore b/fuzzers/qemu_systemmode/.gitignore new file mode 100644 index 0000000000..b511ae114b --- /dev/null +++ b/fuzzers/qemu_systemmode/.gitignore @@ -0,0 +1 @@ +*.qcow2 diff --git a/fuzzers/qemu_systemmode/Cargo.toml b/fuzzers/qemu_systemmode/Cargo.toml new file mode 100644 index 0000000000..d9bf87d0ed --- /dev/null +++ b/fuzzers/qemu_systemmode/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "qemu_systemmode" +version = "0.8.2" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2021" + +[features] +default = ["std"] +std = [] + +[profile.release] +lto = true +codegen-units = 1 +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } diff --git a/fuzzers/qemu_systemmode/README.md b/fuzzers/qemu_systemmode/README.md new file mode 100644 index 0000000000..14098dc09c --- /dev/null +++ b/fuzzers/qemu_systemmode/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/qemu_systemmode/corpus/random b/fuzzers/qemu_systemmode/corpus/random new file mode 100644 index 0000000000..25175d51d3 --- /dev/null +++ b/fuzzers/qemu_systemmode/corpus/random @@ -0,0 +1 @@ +yJv lpZGօrsY˗CMRպ}S7;Io76l1ޘ1R^Όp>G&_|1;ro4ƯUE<`L"6VƷk4/"tf7Fގd]ܮ%|8#wNUn8%4o˗ diff --git a/fuzzers/qemu_systemmode/corpus/zero b/fuzzers/qemu_systemmode/corpus/zero new file mode 100644 index 0000000000..112363ac19 Binary files /dev/null and b/fuzzers/qemu_systemmode/corpus/zero differ diff --git a/fuzzers/qemu_systemmode/example/build.sh b/fuzzers/qemu_systemmode/example/build.sh new file mode 100755 index 0000000000..ceb387b19a --- /dev/null +++ b/fuzzers/qemu_systemmode/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/qemu_systemmode/example/main.c b/fuzzers/qemu_systemmode/example/main.c new file mode 100644 index 0000000000..5b1b1c7216 --- /dev/null +++ b/fuzzers/qemu_systemmode/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); +} \ No newline at end of file diff --git a/fuzzers/qemu_systemmode/example/mps2_m3.ld b/fuzzers/qemu_systemmode/example/mps2_m3.ld new file mode 100644 index 0000000000..adfc15ed78 --- /dev/null +++ b/fuzzers/qemu_systemmode/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/qemu_systemmode/example/startup.c b/fuzzers/qemu_systemmode/example/startup.c new file mode 100644 index 0000000000..3b3acb56b8 --- /dev/null +++ b/fuzzers/qemu_systemmode/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/qemu_systemmode/src/fuzzer.rs b/fuzzers/qemu_systemmode/src/fuzzer.rs new file mode 100644 index 0000000000..9c0a8e3c8c --- /dev/null +++ b/fuzzers/qemu_systemmode/src/fuzzer.rs @@ -0,0 +1,245 @@ +//! 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, QemuExecutor, QemuHooks, + Regs, +}; + +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"); + 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.save_snapshot("start", true); + + emu.set_breakpoint(breakpoint); // BREAKPOINT + + //use libafl_qemu::IntoEnumIterator; + // Save the GPRs + //let mut saved_regs: Vec = vec![]; + //for r in Regs::iter() { + // saved_regs.push(emu.cpu_from_index(0).read_reg(r).unwrap()); + //} + + // 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; + } + + //for (r, v) in saved_regs.iter().enumerate() { + // emu.cpu_from_index(0).write_reg(r as i32, *v).unwrap(); + //} + + 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, + }; + + emu.load_snapshot("start", true); + + 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())); + + // Create a QEMU in-process executor + let executor = QemuExecutor::new( + &mut hooks, + &mut harness, + tuple_list!(edges_observer, time_observer), + &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), + } +} diff --git a/fuzzers/qemu_systemmode/src/main.rs b/fuzzers/qemu_systemmode/src/main.rs new file mode 100644 index 0000000000..bc1e80f767 --- /dev/null +++ b/fuzzers/qemu_systemmode/src/main.rs @@ -0,0 +1,13 @@ +//! A libfuzzer-like fuzzer using qemu for binary-only coverage +#[cfg(target_os = "linux")] +mod fuzzer; + +#[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/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index f539397a75..eabe19c864 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -26,6 +26,8 @@ be = [] usermode = [] systemmode = [] +slirp = [ "systemmode" ] # build qemu with host libslirp (for user networking) + clippy = [] # special feature for clippy, don't use in normal projects§ [dependencies] diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index d62f2b217e..a4bd05a528 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -4,7 +4,7 @@ use which::which; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -const QEMU_REVISION: &str = "ddb71cf43844f8848ae655ca696bdfc3fb7839f1"; +const QEMU_REVISION: &str = "658565bec0a68a84c733217fa9b7802562096559"; fn build_dep_check(tools: &[&str]) { for tool in tools { @@ -42,10 +42,9 @@ pub fn build() { }) }; println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\""); + println!("cargo:rerun-if-env-changed=EMULATION_MODE"); println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=src/asan-giovese.c"); - println!("cargo:rerun-if-changed=src/asan-giovese.h"); println!("cargo:rerun-if-env-changed=CROSS_CC"); // Make sure we have at most one architecutre feature set @@ -73,13 +72,21 @@ pub fn build() { "x86_64".to_string() }) }; + println!("cargo:rerun-if-env-changed=CPU_TARGET"); let jobs = env::var("NUM_JOBS"); - let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| { - println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({cpu_target}) is not the host arch ({}))", env::consts::ARCH); - "cc".to_owned() - }); + let cross_cc = if emulation_mode == "usermode" { + let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| { + println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({cpu_target}) is not the host arch ({}))", env::consts::ARCH); + "cc".to_owned() + }); + println!("cargo:rerun-if-env-changed=CROSS_CC"); + + cross_cc + } else { + String::new() + }; println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\""); @@ -107,6 +114,8 @@ pub fn build() { let custum_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string()); let custum_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok(); + println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_DIR"); + println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_BUILD"); let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = out_dir.to_string_lossy().to_string(); @@ -215,7 +224,11 @@ pub fn build() { //.arg("--as-static-lib") .arg("--as-shared-lib") .arg(&format!("--target-list={cpu_target}-{target_suffix}")) - .arg("--enable-slirp=internal") + .arg(if cfg!(feature = "slirp") { + "--enable-slirp" + } else { + "--disable-slirp" + }) .arg("--enable-fdt=internal") .arg("--audio-drv-list=") .arg("--disable-alsa") @@ -422,7 +435,6 @@ pub fn build() { build_dir.display() )) .arg(format!("{}/libfdt.a", build_dir.display())) - .arg(format!("{}/libslirp.a", build_dir.display())) .arg(format!("{}/libmigration.fa", build_dir.display())) .arg(format!("{}/libhwcore.fa", build_dir.display())) .arg(format!("{}/libqom.fa", build_dir.display())) @@ -457,6 +469,8 @@ pub fn build() { println!("cargo:rustc-link-lib=glib-2.0"); println!("cargo:rustc-link-lib=stdc++"); println!("cargo:rustc-link-lib=z"); + #[cfg(all(feature = "slirp", feature = "systemmode"))] + println!("cargo:rustc-link-lib=slirp"); if emulation_mode == "systemmode" { println!("cargo:rustc-link-lib=pixman-1"); diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index 5d201f540c..35c768ea98 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -7,7 +7,7 @@ use core::{ }; #[cfg(emulation_mode = "usermode")] use core::{mem::MaybeUninit, ptr::copy_nonoverlapping}; -use std::{slice::from_raw_parts, str::from_utf8_unchecked}; +use std::{ffi::CString, slice::from_raw_parts, str::from_utf8_unchecked}; #[cfg(emulation_mode = "usermode")] use libc::c_int; @@ -226,12 +226,10 @@ extern "C" { extern "C" { fn qemu_init(argc: i32, argv: *const *const u8, envp: *const *const u8); + fn vm_start(); fn qemu_main_loop(); fn qemu_cleanup(); - // void libafl_cpu_thread_fn(CPUState *cpu) - fn libafl_cpu_thread_fn(cpu: CPUStatePtr); - // int cpu_memory_rw_debug(CPUState *cpu, target_ulong addr, // uint8_t *buf, int len, int is_write); fn cpu_memory_rw_debug( @@ -241,14 +239,11 @@ extern "C" { len: i32, is_write: i32, ); + fn cpu_physical_memory_rw(addr: GuestAddr, buf: *mut u8, len: i32, iswrite: bool); - static mut libafl_start_vcpu: extern "C" fn(cpu: CPUStatePtr); - - /* - fn libafl_save_qemu_snapshot(name: *const u8); + fn libafl_save_qemu_snapshot(name: *const u8, sync: bool); #[allow(unused)] - fn libafl_load_qemu_snapshot(name: *const u8); - */ + fn libafl_load_qemu_snapshot(name: *const u8, sync: bool); } #[cfg(emulation_mode = "systemmode")] @@ -605,13 +600,6 @@ impl Emulator { Emulator { _private: () } } - #[cfg(emulation_mode = "systemmode")] - pub fn start(&self, cpu: &CPU) { - unsafe { - libafl_cpu_thread_fn(cpu.ptr); - } - } - /// This function gets the memory mappings from the emulator. #[cfg(emulation_mode = "usermode")] #[must_use] @@ -659,11 +647,28 @@ impl Emulator { } pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { - self.current_cpu().unwrap().write_mem(addr, buf); + self.current_cpu() + .unwrap_or_else(|| self.cpu_from_index(0)) + .write_mem(addr, buf); } pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { - self.current_cpu().unwrap().read_mem(addr, buf); + self.current_cpu() + .unwrap_or_else(|| self.cpu_from_index(0)) + .read_mem(addr, buf); + } + + /// Write a value to a phsical guest address, including ROM areas. + #[cfg(emulation_mode = "systemmode")] + pub unsafe fn write_phys_mem(&self, addr: GuestAddr, buf: &[u8]) { + cpu_physical_memory_rw(addr, buf.as_ptr() as *mut u8, buf.len() as i32, true); + } + + /// Read a value from a physical guest address. + #[cfg(emulation_mode = "systemmode")] + pub unsafe fn read_phys_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + #[cfg(emulation_mode = "systemmode")] + cpu_physical_memory_rw(addr, buf.as_mut_ptr(), buf.len() as i32, false); } #[must_use] @@ -723,7 +728,10 @@ impl Emulator { #[cfg(emulation_mode = "usermode")] libafl_qemu_run(); #[cfg(emulation_mode = "systemmode")] - qemu_main_loop(); + { + vm_start(); + qemu_main_loop(); + } } #[cfg(emulation_mode = "usermode")] @@ -896,13 +904,6 @@ impl Emulator { unsafe { libafl_add_backdoor_hook(exec, data) }; } - #[cfg(emulation_mode = "systemmode")] - pub fn set_vcpu_start(&self, hook: extern "C" fn(cpu: CPU)) { - unsafe { - libafl_start_vcpu = core::mem::transmute(hook); - } - } - #[cfg(emulation_mode = "usermode")] pub fn set_on_thread_hook(&self, hook: extern "C" fn(tid: u32)) { unsafe { @@ -910,17 +911,17 @@ impl Emulator { } } - /*#[cfg(emulation_mode = "systemmode")] - pub fn save_snapshot(&self, name: &str) { + #[cfg(emulation_mode = "systemmode")] + pub fn save_snapshot(&self, name: &str, sync: bool) { let s = CString::new(name).expect("Invalid snapshot name"); - unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _) }; + unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) }; } #[cfg(emulation_mode = "systemmode")] - pub fn load_snapshot(&self, name: &str) { + pub fn load_snapshot(&self, name: &str, sync: bool) { let s = CString::new(name).expect("Invalid snapshot name"); - unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _) }; - }*/ + unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _, sync) }; + } #[cfg(emulation_mode = "usermode")] pub fn set_pre_syscall_hook(