From 747a636f4ff5d0ad975b15080348da7b0adc5ecd Mon Sep 17 00:00:00 2001 From: WorksButNotTested <62701594+WorksButNotTested@users.noreply.github.com> Date: Tue, 6 Jun 2023 10:50:38 +0100 Subject: [PATCH] =?UTF-8?q?Add=20sample=20fuzzer=20which=20collects=20DrCo?= =?UTF-8?q?v=20coverage=20for=20various=20architect=E2=80=A6=20(#1300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add sample fuzzer which collects DrCov coverage for various architectures using QEMU instrumentation * Fix clippy * Rename NullCorpus to NopCorpus * Added support for verbose output * Attempt to fix clippy again * Fix remaining defaults to use x86_64 when no arch specified and be more robust handling partial builds * Make build even more robust against partial re-builds * Added missing dependencies to workflow, updated README * Add missing dependencies for i386 * Another dependency * More dependencies * Disable tests on OSX * Add tmate * Add missing dependencies and symlink header directory * Tidy up after test so we don't hog all the disk space --------- Co-authored-by: Your Name --- .github/workflows/build_and_test.yml | 6 +- fuzzers/qemu_coverage/.gitignore | 7 + fuzzers/qemu_coverage/Cargo.toml | 32 ++ fuzzers/qemu_coverage/Makefile.toml | 318 +++++++++++++++ fuzzers/qemu_coverage/README.md | 39 ++ fuzzers/qemu_coverage/build.rs | 48 +++ fuzzers/qemu_coverage/corpus/not_kitty.png | Bin 0 -> 218 bytes .../qemu_coverage/corpus/not_kitty_alpha.png | Bin 0 -> 376 bytes .../qemu_coverage/corpus/not_kitty_gamma.png | Bin 0 -> 228 bytes .../qemu_coverage/corpus/not_kitty_icc.png | Bin 0 -> 427 bytes fuzzers/qemu_coverage/harness.cc | 193 +++++++++ fuzzers/qemu_coverage/src/fuzzer.rs | 386 ++++++++++++++++++ fuzzers/qemu_coverage/src/main.rs | 13 + libafl/src/corpus/mod.rs | 2 + libafl/src/corpus/nop.rs | 139 +++++++ libafl_qemu/Cargo.toml | 1 + libafl_qemu/build_linux.rs | 2 + libafl_qemu/libafl_qemu_sys/Cargo.toml | 1 + libafl_qemu/libafl_qemu_sys/build_linux.rs | 4 +- libafl_qemu/src/calls.rs | 7 + libafl_qemu/src/lib.rs | 5 + libafl_qemu/src/mips.rs | 2 + libafl_qemu/src/ppc.rs | 103 +++++ libafl_qemu/src/snapshot.rs | 14 +- 24 files changed, 1318 insertions(+), 4 deletions(-) create mode 100644 fuzzers/qemu_coverage/.gitignore create mode 100644 fuzzers/qemu_coverage/Cargo.toml create mode 100644 fuzzers/qemu_coverage/Makefile.toml create mode 100644 fuzzers/qemu_coverage/README.md create mode 100644 fuzzers/qemu_coverage/build.rs create mode 100644 fuzzers/qemu_coverage/corpus/not_kitty.png create mode 100644 fuzzers/qemu_coverage/corpus/not_kitty_alpha.png create mode 100644 fuzzers/qemu_coverage/corpus/not_kitty_gamma.png create mode 100644 fuzzers/qemu_coverage/corpus/not_kitty_icc.png create mode 100644 fuzzers/qemu_coverage/harness.cc create mode 100644 fuzzers/qemu_coverage/src/fuzzer.rs create mode 100644 fuzzers/qemu_coverage/src/main.rs create mode 100644 libafl/src/corpus/nop.rs create mode 100644 libafl_qemu/src/ppc.rs diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bcc9e58084..d0ab749450 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -193,7 +193,7 @@ jobs: - uses: lyricwulf/abc@v1 with: # todo: remove afl++-clang when nyx support samcov_pcguard - linux: llvm llvm-dev clang nasm ninja-build gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libgtk-3-dev afl++-clang pax-utils libz3-dev + linux: llvm llvm-dev clang nasm ninja-build gcc-arm-linux-gnueabi g++-arm-linux-gnueabi gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-mipsel-linux-gnu g++-mipsel-linux-gnu gcc-powerpc-linux-gnu g++-powerpc-linux-gnu libc6-dev-i386-cross libc6-dev libc6-dev-i386 lib32gcc-11-dev lib32stdc++-11-dev libgtk-3-dev afl++-clang pax-utils libz3-dev # update bash for macos to support `declare -A` command` macos: llvm libpng nasm coreutils z3 bash wget - name: pip install @@ -218,6 +218,10 @@ jobs: submodules: true # recursively checkout submodules fetch-depth: 0 # to diff with origin/main - uses: Swatinem/rust-cache@v2 + - name: Symlink Headers + if: runner.os == 'Linux' + # We can't install gcc-multilib which would usually do this for us due to collisions with other packages + run: sudo ln -s /usr/include/asm-generic /usr/include/asm - name: Build and run example fuzzers (Linux) if: runner.os == 'Linux' run: RUN_ON_CI=1 ./scripts/test_all_fuzzers.sh diff --git a/fuzzers/qemu_coverage/.gitignore b/fuzzers/qemu_coverage/.gitignore new file mode 100644 index 0000000000..11fb5666bc --- /dev/null +++ b/fuzzers/qemu_coverage/.gitignore @@ -0,0 +1,7 @@ +libpng-* +libpng_harness +libpng_harness_crashing +zlib-* +crashes +target +drcov.log diff --git a/fuzzers/qemu_coverage/Cargo.toml b/fuzzers/qemu_coverage/Cargo.toml new file mode 100644 index 0000000000..39e55b3fa1 --- /dev/null +++ b/fuzzers/qemu_coverage/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "qemu-coverage" +version = "0.10.1" +authors = ["Andrea Fioraldi ", "Dominik Maier ", "WorksButNotTested"] +edition = "2021" + +[profile.release] +#lto = true +#codegen-units = 1 +#opt-level = 3 +debug = true + +[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"] + +[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 = ["usermode"] } +rangemap = { version = "1.0.3" } diff --git a/fuzzers/qemu_coverage/Makefile.toml b/fuzzers/qemu_coverage/Makefile.toml new file mode 100644 index 0000000000..f94899eeda --- /dev/null +++ b/fuzzers/qemu_coverage/Makefile.toml @@ -0,0 +1,318 @@ +[env] +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/mac" +''' + + +[tasks.target_dir] +condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" ] } +script_runner="@shell" +script=''' +mkdir ${CARGO_MAKE_CRATE_TARGET_DIRECTORY} +''' + +[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.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-coverage ${TARGET_DIR}/release/qemu-coverage-${CARGO_MAKE_PROFILE} +''' + +[tasks.harness] +linux_alias = "harness_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.harness_unix] +script_runner="@shell" +script=''' +${CROSS_CXX} \ + ./harness.cc \ + $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" ] + +[tasks.run] +linux_alias = "run_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.run_unix] +command = "${TARGET_DIR}/release/qemu-coverage-${CARGO_MAKE_PROFILE}" +args = [ + "--coverage", "${TARGET_DIR}/drcov.log", + "--input", "./corpus", + "--", + "${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}", +] +dependencies = [ "harness", "fuzzer" ] + +[tasks.test] +linux_alias = "test_unix" +mac_alias = "unsupported" +windows_alias = "unsupported" + +[tasks.test_unix] +dependencies = [ "all" ] +# Tidy up after we've run our tests so we don't hog all the disk space +command = "cargo" +args = [ + "make", + "clean", +] + +[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 -rf ${CARGO_MAKE_CRATE_TARGET_DIRECTORY} +cargo clean +''' + +[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_coverage/README.md b/fuzzers/qemu_coverage/README.md new file mode 100644 index 0000000000..fe22a403ce --- /dev/null +++ b/fuzzers/qemu_coverage/README.md @@ -0,0 +1,39 @@ +# qemu-coverage + +This folder contains an example fuzzer which runs each entry in the input corpus and collects +the cumuative coverage data in drcov format. This fuzzer also distributes the test cases in +the input corupus evenly across the selected cores. + +The following architectures are supported: +* arm +* aarch64 +* i386 +* x86_64 +* mips +* ppc + +## Prerequisites +```bash +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 +``` + +## Run + +Defaults to `x86_64` architecture +```bash +cargo make run +``` + +```bash +cargo make +``` diff --git a/fuzzers/qemu_coverage/build.rs b/fuzzers/qemu_coverage/build.rs new file mode 100644 index 0000000000..4c89763739 --- /dev/null +++ b/fuzzers/qemu_coverage/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_coverage/corpus/not_kitty.png b/fuzzers/qemu_coverage/corpus/not_kitty.png new file mode 100644 index 0000000000000000000000000000000000000000..eff7c1707b936a8f8df725814f604d454b78b5c3 GIT binary patch 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} literal 0 HcmV?d00001 diff --git a/fuzzers/qemu_coverage/corpus/not_kitty_gamma.png b/fuzzers/qemu_coverage/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000000000000000000000000000000000..939d9d29a9b9f95bac5e9a72854361ee85469921 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/fuzzers/qemu_coverage/corpus/not_kitty_icc.png b/fuzzers/qemu_coverage/corpus/not_kitty_icc.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c7804d99829cc6307c1c6ae9915cf42d555414 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/fuzzers/qemu_coverage/harness.cc b/fuzzers/qemu_coverage/harness.cc new file mode 100644 index 0000000000..d9e149d5cb --- /dev/null +++ b/fuzzers/qemu_coverage/harness.cc @@ -0,0 +1,193 @@ +// 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_coverage/src/fuzzer.rs b/fuzzers/qemu_coverage/src/fuzzer.rs new file mode 100644 index 0000000000..c69192960b --- /dev/null +++ b/fuzzers/qemu_coverage/src/fuzzer.rs @@ -0,0 +1,386 @@ +//! A libfuzzer-like fuzzer using qemu for binary-only coverage +//! +#[cfg(feature = "i386")] +use core::mem::size_of; +use core::time::Duration; +use std::{env, fs::DirEntry, io, path::PathBuf, process}; + +use clap::{builder::Str, Parser}; +use libafl::{ + bolts::{ + core_affinity::Cores, + current_nanos, + launcher::Launcher, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, + AsSlice, + }, + corpus::{Corpus, NopCorpus}, + events::{EventConfig, EventRestarter}, + executors::{ExitKind, TimeoutExecutor}, + fuzzer::StdFuzzer, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + prelude::LlmpRestartingEventManager, + schedulers::QueueScheduler, + state::{HasCorpus, StdState}, + Error, +}; +use libafl_qemu::{ + drcov::QemuDrCovHelper, elf::EasyElf, emu::Emulator, MmapPerms, QemuExecutor, QemuHooks, + QemuInstrumentationFilter, Regs, +}; +use rangemap::RangeMap; + +#[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 = "Timeout in seconds", default_value_t = 1_u64)] + timeout: u64, + + #[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, +} + +pub fn fuzz() { + let mut options = FuzzerOptions::parse(); + + let corpus_dir = PathBuf::from(options.input); + + let corpus_files = corpus_dir + .read_dir() + .expect("Failed to read corpus dir") + .collect::, io::Error>>() + .expect("Failed to read dir entry"); + + let num_files = corpus_files.len(); + let num_cores = options.cores.ids.len(); + let files_per_core = (num_files as f64 / num_cores as f64).ceil() as usize; + + let program = env::args().next().unwrap(); + println!("Program: {program:}"); + + options.args.insert(0, program); + println!("ARGS: {:#?}", options.args); + + env::remove_var("LD_LIBRARY_PATH"); + let env: Vec<(String, String)> = env::vars().collect(); + 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(); + + 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); + unsafe { emu.run() }; + + for m in emu.mappings() { + println!( + "Mapping: 0x{:016x}-0x{:016x}, {}", + m.start(), + m.end(), + m.path().unwrap_or("") + ); + } + + 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!("Return address = {ret_addr:#x}"); + + emu.remove_breakpoint(test_one_input_ptr); + emu.set_breakpoint(ret_addr); + + let input_addr = emu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap(); + println!("Placing input at {input_addr:#x}"); + + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + 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); + + #[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(); + } + + ExitKind::Ok + }; + + let mut run_client = |state: Option<_>, mut mgr: LlmpRestartingEventManager<_, _>, core_id| { + let core_idx = options + .cores + .position(core_id) + .expect("Failed to get core index"); + let files = corpus_files + .iter() + .skip(files_per_core * core_idx) + .take(files_per_core) + .map(|x| x.path()) + .collect::>(); + + if files.is_empty() { + mgr.send_exiting()?; + Err(Error::ShuttingDown)? + } + + #[allow(clippy::let_unit_value)] + let mut feedback = (); + + #[allow(clippy::let_unit_value)] + let mut objective = (); + + let mut state = state.unwrap_or_else(|| { + StdState::new( + StdRand::with_seed(current_nanos()), + NopCorpus::new(), + NopCorpus::new(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + let scheduler = QueueScheduler::new(); + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + 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!(QemuDrCovHelper::new( + QemuInstrumentationFilter::None, + rangemap, + PathBuf::from(&options.coverage), + false, + )), + ); + + let executor = QemuExecutor::new( + &mut hooks, + &mut harness, + (), + &mut fuzzer, + &mut state, + &mut mgr, + ) + .expect("Failed to create QemuExecutor"); + + let mut executor = TimeoutExecutor::new(executor, Duration::from_secs(options.timeout)); + + if state.must_load_initial_inputs() { + state + .load_initial_inputs_by_filenames(&mut fuzzer, &mut executor, &mut mgr, &files) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &corpus_dir); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + println!("Processed {} inputs from disk.", files.len()); + + mgr.send_exiting()?; + Err(Error::ShuttingDown)? + }; + + match Launcher::builder() + .shmem_provider(StdShMemProvider::new().expect("Failed to init shared memory")) + .broker_port(options.port) + .configuration(EventConfig::from_build_id()) + .monitor(MultiMonitor::new(|s| println!("{s}"))) + .run_client(&mut run_client) + .cores(&options.cores) + .stdout_file(if options.verbose { + None + } else { + Some("/dev/null") + }) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => println!("Run finished successfully."), + Err(err) => panic!("Failed to run launcher: {err:?}"), + } +} diff --git a/fuzzers/qemu_coverage/src/main.rs b/fuzzers/qemu_coverage/src/main.rs new file mode 100644 index 0000000000..bc1e80f767 --- /dev/null +++ b/fuzzers/qemu_coverage/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/src/corpus/mod.rs b/libafl/src/corpus/mod.rs index 0f76b259c8..7203795251 100644 --- a/libafl/src/corpus/mod.rs +++ b/libafl/src/corpus/mod.rs @@ -25,8 +25,10 @@ pub use cached::CachedOnDiskCorpus; pub mod minimizer; use core::{cell::RefCell, fmt}; +pub mod nop; #[cfg(feature = "cmin")] pub use minimizer::*; +pub use nop::NopCorpus; use serde::{Deserialize, Serialize}; use crate::{inputs::UsesInput, Error}; diff --git a/libafl/src/corpus/nop.rs b/libafl/src/corpus/nop.rs new file mode 100644 index 0000000000..07e9b3d1a4 --- /dev/null +++ b/libafl/src/corpus/nop.rs @@ -0,0 +1,139 @@ +//! The null corpus does not store any [`Testcase`]s. +use core::{cell::RefCell, marker::PhantomData}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + corpus::{Corpus, CorpusId, Testcase}, + inputs::{Input, UsesInput}, + Error, ErrorBacktrace, +}; + +/// A corpus which does not store any [`Testcase`]s. +#[derive(Default, Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "I: serde::de::DeserializeOwned")] +pub struct NopCorpus { + empty: Option, + phantom: PhantomData, +} + +impl UsesInput for NopCorpus +where + I: Input, +{ + type Input = I; +} + +impl Corpus for NopCorpus +where + I: Input, +{ + /// Returns the number of elements + #[inline] + fn count(&self) -> usize { + 0 + } + + /// Add an entry to the corpus and return its index + #[inline] + fn add(&mut self, _testcase: Testcase) -> Result { + Err(Error::Unsupported( + "Unsupported".into(), + ErrorBacktrace::new(), + )) + } + + /// Replaces the testcase at the given idx + #[inline] + fn replace(&mut self, _idx: CorpusId, _testcase: Testcase) -> Result, Error> { + Err(Error::Unsupported( + "Unsupported".into(), + ErrorBacktrace::new(), + )) + } + + /// Removes an entry from the corpus, returning it if it was present. + #[inline] + fn remove(&mut self, _idx: CorpusId) -> Result, Error> { + Err(Error::Unsupported( + "Unsupported".into(), + ErrorBacktrace::new(), + )) + } + + /// Get by id + #[inline] + fn get(&self, _idx: CorpusId) -> Result<&RefCell>, Error> { + Err(Error::Unsupported( + "Unsupported".into(), + ErrorBacktrace::new(), + )) + } + + /// Current testcase scheduled + #[inline] + fn current(&self) -> &Option { + &self.empty + } + + /// Current testcase scheduled (mutable) + #[inline] + fn current_mut(&mut self) -> &mut Option { + &mut self.empty + } + + #[inline] + fn next(&self, _idx: CorpusId) -> Option { + None + } + + #[inline] + fn prev(&self, _idx: CorpusId) -> Option { + None + } + + #[inline] + fn first(&self) -> Option { + None + } + + #[inline] + fn last(&self) -> Option { + None + } + + #[inline] + fn nth(&self, _nth: usize) -> CorpusId { + CorpusId::from(0_usize) + } + + #[inline] + fn load_input_into(&self, _testcase: &mut Testcase) -> Result<(), Error> { + Err(Error::Unsupported( + "Unsupported".into(), + ErrorBacktrace::new(), + )) + } + + #[inline] + fn store_input_from(&self, _testcase: &Testcase) -> Result<(), Error> { + Err(Error::Unsupported( + "Unsupported".into(), + ErrorBacktrace::new(), + )) + } +} + +impl NopCorpus +where + I: Input, +{ + /// Creates a new [`NopCorpus`]. + #[must_use] + pub fn new() -> Self { + Self { + empty: None, + phantom: PhantomData {}, + } + } +} diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 4d09317e0b..d8574a0389 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -22,6 +22,7 @@ i386 = ["libafl_qemu_sys/i386"] # build qemu for i386 arm = ["libafl_qemu_sys/arm"] # build qemu for arm aarch64 = ["libafl_qemu_sys/aarch64"] # build qemu for aarch64 mips = ["libafl_qemu_sys/mips"] # build qemu for mips (el, use with the 'be' feature of mips be) +ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc be = ["libafl_qemu_sys/be"] diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index 485e3bd2df..625f9ebae9 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -28,6 +28,8 @@ pub fn build() { "i386".to_string() } else if cfg!(feature = "mips") { "mips".to_string() + } else if cfg!(feature = "ppc") { + "ppc".to_string() } else { env::var("CPU_TARGET").unwrap_or_else(|_| { "x86_64".to_string() diff --git a/libafl_qemu/libafl_qemu_sys/Cargo.toml b/libafl_qemu/libafl_qemu_sys/Cargo.toml index 85bf77a6ff..a8dc87e1f1 100644 --- a/libafl_qemu/libafl_qemu_sys/Cargo.toml +++ b/libafl_qemu/libafl_qemu_sys/Cargo.toml @@ -18,6 +18,7 @@ i386 = [] # build qemu for i386 arm = [] # build qemu for arm aarch64 = [] # build qemu for aarch64 mips = [] # build qemu for mips (el, use with the 'be' feature of mips be) +ppc = [] # build qemu for powerpc be = [] diff --git a/libafl_qemu/libafl_qemu_sys/build_linux.rs b/libafl_qemu/libafl_qemu_sys/build_linux.rs index 0f470bd8a6..871c9a2b97 100644 --- a/libafl_qemu/libafl_qemu_sys/build_linux.rs +++ b/libafl_qemu/libafl_qemu_sys/build_linux.rs @@ -38,7 +38,7 @@ pub fn build() { // Make sure we have at most one architecutre feature set // Else, we default to `x86_64` - having a default makes CI easier :) - assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips"); + assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips", "ppc"); // Make sure that we don't have BE set for any architecture other than arm and mips // Sure aarch64 may support BE, but its not in common usage and we don't @@ -55,6 +55,8 @@ pub fn build() { "i386".to_string() } else if cfg!(feature = "mips") { "mips".to_string() + } else if cfg!(feature = "ppc") { + "ppc".to_string() } else { env::var("CPU_TARGET").unwrap_or_else(|_| { println!( diff --git a/libafl_qemu/src/calls.rs b/libafl_qemu/src/calls.rs index ac5010bd96..1c68186bd8 100644 --- a/libafl_qemu/src/calls.rs +++ b/libafl_qemu/src/calls.rs @@ -274,6 +274,13 @@ where ret_addr }; + #[cfg(cpu_target = "ppc")] + let ret_addr = { + let emu = hooks.emulator(); + let ret_addr: GuestAddr = emu.read_reg(Regs::Lr).unwrap(); + ret_addr + }; + // log::info!("RET @ 0x{:#x}", ret_addr); let mut collectors = if let Some(h) = hooks.helpers_mut().match_first_type_mut::() { diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index df89d5b6c6..292c745f66 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -50,6 +50,11 @@ pub mod mips; #[cfg(cpu_target = "mips")] pub use mips::*; +#[cfg(cpu_target = "ppc")] +pub mod ppc; +#[cfg(cpu_target = "ppc")] +pub use ppc::*; + pub mod elf; pub mod helper; diff --git a/libafl_qemu/src/mips.rs b/libafl_qemu/src/mips.rs index 27a8a8e7ba..72469bc5e5 100644 --- a/libafl_qemu/src/mips.rs +++ b/libafl_qemu/src/mips.rs @@ -40,6 +40,8 @@ pub enum Regs { Sp = 29, Fp = 30, Ra = 31, + + Pc = 37, } /// alias registers diff --git a/libafl_qemu/src/ppc.rs b/libafl_qemu/src/ppc.rs new file mode 100644 index 0000000000..b6fb27cea1 --- /dev/null +++ b/libafl_qemu/src/ppc.rs @@ -0,0 +1,103 @@ +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "python")] +use pyo3::prelude::*; +pub use strum_macros::EnumIter; +pub use syscall_numbers::powerpc::*; + +/// Registers for the MIPS instruction set. +#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] +#[repr(i32)] +pub enum Regs { + R0 = 0, + R1 = 1, + R2 = 2, + R3 = 3, + R4 = 4, + R5 = 5, + R6 = 6, + R7 = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15, + R16 = 16, + R17 = 17, + R18 = 18, + R19 = 19, + R20 = 20, + R21 = 21, + R22 = 22, + R23 = 23, + R24 = 24, + R25 = 25, + R26 = 26, + R27 = 27, + R28 = 28, + R29 = 29, + R30 = 30, + R31 = 31, + + F0 = 32, + F1 = 33, + F2 = 34, + F3 = 35, + F4 = 36, + F5 = 37, + F6 = 38, + F7 = 39, + F8 = 40, + F9 = 41, + F10 = 42, + F11 = 43, + F12 = 44, + F13 = 45, + F14 = 46, + F15 = 47, + F16 = 48, + F17 = 49, + F18 = 50, + F19 = 51, + F20 = 52, + F21 = 53, + F22 = 54, + F23 = 55, + F24 = 56, + F25 = 57, + F26 = 58, + F27 = 59, + F28 = 60, + F29 = 61, + F30 = 62, + F31 = 63, + + Nip = 64, + Msr = 65, + Cr = 66, + Lr = 67, + Ctr = 68, + Xer = 69, + Fpscr = 70, +} + +/// alias registers +#[allow(non_upper_case_globals)] +impl Regs { + pub const Pc: Regs = Regs::Nip; +} + +#[cfg(feature = "python")] +impl IntoPy for Regs { + fn into_py(self, py: Python) -> PyObject { + let n: i32 = self.into(); + n.into_py(py) + } +} + +/// Return an MIPS ArchCapstoneBuilder +pub fn capstone() -> capstone::arch::ppc::ArchCapstoneBuilder { + capstone::Capstone::new().ppc() +} diff --git a/libafl_qemu/src/snapshot.rs b/libafl_qemu/src/snapshot.rs index ef4d516f05..530d1a10d7 100644 --- a/libafl_qemu/src/snapshot.rs +++ b/libafl_qemu/src/snapshot.rs @@ -14,7 +14,12 @@ use crate::SYS_fstatat64; use crate::SYS_mmap; #[cfg(any(cpu_target = "arm", cpu_target = "mips"))] use crate::SYS_mmap2; -#[cfg(not(any(cpu_target = "arm", cpu_target = "mips", cpu_target = "i386")))] +#[cfg(not(any( + cpu_target = "arm", + cpu_target = "mips", + cpu_target = "i386", + cpu_target = "ppc" +)))] use crate::SYS_newfstatat; use crate::{ emu::{Emulator, MmapPerms, SyscallHookResult}, @@ -638,7 +643,12 @@ where let h = hooks.match_helper_mut::().unwrap(); h.access(a0 as GuestAddr, a3 as usize); } - #[cfg(not(any(cpu_target = "arm", cpu_target = "i386", cpu_target = "mips")))] + #[cfg(not(any( + cpu_target = "arm", + cpu_target = "i386", + cpu_target = "mips", + cpu_target = "ppc" + )))] SYS_newfstatat => { if a2 != 0 { let h = hooks.match_helper_mut::().unwrap();