From c6062889d53053b5b257e714d30f836f2795d704 Mon Sep 17 00:00:00 2001 From: WorksButNotTested <62701594+WorksButNotTested@users.noreply.github.com> Date: Fri, 30 Jun 2023 01:18:00 +0100 Subject: [PATCH] Extend qemu_launcher to support multiple architectures (#1328) * Change qemu_launcher fuzzer to support multiple architectures and remove qemu_arm_launcher * Review changes * Changes to milliseconds --------- Co-authored-by: Your Name --- fuzzers/qemu_arm_launcher/.gitignore | 7 - fuzzers/qemu_arm_launcher/Cargo.toml | 20 -- fuzzers/qemu_arm_launcher/Makefile.toml | 173 ---------- fuzzers/qemu_arm_launcher/README.md | 24 -- .../qemu_arm_launcher/corpus/not_kitty.png | Bin 218 -> 0 bytes .../corpus/not_kitty_alpha.png | Bin 376 -> 0 bytes .../corpus/not_kitty_gamma.png | Bin 228 -> 0 bytes .../corpus/not_kitty_icc.png | Bin 427 -> 0 bytes fuzzers/qemu_arm_launcher/harness.cc | 193 ----------- fuzzers/qemu_arm_launcher/src/fuzzer.rs | 238 -------------- fuzzers/qemu_arm_launcher/src/main.rs | 13 - fuzzers/qemu_launcher/Cargo.toml | 23 +- fuzzers/qemu_launcher/Makefile.toml | 307 +++++++++++++++--- fuzzers/qemu_launcher/README.md | 69 ++-- fuzzers/qemu_launcher/build.rs | 48 +++ fuzzers/qemu_launcher/harness.cc | 2 +- fuzzers/qemu_launcher/src/fuzzer.rs | 291 ++++++++++++++--- 17 files changed, 609 insertions(+), 799 deletions(-) delete mode 100644 fuzzers/qemu_arm_launcher/.gitignore delete mode 100644 fuzzers/qemu_arm_launcher/Cargo.toml delete mode 100644 fuzzers/qemu_arm_launcher/Makefile.toml delete mode 100644 fuzzers/qemu_arm_launcher/README.md delete mode 100644 fuzzers/qemu_arm_launcher/corpus/not_kitty.png delete mode 100644 fuzzers/qemu_arm_launcher/corpus/not_kitty_alpha.png delete mode 100644 fuzzers/qemu_arm_launcher/corpus/not_kitty_gamma.png delete mode 100644 fuzzers/qemu_arm_launcher/corpus/not_kitty_icc.png delete mode 100644 fuzzers/qemu_arm_launcher/harness.cc delete mode 100644 fuzzers/qemu_arm_launcher/src/fuzzer.rs delete mode 100644 fuzzers/qemu_arm_launcher/src/main.rs create mode 100644 fuzzers/qemu_launcher/build.rs diff --git a/fuzzers/qemu_arm_launcher/.gitignore b/fuzzers/qemu_arm_launcher/.gitignore deleted file mode 100644 index 11fb5666bc..0000000000 --- a/fuzzers/qemu_arm_launcher/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -libpng-* -libpng_harness -libpng_harness_crashing -zlib-* -crashes -target -drcov.log diff --git a/fuzzers/qemu_arm_launcher/Cargo.toml b/fuzzers/qemu_arm_launcher/Cargo.toml deleted file mode 100644 index 8664cbb855..0000000000 --- a/fuzzers/qemu_arm_launcher/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "qemu_arm_launcher" -version = "0.10.1" -authors = ["Andrea Fioraldi ", "Dominik Maier "] -edition = "2021" - -[features] -default = ["std"] -std = [] - -[profile.release] -#lto = true -#codegen-units = 1 -#opt-level = 3 -debug = true - -[dependencies] -libafl = { path = "../../libafl/" } -libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "usermode"] } -rangemap = "1.0.3" diff --git a/fuzzers/qemu_arm_launcher/Makefile.toml b/fuzzers/qemu_arm_launcher/Makefile.toml deleted file mode 100644 index fffe80e10b..0000000000 --- a/fuzzers/qemu_arm_launcher/Makefile.toml +++ /dev/null @@ -1,173 +0,0 @@ -# Variables -[env] -FUZZER_NAME='libpng_harness' -FUZZER_NAME_CRASHING='libpng_harness_crashing' -PROJECT_DIR = { script = ["pwd"] } -CROSS_CC = "arm-linux-gnueabi-gcc" - -[tasks.unsupported] -script_runner="@shell" -script=''' -echo "Qemu fuzzer not supported on windows/mac" -''' - -#zlib -[tasks.zlib] -linux_alias = "zlib_unix" -mac_alias = "unsupported" -windows_alias = "unsupported" - -[tasks.zlib_unix_wget] -condition = { files_not_exist = [ "./zlib-1.2.13" ] } -script_runner="@shell" -# NOTE: There's no specific reason we're using an old version of zlib, -# but newer versions get moved to fossils/ after a while. -script=''' -wget https://zlib.net/fossils/zlib-1.2.13.tar.gz -tar -xvf zlib-1.2.13.tar.gz -''' - -[tasks.zlib_unix] -condition = { files_not_exist = [ "./zlib-1.2.13/zlib/lib/libz.a" ] } -script_runner="@shell" -script=''' -cd zlib-1.2.13 && CC=$CROSS_CC ./configure --prefix=./zlib -make install -''' -dependencies = [ "zlib_unix_wget" ] - -# libpng -[tasks.libpng] -linux_alias = "libpng_unix" -mac_alias = "unsupported" -windows_alias = "unsupported" - -[tasks.libpng_unix_wget] -condition = { files_not_exist = [ "./libpng-1.6.37" ] } -script_runner="@shell" -script=''' -wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz -tar -xvf libpng-1.6.37.tar.xz -''' - -[tasks.libpng_unix] -condition = { files_not_exist = [ "./libpng-1.6.37/.libs/libpng16.a" ] } -script_runner="@shell" -script=''' -cd libpng-1.6.37 && CC=$CROSS_CC CFLAGS=-I../zlib-1.2.13/zlib/lib LDFLAGS=-L../zlib-1.2.13/zlib/lib ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes --host=arm -make -''' -dependencies = [ "zlib", "libpng_unix_wget" ] - -# fuzzer -[tasks.fuzzer] -linux_alias = "fuzzer_unix" -mac_alias = "fuzzer_unix" -windows_alias = "unsupported" - -[tasks.fuzzer_unix] -command = "cargo" -args = ["build", "--release"] - -# Harness -[tasks.harness] -linux_alias = "harness_unix" -mac_alias = "unsupported" -windows_alias = "unsupported" - -[tasks.harness_unix] -script_runner="@shell" -script=''' -# Build the libpng harness -arm-linux-gnueabi-g++ \ - ./harness.cc \ - ./libpng-1.6.37/.libs/libpng16.a \ - ./zlib-1.2.13/zlib/lib/libz.a \ - -I./libpng-1.6.37/ \ - -I../zlib-1.2.13/zlib/lib \ - -L../zlib-1.2.13/zlib/lib \ - -o ${FUZZER_NAME} \ - -lm \ - -static -''' -dependencies = [ "libpng" ] - -# Run the fuzzer -[tasks.run] -linux_alias = "run_unix" -mac_alias = "run_unix" -windows_alias = "unsupported" - -[tasks.run_unix] -command = "cargo" -args = ["run", "--release", "./${FUZZER_NAME}"] -dependencies = [ "harness", "fuzzer" ] - -# Harness with an artifical crash -[tasks.harness_crashing] -linux_alias = "harness_unix_crashing" -mac_alias = "unsupported" -windows_alias = "unsupported" - -[tasks.harness_unix_crashing] -script_runner="@shell" -script=''' -# Build the libpng harness -arm-linux-gnueabi-g++ \ - ./harness.cc \ - ./libpng-1.6.37/.libs/libpng16.a \ - ./zlib-1.2.13/zlib/lib/libz.a \ - -I./libpng-1.6.37/ \ - -I../zlib-1.2.13/zlib/lib \ - -L../zlib-1.2.13/zlib/lib \ - -o ${FUZZER_NAME_CRASHING} \ - -lm \ - -DHAS_DUMMY_CRASH \ - -static -''' -dependencies = [ "libpng" ] - -# Run the fuzzer with an artificial crash -[tasks.run_crashing] -linux_alias = "run_unix_crashing" -mac_alias = "unsupported" -windows_alias = "unsupported" - -[tasks.run_unix_crashing] -command = "cargo" -args = ["run", "--release", "./${FUZZER_NAME_CRASHING}"] -dependencies = [ "harness_crashing", "fuzzer" ] - -# Run the fuzzer -[tasks.test] -linux_alias = "test_unix" -mac_alias = "test_unix" -windows_alias = "unsupported" - -# Short test -[tasks.test_unix] -script_runner = "@shell" -script=''' -rm -rf libafl_unix_shmem_server || true -timeout 11s ./target/release/qemu_arm_launcher ./${FUZZER_NAME} 2>/dev/null >fuzz_stdout.log || true -echo "This test is skipped" -''' -dependencies = [ "harness", "fuzzer" ] - -# Clean up -[tasks.clean] -linux_alias = "clean_unix" -mac_alias = "clean_unix" -windows_alias = "unsupported" - -[tasks.clean_unix] -# Disable default `clean` definition -clear = true -script_runner="@shell" -script=''' -rm -f ./${FUZZER_NAME} -rm -f ./${FUZZER_NAME_CRASHING} -rm -rf zlib-* -rm -rf libpng-* -cargo clean -''' diff --git a/fuzzers/qemu_arm_launcher/README.md b/fuzzers/qemu_arm_launcher/README.md deleted file mode 100644 index 8694fac552..0000000000 --- a/fuzzers/qemu_arm_launcher/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# LibAFL with launcher for libpng with qemu arm32 in usermode - -This folder contains an example fuzzer for libpng using the qemu emulator in arm32 usermode. -To show off crash detection, we added an optional undefined instruction to the harness. -Everything has been tested on Linux. - -In contrast to the normal libfuzzer libpng example, this uses the `launcher` feature, that automatically spawns `n` child processes, and binds them to a free core. - -## Prerequisites -```bash -sudo apt install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi -``` - -## Run - -```bash -cargo make run -``` - -## Run with artifical crash - -```bash -cargo make run_crashing -``` diff --git a/fuzzers/qemu_arm_launcher/corpus/not_kitty.png b/fuzzers/qemu_arm_launcher/corpus/not_kitty.png deleted file mode 100644 index eff7c1707b936a8f8df725814f604d454b78b5c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X_yc@GT+_~+`TzevkY_wIZRYx+5&y#hyq+?%!C8<`)MX5lF!N|bSRM)^r*U&J;z}U*bz{;0L z1Vuw`eoAIqC5i?kD`P_|6GMoGiCWXn12ss3YzWRzD=AMbN@Z|N$xljE@XSq2PYp^< WOsOn9nQ8-6#Ng@b=d#Wzp$PyV*n0l} diff --git a/fuzzers/qemu_arm_launcher/corpus/not_kitty_gamma.png b/fuzzers/qemu_arm_launcher/corpus/not_kitty_gamma.png deleted file mode 100644 index 939d9d29a9b9f95bac5e9a72854361ee85469921..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmTQ929t;oCfmw1AIbU z)6Sgv|NlRbXFM})=KnKxKI=t+9LW;bh?3y^w370~qErUQl>DSr1<%~X^wgl##FWay zlc_d9MbVxvjv*GO?@o5)YH;9THa`3B|5>?^8?LvjJ}xLe>!7e@k)r^sLedir0mCVe z=5sMjEm$*~tHD+}{NS_$nMdb|ABqg-@UGMMsZ=uY-X%Cq@&3vmZ%&@H{P?6&+U!yq VvuXWlo?M_c44$rjF6*2UngF4cP+$N6 diff --git a/fuzzers/qemu_arm_launcher/corpus/not_kitty_icc.png b/fuzzers/qemu_arm_launcher/corpus/not_kitty_icc.png deleted file mode 100644 index f0c7804d99829cc6307c1c6ae9915cf42d555414..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 427 zcmV;c0aX5pP)9xSWu9|B*4Isn^#g47m^r~thH)GiR<@yX0fO)OF<2Kt#qCldyUF#H?{4jV?XGw9)psxE&K1B1m^ z1_tH{2(hG@3=G>_85ksPA;eS`Ffj19FfeR8pIlm01~rBeWCZ{dbvfq;rA3DT000kA zOjJc?%*_A){{R30GnreSaefwW^{L9a%BKPWN%_+AW3auXJt}l zVPtu6$z?nM003J_L_t(I%iWVf3V=Wi12fJ3|IHp$*hSlV@t||fKp?cDK@bHXV&o_g zF_hw;3ILUGteXmeJsVfSmcVJno)^MdQwU3bFHCtNG)uY>mLcD%`0UBaIq~Fq8#dBr V12uok3~c}a002ovPDHLkV1nKBo!S5Z diff --git a/fuzzers/qemu_arm_launcher/harness.cc b/fuzzers/qemu_arm_launcher/harness.cc deleted file mode 100644 index d9e149d5cb..0000000000 --- a/fuzzers/qemu_arm_launcher/harness.cc +++ /dev/null @@ -1,193 +0,0 @@ -// libpng_read_fuzzer.cc -// Copyright 2017-2018 Glenn Randers-Pehrson -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that may -// be found in the LICENSE file https://cs.chromium.org/chromium/src/LICENSE - -// Last changed in libpng 1.6.35 [July 15, 2018] - -// The modifications in 2017 by Glenn Randers-Pehrson include -// 1. addition of a PNG_CLEANUP macro, -// 2. setting the option to ignore ADLER32 checksums, -// 3. adding "#include " which is needed on some platforms -// to provide memcpy(). -// 4. adding read_end_info() and creating an end_info structure. -// 5. adding calls to png_set_*() transforms commonly used by browsers. - -#include -#include -#include - -#include - -#define PNG_INTERNAL -#include "png.h" - -#define PNG_CLEANUP \ - if (png_handler.png_ptr) { \ - if (png_handler.row_ptr) \ - png_free(png_handler.png_ptr, png_handler.row_ptr); \ - if (png_handler.end_info_ptr) \ - png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr, \ - &png_handler.end_info_ptr); \ - else if (png_handler.info_ptr) \ - png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr, \ - nullptr); \ - else \ - png_destroy_read_struct(&png_handler.png_ptr, nullptr, nullptr); \ - png_handler.png_ptr = nullptr; \ - png_handler.row_ptr = nullptr; \ - png_handler.info_ptr = nullptr; \ - png_handler.end_info_ptr = nullptr; \ - } - -struct BufState { - const uint8_t *data; - size_t bytes_left; -}; - -struct PngObjectHandler { - png_infop info_ptr = nullptr; - png_structp png_ptr = nullptr; - png_infop end_info_ptr = nullptr; - png_voidp row_ptr = nullptr; - BufState *buf_state = nullptr; - - ~PngObjectHandler() { - if (row_ptr) { png_free(png_ptr, row_ptr); } - if (end_info_ptr) - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr); - else if (info_ptr) - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - else - png_destroy_read_struct(&png_ptr, nullptr, nullptr); - delete buf_state; - } -}; - -void user_read_data(png_structp png_ptr, png_bytep data, size_t length) { - BufState *buf_state = static_cast(png_get_io_ptr(png_ptr)); - if (length > buf_state->bytes_left) { png_error(png_ptr, "read error"); } - memcpy(data, buf_state->data, length); - buf_state->bytes_left -= length; - buf_state->data += length; -} - -static const int kPngHeaderSize = 8; - -// Entry point for LibFuzzer. -// Roughly follows the libpng book example: -// http://www.libpng.org/pub/png/book/chapter13.html -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - if (size < kPngHeaderSize) { return 0; } - - std::vector v(data, data + size); - if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { - // not a PNG. - return 0; - } - - PngObjectHandler png_handler; - png_handler.png_ptr = nullptr; - png_handler.row_ptr = nullptr; - png_handler.info_ptr = nullptr; - png_handler.end_info_ptr = nullptr; - - png_handler.png_ptr = - png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png_handler.png_ptr) { return 0; } - - png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr); - if (!png_handler.info_ptr) { - PNG_CLEANUP - return 0; - } - - png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr); - if (!png_handler.end_info_ptr) { - PNG_CLEANUP - return 0; - } - - png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); -#ifdef PNG_IGNORE_ADLER32 - png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); -#endif - - // Setting up reading from buffer. - png_handler.buf_state = new BufState(); - png_handler.buf_state->data = data + kPngHeaderSize; - png_handler.buf_state->bytes_left = size - kPngHeaderSize; - png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data); - png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize); - - if (setjmp(png_jmpbuf(png_handler.png_ptr))) { - PNG_CLEANUP - return 0; - } - - // Reading. - png_read_info(png_handler.png_ptr, png_handler.info_ptr); - - // reset error handler to put png_deleter into scope. - if (setjmp(png_jmpbuf(png_handler.png_ptr))) { - PNG_CLEANUP - return 0; - } - - png_uint_32 width, height; - int bit_depth, color_type, interlace_type, compression_type; - int filter_type; - - if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, &height, - &bit_depth, &color_type, &interlace_type, &compression_type, - &filter_type)) { - PNG_CLEANUP - return 0; - } - - // This is going to be too slow. - if (width && height > 100000000 / width) { - PNG_CLEANUP -#ifdef HAS_DUMMY_CRASH - #if defined(__aarch64__) || defined(__arm__) - asm volatile(".word 0xf7f0a000\n"); - #else - asm("ud2"); - #endif -#endif - return 0; - } - - // Set several transforms that browsers typically use: - png_set_gray_to_rgb(png_handler.png_ptr); - png_set_expand(png_handler.png_ptr); - png_set_packing(png_handler.png_ptr); - png_set_scale_16(png_handler.png_ptr); - png_set_tRNS_to_alpha(png_handler.png_ptr); - - int passes = png_set_interlace_handling(png_handler.png_ptr); - - png_read_update_info(png_handler.png_ptr, png_handler.info_ptr); - - png_handler.row_ptr = - png_malloc(png_handler.png_ptr, - png_get_rowbytes(png_handler.png_ptr, png_handler.info_ptr)); - - for (int pass = 0; pass < passes; ++pass) { - for (png_uint_32 y = 0; y < height; ++y) { - png_read_row(png_handler.png_ptr, - static_cast(png_handler.row_ptr), nullptr); - } - } - - png_read_end(png_handler.png_ptr, png_handler.end_info_ptr); - - PNG_CLEANUP - return 0; -} - -int main() { - uint8_t buf[10] = {0}; - LLVMFuzzerTestOneInput(buf, 10); -} diff --git a/fuzzers/qemu_arm_launcher/src/fuzzer.rs b/fuzzers/qemu_arm_launcher/src/fuzzer.rs deleted file mode 100644 index 57cbc13b08..0000000000 --- a/fuzzers/qemu_arm_launcher/src/fuzzer.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! A libfuzzer-like fuzzer using qemu for binary-only coverage -//! -use core::{ptr::addr_of_mut, 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::{HitcountsMapObserver, TimeObserver, VariableMapObserver}, - schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, - stages::StdMutationalStage, - state::{HasCorpus, StdState}, - Error, -}; -use libafl_qemu::{ - drcov::QemuDrCovHelper, - //asan::QemuAsanHelper, - edges::{edges_map_mut_slice, QemuEdgeCoverageHelper, MAX_EDGES_NUM}, - elf::EasyElf, - emu::Emulator, - MmapPerms, - QemuExecutor, - QemuHooks, - QemuInstrumentationFilter, - Regs, -}; -use rangemap::RangeMap; - -pub fn fuzz() { - // Hardcoded parameters - let timeout = Duration::from_secs(1); - let broker_port = 1337; - let cores = Cores::from_cmdline("0-11").unwrap(); - let corpus_dirs = [PathBuf::from("./corpus")]; - let objective_dir = PathBuf::from("./crashes"); - - // Initialize QEMU - env::remove_var("LD_LIBRARY_PATH"); - let args: Vec = env::args().collect(); - let env: Vec<(String, String)> = env::vars().collect(); - let emu = Emulator::new(&args, &env).unwrap(); - - let mut elf_buffer = Vec::new(); - let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer).unwrap(); - - let test_one_input_ptr = elf - .resolve_symbol("LLVMFuzzerTestOneInput", emu.load_addr()) - .expect("Symbol LLVMFuzzerTestOneInput not found"); - println!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - - emu.set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput - unsafe { emu.run() }; - - println!("Break at {:#x}", emu.read_reg::<_, u64>(Regs::Pc).unwrap()); - - // Get the return address - let stack_ptr: u64 = emu.read_reg(Regs::Sp).unwrap(); - let ret_addr: u32 = emu.read_reg(Regs::Lr).unwrap(); - - println!("Stack pointer = {stack_ptr:#x}"); - println!("Return address = {ret_addr:#x}"); - - emu.remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput - emu.set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr - - let input_addr = emu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap(); - println!("Placing input at {input_addr:#x}"); - - // 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 mut len = buf.len(); - if len > 4096 { - buf = &buf[0..4096]; - len = 4096; - } - - unsafe { - emu.write_mem(input_addr, buf); - - emu.write_reg(Regs::R0, input_addr).unwrap(); - emu.write_reg(Regs::R1, len).unwrap(); - emu.write_reg(Regs::Pc, test_one_input_ptr).unwrap(); - emu.write_reg(Regs::Lr, ret_addr).unwrap(); - - emu.run(); - } - - ExitKind::Ok - }; - - let mut run_client = |state: Option<_>, mut mgr, _core_id| { - // Create an observation channel using the coverage map - let edges_observer = unsafe { - HitcountsMapObserver::new(VariableMapObserver::from_mut_slice( - "edges", - edges_map_mut_slice(), - addr_of_mut!(MAX_EDGES_NUM), - )) - }; - - // 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::tracking(&edges_observer, true, false), - // Time feedback, this one does not need a feedback state - TimeFeedback::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 rangemap = RangeMap::::new(); - let mappings = emu.mappings(); - let mut idx = 0; - for map in mappings { - if map.path().unwrap() != "" { - rangemap.insert( - (map.start() as usize)..(map.end() as usize), - (idx, map.path().unwrap().to_string()), - ); - idx += 1; - } - } - let mut hooks = QemuHooks::new( - &emu, - tuple_list!( - QemuEdgeCoverageHelper::default(), - QemuDrCovHelper::new( - QemuInstrumentationFilter::None, - rangemap, - PathBuf::from("drcov.log"), - false, - ) - ), - ); - - // 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.must_load_initial_inputs() { - 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)?; - 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}")); - - // 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_arm_launcher/src/main.rs b/fuzzers/qemu_arm_launcher/src/main.rs deleted file mode 100644 index bc1e80f767..0000000000 --- a/fuzzers/qemu_arm_launcher/src/main.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! 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/fuzzers/qemu_launcher/Cargo.toml b/fuzzers/qemu_launcher/Cargo.toml index 36bf503f46..8e80c01c8e 100644 --- a/fuzzers/qemu_launcher/Cargo.toml +++ b/fuzzers/qemu_launcher/Cargo.toml @@ -7,6 +7,14 @@ edition = "2021" [features] default = ["std"] std = [] +be = [] +64bit = [] +arm = ["libafl_qemu/arm"] +x86_64 = ["libafl_qemu/x86_64", "64bit"] +i386 = ["libafl_qemu/i386"] +aarch64 = ["libafl_qemu/aarch64", "64bit"] +mips = ["libafl_qemu/mips"] +ppc = ["libafl_qemu/ppc", "be"] [profile.release] lto = true @@ -14,6 +22,19 @@ codegen-units = 1 opt-level = 3 debug = true +[build-dependencies] +vergen = { version = "8.2.1", features = ["build", "cargo", "git", "gitcl", "rustc", "si"] } + [dependencies] +clap = { version = "4.3.0", features = ["derive", "string"]} libafl = { path = "../../libafl/" } -libafl_qemu = { path = "../../libafl_qemu/", features = ["x86_64", "usermode"] } +libafl_qemu = { path = "../../libafl_qemu/", features = ["usermode"] } +rangemap = { version = "1.0.3" } + + + + + + + + diff --git a/fuzzers/qemu_launcher/Makefile.toml b/fuzzers/qemu_launcher/Makefile.toml index d02da0d51c..1ef2d036ec 100644 --- a/fuzzers/qemu_launcher/Makefile.toml +++ b/fuzzers/qemu_launcher/Makefile.toml @@ -1,86 +1,250 @@ -# Variables [env] -FUZZER_NAME='libpng_harness' -PROJECT_DIR = { script = ["pwd"] } +CROSS_CC = "x86_64-linux-gnu-gcc" +CROSS_CXX = "x86_64-linux-gnu-g++" +CROSS_CFLAGS = "" +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/x86_64" +LIBPNG_ARCH = "x86_64" +LIBPNG_OPTIMIZATIONS = "yes" +FEATURE = "x86_64" +#LIBAFL_DEBUG_OUTPUT = "1" +#CUSTOM_QEMU_DIR= "~/qemu-libafl-bridge" + +[env.arm] +CROSS_CC = "arm-linux-gnueabi-gcc" +CROSS_CXX = "arm-linux-gnueabi-g++" +CROSS_CFLAGS = "" +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/arm" +LIBPNG_ARCH = "arm" +LIBPNG_OPTIMIZATIONS = "yes" +FEATURE = "arm" + +[env.aarch64] +CROSS_CC = "aarch64-linux-gnu-gcc" +CROSS_CXX = "aarch64-linux-gnu-g++" +CROSS_CFLAGS = "" +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/aarch64" +LIBPNG_ARCH = "aarch64" +LIBPNG_OPTIMIZATIONS = "yes" +FEATURE = "aarch64" + +[env.x86_64] +CROSS_CC = "x86_64-linux-gnu-gcc" +CROSS_CXX = "x86_64-linux-gnu-g++" +CROSS_CFLAGS = "" +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/x86_64" +LIBPNG_ARCH = "x86_64" +LIBPNG_OPTIMIZATIONS = "yes" +FEATURE = "x86_64" + +[env.i386] +CROSS_CC = "x86_64-linux-gnu-gcc" +CROSS_CXX = "x86_64-linux-gnu-g++" +CROSS_CFLAGS = "-m32" +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/i386" +LIBPNG_ARCH = "i386" +LIBPNG_OPTIMIZATIONS = "yes" +FEATURE = "i386" + +[env.mips] +CROSS_CC = "mipsel-linux-gnu-gcc" +CROSS_CXX = "mipsel-linux-gnu-g++" +CROSS_CFLAGS = "" +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/mips" +LIBPNG_ARCH = "mips" +LIBPNG_OPTIMIZATIONS = "yes" +FEATURE = "mips" + +[env.ppc] +CROSS_CC = "powerpc-linux-gnu-gcc" +CROSS_CXX = "powerpc-linux-gnu-g++" +CROSS_CFLAGS = "" +TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/ppc" +LIBPNG_ARCH = "ppc" +LIBPNG_OPTIMIZATIONS = "no" +FEATURE = "ppc" [tasks.unsupported] script_runner="@shell" script=''' -echo "Qemu fuzzer not supported on windows" +echo "Qemu fuzzer not supported on windows/mac" ''' -# libpng -[tasks.libpng] -linux_alias = "libpng_unix" -mac_alias = "libpng_unix" -windows_alias = "unsupported" -[tasks.libpng_unix] -condition = { files_not_exist = [ "./libpng-1.6.37" ] } +[tasks.target_dir] +condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" ] } script_runner="@shell" script=''' -wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz -tar -xvf libpng-1.6.37.tar.xz +mkdir ${CARGO_MAKE_CRATE_TARGET_DIRECTORY} ''' -# fuzzer -[tasks.fuzzer] -linux_alias = "fuzzer_unix" -mac_alias = "fuzzer_unix" +[tasks.deps_dir] +dependencies = ["target_dir"] +condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/" ] } +script_runner="@shell" +script=''' +mkdir ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/ +''' + +[tasks.arch_target_dir] +dependencies = ["target_dir"] +condition = { files_not_exist = [ "${TARGET_DIR}" ] } +script_runner="@shell" +script=''' +mkdir ${TARGET_DIR} +''' + +[tasks.zlib] +linux_alias = "zlib_unix" +mac_alias = "unsupported" windows_alias = "unsupported" -[tasks.fuzzer_unix] -command = "cargo" -args = ["build", "--release"] +[tasks.zlib_unix_wget] +dependencies = ["deps_dir"] +condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/zlib-1.2.13" ] } +script_runner="@shell" +# NOTE: There's no specific reason we're using an old version of zlib, +# but newer versions get moved to fossils/ after a while. +script=''' +wget \ + -O "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/zlib-1.2.13.tar.gz" \ + https://zlib.net/fossils/zlib-1.2.13.tar.gz + +tar \ + zxvf ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/zlib-1.2.13.tar.gz \ + -C ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/ +''' + +[tasks.zlib_unix] +dependencies = ["arch_target_dir", "zlib_unix_wget" ] +condition = { files_not_exist = [ "${TARGET_DIR}/build-zlib/libz.a" ] } +script_runner="@shell" +script=''' +rm -rf ${TARGET_DIR}/build-zlib/ + +mkdir ${TARGET_DIR}/build-zlib/ + +cd ${TARGET_DIR}/build-zlib/ && \ + CC=$CROSS_CC \ + CFLAGS=${CROSS_CFLAGS} \ + ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/zlib-1.2.13/configure \ + --prefix=./zlib + +make install +''' + +[tasks.libpng] +linux_alias = "libpng_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.libpng_unix_wget] +dependencies = ["deps_dir"] +condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/libpng-1.6.37" ] } +script_runner="@shell" +script=''' +wget \ + -O "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/libpng-1.6.37.tar.xz" \ + https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz + +tar \ + -xvf "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/libpng-1.6.37.tar.xz" \ + -C ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/ +''' + +[tasks.libpng_unix] +dependencies = [ "arch_target_dir", "zlib", "libpng_unix_wget" ] +condition = { files_not_exist = [ "${TARGET_DIR}/build-png/.libs/libpng16.a" ] } +script_runner="@shell" +script=''' +rm -rf ${TARGET_DIR}/build-png/ + +mkdir ${TARGET_DIR}/build-png/ + +cd ${TARGET_DIR}/build-png/ && \ + CC=$CROSS_CC \ + CFLAGS="${CROSS_CFLAGS} -I"${TARGET_DIR}/build-zlib/zlib/lib"" \ + LDFLAGS=-L"${TARGET_DIR}/build-zlib/zlib/lib" \ + ${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/deps/libpng-1.6.37/configure \ + --enable-shared=no \ + --with-pic=yes \ + --enable-hardware-optimizations=${LIBPNG_OPTIMIZATIONS} \ + --host=${LIBPNG_ARCH} \ + +make +''' + +[tasks.build] +linux_alias = "build_unix" +mac_alias = "build_unix" +windows_alias = "unsupported" + +[tasks.build_unix] +command = "cargo" +args = [ + "build", + "--release", + "--features", "${FEATURE}", + "--target-dir", "${TARGET_DIR}" +] + +[tasks.fuzzer] +dependencies = ["build"] +script_runner="@shell" +script=''' +mv ${TARGET_DIR}/release/qemu_launcher ${TARGET_DIR}/release/qemu_launcher-${CARGO_MAKE_PROFILE} +''' -# Harness [tasks.harness] linux_alias = "harness_unix" -mac_alias = "harness_unix" +mac_alias = "unsupported" windows_alias = "unsupported" [tasks.harness_unix] script_runner="@shell" script=''' -cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes -cd "${PROJECT_DIR}" -make -C libpng-1.6.37 -# Build the libpng harness -c++ \ +${CROSS_CXX} \ ./harness.cc \ - ./libpng-1.6.37/.libs/libpng16.a \ - -I./libpng-1.6.37/ \ - -o ${FUZZER_NAME} \ - -lm -lz + $CROSS_CFLAGS \ + "${TARGET_DIR}/build-png/.libs/libpng16.a" \ + "${TARGET_DIR}/build-zlib/libz.a" \ + -I"${TARGET_DIR}/build-png" \ + -I"${TARGET_DIR}/build-zlib/zlib/lib" \ + -L"${TARGET_DIR}/build-zlib/zlib/lib" \ + -o"${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}" \ + -lm \ + -static ''' dependencies = [ "libpng" ] -# Run the fuzzer [tasks.run] linux_alias = "run_unix" -mac_alias = "run_unix" +mac_alias = "unsupported" windows_alias = "unsupported" [tasks.run_unix] -command = "cargo" -args = ["run", "--release", "./${FUZZER_NAME}"] +command = "${TARGET_DIR}/release/qemu_launcher-${CARGO_MAKE_PROFILE}" +args = [ + "--coverage", "${TARGET_DIR}/drcov.log", + "--input", "./corpus", + "--output", "${TARGET_DIR}/output/", + "--", + "${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}", +] dependencies = [ "harness", "fuzzer" ] -# Run the fuzzer [tasks.test] linux_alias = "test_unix" -mac_alias = "test_unix" +mac_alias = "unsupported" windows_alias = "unsupported" -# Short test [tasks.test_unix] +# Tidy up after we've run our tests so we don't hog all the disk space +dependencies = [ "clean" ] script_runner = "@shell" script=''' echo "This test is skipped" ''' -dependencies = [ "harness", "fuzzer" ] -# Clean up [tasks.clean] linux_alias = "clean_unix" mac_alias = "clean_unix" @@ -91,7 +255,64 @@ windows_alias = "unsupported" clear = true script_runner="@shell" script=''' -rm -f ./${FUZZER_NAME} -make -C libpng-1.6.37 clean +rm -rf ${CARGO_MAKE_CRATE_TARGET_DIRECTORY} cargo clean -''' \ No newline at end of file +''' + +[tasks.arm] +command = "cargo" +args = [ + "make", + "-p", "arm", + "run", +] + +[tasks.aarch64] +command = "cargo" +args = [ + "make", + "-p", "aarch64", + "run", +] + +[tasks.x86_64] +command = "cargo" +args = [ + "make", + "-p", "x86_64", + "run", +] + +[tasks.i386] +command = "cargo" +args = [ + "make", + "-p", "i386", + "run", +] + +[tasks.mips] +command = "cargo" +args = [ + "make", + "-p", "mips", + "run", +] + +[tasks.ppc] +command = "cargo" +args = [ + "make", + "-p", "ppc", + "run", +] + +[tasks.all] +dependencies = [ + "arm", + "aarch64", + "x86_64", + "i386", + "mips", + "ppc" +] diff --git a/fuzzers/qemu_launcher/README.md b/fuzzers/qemu_launcher/README.md index b96b0e851c..c955e83f73 100644 --- a/fuzzers/qemu_launcher/README.md +++ b/fuzzers/qemu_launcher/README.md @@ -1,47 +1,38 @@ -# Libfuzzer for libpng, with launcher +# qemu_launcher_ -This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. -To show off crash detection, we added a `ud2` instruction to the harness, edit harness.cc if you want a non-crashing example. -It has been tested on Linux. +This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. It has been tested on Linux. +This automatically spawns n child processes, and binds them to a free core. -In contrast to the normal libfuzzer libpng example, this uses the `launcher` feature, that automatically spawns `n` child processes, and binds them to a free core. - -## Build - -To build this example, run +The following architectures are supported: +* arm +* aarch64 +* i386 +* x86_64 +* mips +* ppc +## Prerequisites ```bash -cargo build --release +sudo apt install \ + gcc-arm-linux-gnueabi \ + g++-arm-linux-gnueabi \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu \ + gcc \ + g++ \ + gcc-mipsel-linux-gnu \ + g++-mipsel-linux-gnu \ + gcc-powerpc-linux-gnu \ + g++-powerpc-linux-gnu ``` -This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback. -In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target. - -Then download libpng, and unpack the archive: -```bash -wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz -tar -xvf libpng-1.6.37.tar.xz -``` - -Now compile libpng, using the libafl_cc compiler wrapper: - -```bash -cd libpng-1.6.37 -./configure -make CC=../target/release/libafl_cc CXX=../target/release/libafl_cxx -j `nproc` -``` - -You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`. - -Now, we have to build the libfuzzer harness and link all together to create our fuzzer binary. - -``` -cd .. -./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm -``` - -Afterwards, the fuzzer will be ready to run. - ## Run -Just run once, the launcher feature should do the rest. \ No newline at end of file +Defaults to `x86_64` architecture +```bash +cargo make run +``` + +```bash +cargo make +``` diff --git a/fuzzers/qemu_launcher/build.rs b/fuzzers/qemu_launcher/build.rs new file mode 100644 index 0000000000..4c89763739 --- /dev/null +++ b/fuzzers/qemu_launcher/build.rs @@ -0,0 +1,48 @@ +use vergen::EmitBuilder; + +#[macro_export] +macro_rules! assert_unique_feature { + () => {}; + ($first:tt $(,$rest:tt)*) => { + $( + #[cfg(not(feature = "clippy"))] // ignore multiple definition for clippy + #[cfg(all(feature = $first, feature = $rest))] + compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together")); + )* + assert_unique_feature!($($rest),*); + } +} + +fn main() { + EmitBuilder::builder() + .all_build() + .all_cargo() + .all_git() + .all_rustc() + .all_sysinfo() + .emit() + .unwrap(); + + assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc"); + + let cpu_target = if cfg!(feature = "x86_64") { + "x86_64".to_string() + } else if cfg!(feature = "arm") { + "arm".to_string() + } else if cfg!(feature = "aarch64") { + "aarch64".to_string() + } else if cfg!(feature = "i386") { + "i386".to_string() + } else if cfg!(feature = "mips") { + "mips".to_string() + } else if cfg!(feature = "ppc") { + "ppc".to_string() + } else { + println!("cargo:warning=No architecture specified defaulting to x86_64..."); + println!("cargo:rustc-cfg=feature=\"x86_64\""); + println!("cargo:rustc-cfg=feature=\"64bit\""); + "x86_64".to_string() + }; + + println!("cargo:rustc-env=CPU_TARGET={cpu_target}"); +} diff --git a/fuzzers/qemu_launcher/harness.cc b/fuzzers/qemu_launcher/harness.cc index f7e3d03a3e..d9e149d5cb 100644 --- a/fuzzers/qemu_launcher/harness.cc +++ b/fuzzers/qemu_launcher/harness.cc @@ -150,7 +150,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { if (width && height > 100000000 / width) { PNG_CLEANUP #ifdef HAS_DUMMY_CRASH - #ifdef __aarch64__ + #if defined(__aarch64__) || defined(__arm__) asm volatile(".word 0xf7f0a000\n"); #else asm("ud2"); diff --git a/fuzzers/qemu_launcher/src/fuzzer.rs b/fuzzers/qemu_launcher/src/fuzzer.rs index a38fad3c61..e422e8727b 100644 --- a/fuzzers/qemu_launcher/src/fuzzer.rs +++ b/fuzzers/qemu_launcher/src/fuzzer.rs @@ -1,8 +1,11 @@ //! A libfuzzer-like fuzzer using qemu for binary-only coverage //! +#[cfg(feature = "i386")] +use core::mem::size_of; use core::{ptr::addr_of_mut, time::Duration}; use std::{env, path::PathBuf, process}; +use clap::{builder::Str, Parser}; use libafl::{ bolts::{ core_affinity::Cores, @@ -14,7 +17,7 @@ use libafl::{ AsSlice, }, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, - events::EventConfig, + events::{EventConfig, LlmpRestartingEventManager}, executors::{ExitKind, TimeoutExecutor}, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, @@ -29,32 +32,101 @@ use libafl::{ Error, }; use libafl_qemu::{ - //asan::QemuAsanHelper, + drcov::QemuDrCovHelper, edges::{edges_map_mut_slice, QemuEdgeCoverageHelper, MAX_EDGES_NUM}, elf::EasyElf, emu::Emulator, - //snapshot::QemuSnapshotHelper, - MmapPerms, - QemuExecutor, - QemuHooks, - Regs, + MmapPerms, QemuExecutor, QemuHooks, QemuInstrumentationFilter, Regs, }; +use rangemap::RangeMap; -pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB +#[cfg(feature = "64bit")] +type GuestReg = u64; + +#[cfg(not(feature = "64bit"))] +type GuestReg = u32; + +#[derive(Default)] +pub struct Version; + +impl From for Str { + fn from(_: Version) -> Str { + let version = [ + ("Architecture:", env!("CPU_TARGET")), + ("Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP")), + ("Describe:", env!("VERGEN_GIT_DESCRIBE")), + ("Commit SHA:", env!("VERGEN_GIT_SHA")), + ("Commit Date:", env!("VERGEN_RUSTC_COMMIT_DATE")), + ("Commit Branch:", env!("VERGEN_GIT_BRANCH")), + ("Rustc Version:", env!("VERGEN_RUSTC_SEMVER")), + ("Rustc Channel:", env!("VERGEN_RUSTC_CHANNEL")), + ("Rustc Host Triple:", env!("VERGEN_RUSTC_HOST_TRIPLE")), + ("Rustc Commit SHA:", env!("VERGEN_RUSTC_COMMIT_HASH")), + ("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")), + ] + .iter() + .map(|(k, v)| format!("{k:25}: {v}\n")) + .collect::(); + + format!("\n{version:}").into() + } +} + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +#[command( + name = format!("qemu-coverage-{}",env!("CPU_TARGET")), + version = Version::default(), + about, + long_about = "Tool for generating DrCov coverage data using QEMU instrumentation" +)] +pub struct FuzzerOptions { + #[arg(long, help = "Coverage file")] + coverage: String, + + #[arg(long, help = "Input directory")] + input: String, + + #[arg(long, help = "Output directory")] + output: String, + + #[arg(long, help = "Timeout in milli-seconds", default_value = "1000", value_parser = FuzzerOptions::parse_timeout)] + timeout: Duration, + + #[arg(long = "port", help = "Broker port", default_value_t = 1337_u16)] + port: u16, + + #[arg(long, help = "Cpu cores to use", default_value = "all", value_parser = Cores::from_cmdline)] + cores: Cores, + + #[clap(short, long, help = "Enable output from the fuzzer clients")] + verbose: bool, + + #[arg(last = true, help = "Arguments passed to the target")] + args: Vec, +} + +impl FuzzerOptions { + fn parse_timeout(src: &str) -> Result { + Ok(Duration::from_millis(src.parse()?)) + } +} pub fn fuzz() { - // Hardcoded parameters - let timeout = Duration::from_secs(1); - let broker_port = 1337; - let cores = Cores::from_cmdline("0-11").unwrap(); - let corpus_dirs = [PathBuf::from("./corpus")]; - let objective_dir = PathBuf::from("./crashes"); + let mut options = FuzzerOptions::parse(); + + let output_dir = PathBuf::from(options.output); + let corpus_dirs = [PathBuf::from(options.input)]; + + let program = env::args().next().unwrap(); + println!("Program: {program:}"); + + options.args.insert(0, program); + println!("ARGS: {:#?}", options.args); - // Initialize QEMU env::remove_var("LD_LIBRARY_PATH"); - let args: Vec = env::args().collect(); let env: Vec<(String, String)> = env::vars().collect(); - let emu = Emulator::new(&args, &env).unwrap(); + let emu = Emulator::new(&options.args, &env).unwrap(); let mut elf_buffer = Vec::new(); let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer).unwrap(); @@ -64,45 +136,143 @@ pub fn fuzz() { .expect("Symbol LLVMFuzzerTestOneInput not found"); println!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); - emu.set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + emu.set_breakpoint(test_one_input_ptr); unsafe { emu.run() }; - println!("Break at {:#x}", emu.read_reg::<_, u64>(Regs::Rip).unwrap()); + for m in emu.mappings() { + println!( + "Mapping: 0x{:016x}-0x{:016x}, {}", + m.start(), + m.end(), + m.path().unwrap_or("") + ); + } - // Get the return address - let stack_ptr: u64 = emu.read_reg(Regs::Rsp).unwrap(); - let mut ret_addr = [0; 8]; - unsafe { emu.read_mem(stack_ptr, &mut ret_addr) }; - let ret_addr = u64::from_le_bytes(ret_addr); + let read_reg = |emu: &Emulator, reg: Regs| -> GuestReg { + let val: GuestReg = emu.read_reg(reg).unwrap(); + + #[cfg(feature = "be")] + return GuestReg::from_be(val); + + #[cfg(not(feature = "be"))] + return GuestReg::from_le(val); + }; + + let write_reg = |emu: &Emulator, reg: Regs, val: GuestReg| { + #[cfg(feature = "be")] + let val = GuestReg::to_be(val); + + #[cfg(not(feature = "be"))] + let val = GuestReg::to_le(val); + + emu.write_reg(reg, val).unwrap(); + }; + + println!("Break at {:#x}", read_reg(&emu, Regs::Pc)); + + #[cfg(feature = "arm")] + let ret_addr: u32 = read_reg(&emu, Regs::Lr); + + #[cfg(feature = "aarch64")] + let ret_addr: u64 = read_reg(&emu, Regs::Lr); + + #[cfg(feature = "x86_64")] + let stack_ptr: u64 = read_reg(&emu, Regs::Rsp); + + #[cfg(feature = "x86_64")] + let ret_addr: u64 = { + let mut ret_addr = [0; 8]; + unsafe { emu.read_mem(stack_ptr, &mut ret_addr) }; + u64::from_le_bytes(ret_addr) + }; + + #[cfg(feature = "i386")] + let stack_ptr: u32 = read_reg(&emu, Regs::Esp); + + #[cfg(feature = "i386")] + let ret_addr: u32 = { + let mut ret_addr = [0; 4]; + unsafe { emu.read_mem(stack_ptr, &mut ret_addr) }; + u32::from_le_bytes(ret_addr) + }; + + #[cfg(feature = "mips")] + let ret_addr: u32 = read_reg(&emu, Regs::Ra); + + #[cfg(feature = "ppc")] + let ret_addr: u32 = read_reg(&emu, Regs::Lr); - println!("Stack pointer = {stack_ptr:#x}"); println!("Return address = {ret_addr:#x}"); - emu.remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput - emu.set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr + emu.remove_breakpoint(test_one_input_ptr); + emu.set_breakpoint(ret_addr); - let input_addr = emu - .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) - .unwrap(); + let input_addr = emu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap(); println!("Placing input at {input_addr:#x}"); - // 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 mut len = buf.len(); - if len > MAX_INPUT_SIZE { - buf = &buf[0..MAX_INPUT_SIZE]; - len = MAX_INPUT_SIZE; - } + let buf = target + .as_slice() + .chunks(4096) + .next() + .expect("Failed to get chunk"); + let len = buf.len() as GuestReg; unsafe { emu.write_mem(input_addr, buf); - emu.write_reg(Regs::Rdi, input_addr).unwrap(); - emu.write_reg(Regs::Rsi, len).unwrap(); - emu.write_reg(Regs::Rip, test_one_input_ptr).unwrap(); - emu.write_reg(Regs::Rsp, stack_ptr).unwrap(); + #[cfg(feature = "arm")] + { + write_reg(&emu, Regs::R0, input_addr); + write_reg(&emu, Regs::R1, len); + write_reg(&emu, Regs::Pc, test_one_input_ptr); + write_reg(&emu, Regs::Lr, ret_addr); + } + + #[cfg(feature = "aarch64")] + { + write_reg(&emu, Regs::X0, input_addr); + write_reg(&emu, Regs::X1, len); + write_reg(&emu, Regs::Pc, test_one_input_ptr); + write_reg(&emu, Regs::Lr, ret_addr); + } + + #[cfg(feature = "x86_64")] + { + write_reg(&emu, Regs::Rdi, input_addr); + write_reg(&emu, Regs::Rsi, len); + write_reg(&emu, Regs::Rip, test_one_input_ptr); + write_reg(&emu, Regs::Rsp, stack_ptr); + } + + #[cfg(feature = "i386")] + { + let input_addr_bytes = input_addr.to_le_bytes(); + emu.write_mem(stack_ptr + (size_of::() as u32), &input_addr_bytes); + + let len_bytes = len.to_le_bytes(); + emu.write_mem(stack_ptr + ((2 * size_of::()) as u32), &len_bytes); + + write_reg(&emu, Regs::Eip, test_one_input_ptr); + write_reg(&emu, Regs::Esp, stack_ptr); + } + + #[cfg(feature = "mips")] + { + write_reg(&emu, Regs::A0, input_addr); + write_reg(&emu, Regs::A1, len); + write_reg(&emu, Regs::Pc, test_one_input_ptr); + write_reg(&emu, Regs::Ra, ret_addr); + } + + #[cfg(feature = "ppc")] + { + write_reg(&emu, Regs::R3, input_addr); + write_reg(&emu, Regs::R4, len); + write_reg(&emu, Regs::Pc, test_one_input_ptr); + write_reg(&emu, Regs::Lr, ret_addr); + } emu.run(); } @@ -110,7 +280,7 @@ pub fn fuzz() { ExitKind::Ok }; - let mut run_client = |state: Option<_>, mut mgr, _core_id| { + let mut run_client = |state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _>, _core_id| { // Create an observation channel using the coverage map let edges_observer = unsafe { HitcountsMapObserver::new(VariableMapObserver::from_mut_slice( @@ -144,7 +314,7 @@ pub fn fuzz() { 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(), + OnDiskCorpus::new(output_dir.clone()).unwrap(), // States of the feedbacks. // The feedbacks can report the data that should persist in the State. &mut feedback, @@ -160,7 +330,34 @@ pub fn fuzz() { // 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())); + let rangemap = emu + .mappings() + .filter_map(|m| { + m.path() + .map(|p| ((m.start() as usize)..(m.end() as usize), p.to_string())) + .filter(|(_, p)| !p.is_empty()) + }) + .enumerate() + .fold( + RangeMap::::new(), + |mut rm, (i, (r, p))| { + rm.insert(r, (i as u16, p)); + rm + }, + ); + + let mut hooks = QemuHooks::new( + &emu, + tuple_list!( + QemuEdgeCoverageHelper::default(), + QemuDrCovHelper::new( + QemuInstrumentationFilter::None, + rangemap, + PathBuf::from(&options.coverage), + false, + ) + ), + ); // Create a QEMU in-process executor let executor = QemuExecutor::new( @@ -174,7 +371,7 @@ pub fn fuzz() { .expect("Failed to create QemuExecutor"); // Wrap the executor to keep track of the timeout - let mut executor = TimeoutExecutor::new(executor, timeout); + let mut executor = TimeoutExecutor::new(executor, options.timeout); if state.must_load_initial_inputs() { state @@ -203,11 +400,11 @@ pub fn fuzz() { // Build and run a Launcher match Launcher::builder() .shmem_provider(shmem_provider) - .broker_port(broker_port) + .broker_port(options.port) .configuration(EventConfig::from_build_id()) .monitor(monitor) .run_client(&mut run_client) - .cores(&cores) + .cores(&options.cores) .stdout_file(Some("/dev/null")) .build() .launch()