From d3a4b726d82ce3f8ff21bdd84baf3faff60c8094 Mon Sep 17 00:00:00 2001 From: WorksButNotTested <62701594+WorksButNotTested@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:59:41 +0100 Subject: [PATCH] Added qemu_cmin (#1572) Co-authored-by: Your Name --- fuzzers/qemu_cmin/.gitignore | 8 + fuzzers/qemu_cmin/Cargo.toml | 33 ++ fuzzers/qemu_cmin/Makefile.toml | 323 +++++++++++++++++++ fuzzers/qemu_cmin/README.md | 38 +++ fuzzers/qemu_cmin/build.rs | 47 +++ fuzzers/qemu_cmin/corpus/not_kitty.png | Bin 0 -> 218 bytes fuzzers/qemu_cmin/corpus/not_kitty_alpha.png | Bin 0 -> 376 bytes fuzzers/qemu_cmin/corpus/not_kitty_gamma.png | Bin 0 -> 228 bytes fuzzers/qemu_cmin/corpus/not_kitty_icc.png | Bin 0 -> 427 bytes fuzzers/qemu_cmin/harness.cc | 193 +++++++++++ fuzzers/qemu_cmin/src/fuzzer.rs | 251 ++++++++++++++ fuzzers/qemu_cmin/src/main.rs | 13 + 12 files changed, 906 insertions(+) create mode 100644 fuzzers/qemu_cmin/.gitignore create mode 100644 fuzzers/qemu_cmin/Cargo.toml create mode 100644 fuzzers/qemu_cmin/Makefile.toml create mode 100644 fuzzers/qemu_cmin/README.md create mode 100644 fuzzers/qemu_cmin/build.rs create mode 100644 fuzzers/qemu_cmin/corpus/not_kitty.png create mode 100644 fuzzers/qemu_cmin/corpus/not_kitty_alpha.png create mode 100644 fuzzers/qemu_cmin/corpus/not_kitty_gamma.png create mode 100644 fuzzers/qemu_cmin/corpus/not_kitty_icc.png create mode 100644 fuzzers/qemu_cmin/harness.cc create mode 100644 fuzzers/qemu_cmin/src/fuzzer.rs create mode 100644 fuzzers/qemu_cmin/src/main.rs diff --git a/fuzzers/qemu_cmin/.gitignore b/fuzzers/qemu_cmin/.gitignore new file mode 100644 index 0000000000..0be9ee766c --- /dev/null +++ b/fuzzers/qemu_cmin/.gitignore @@ -0,0 +1,8 @@ +libpng-* +libpng_harness +libpng_harness_crashing +zlib-* +crashes +target +output +corpus/ diff --git a/fuzzers/qemu_cmin/Cargo.toml b/fuzzers/qemu_cmin/Cargo.toml new file mode 100644 index 0000000000..4aaddf82ad --- /dev/null +++ b/fuzzers/qemu_cmin/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "qemu_cmin" +version = "0.11.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 = ["libafl_qemu/be"] +arm = ["libafl_qemu/arm"] +x86_64 = ["libafl_qemu/x86_64"] +i386 = ["libafl_qemu/i386"] +aarch64 = ["libafl_qemu/aarch64"] +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_bolts = { path = "../../libafl_bolts/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["usermode"] } +log = {version = "0.4.20" } +rangemap = { version = "1.3" } diff --git a/fuzzers/qemu_cmin/Makefile.toml b/fuzzers/qemu_cmin/Makefile.toml new file mode 100644 index 0000000000..54613e713a --- /dev/null +++ b/fuzzers/qemu_cmin/Makefile.toml @@ -0,0 +1,323 @@ +[env] +PROFILE = { value = "release", condition = {env_not_set = ["PROFILE"]} } +PROFILE_DIR = {value = "release", condition = {env_not_set = ["PROFILE_DIR"] }} +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", + "--profile", + "${PROFILE}", + "--features", "${FEATURE}", + "--target-dir", "${TARGET_DIR}" +] + +[tasks.fuzzer] +dependencies = ["build"] +script_runner="@shell" +script=''' +rm -f ${TARGET_DIR}/${PROFILE_DIR}/qemu_cmin-${CARGO_MAKE_PROFILE} +mv ${TARGET_DIR}/${PROFILE_DIR}/qemu_cmin ${TARGET_DIR}/${PROFILE_DIR}/qemu_cmin-${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}/${PROFILE_DIR}/qemu_cmin-${CARGO_MAKE_PROFILE}" +args = [ + "--output", "./output", + "--input", "./corpus", + "--verbose", + "--", + "${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_cmin/README.md b/fuzzers/qemu_cmin/README.md new file mode 100644 index 0000000000..9b22b87e98 --- /dev/null +++ b/fuzzers/qemu_cmin/README.md @@ -0,0 +1,38 @@ +# qemu_cmin + +This folder contains an example fuzzer which runs each entry in the input corpus and minimizes the input corpus. 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_cmin/build.rs b/fuzzers/qemu_cmin/build.rs new file mode 100644 index 0000000000..16317b1560 --- /dev/null +++ b/fuzzers/qemu_cmin/build.rs @@ -0,0 +1,47 @@ +use vergen::EmitBuilder; + +#[macro_export] +macro_rules! assert_unique_feature { + () => {}; + ($first:tt $(,$rest:tt)*) => { + $( + #[cfg(all(not(any(doc, feature = "clippy")), 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_cmin/corpus/not_kitty.png b/fuzzers/qemu_cmin/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_cmin/corpus/not_kitty_gamma.png b/fuzzers/qemu_cmin/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_cmin/corpus/not_kitty_icc.png b/fuzzers/qemu_cmin/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_cmin/harness.cc b/fuzzers/qemu_cmin/harness.cc new file mode 100644 index 0000000000..d9e149d5cb --- /dev/null +++ b/fuzzers/qemu_cmin/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_cmin/src/fuzzer.rs b/fuzzers/qemu_cmin/src/fuzzer.rs new file mode 100644 index 0000000000..11ad859d7d --- /dev/null +++ b/fuzzers/qemu_cmin/src/fuzzer.rs @@ -0,0 +1,251 @@ +//! A libfuzzer-like fuzzer using qemu for binary-only coverage +//! +#[cfg(feature = "i386")] +use core::mem::size_of; +use std::{env, io, path::PathBuf, process}; + +use clap::{builder::Str, Parser}; +use libafl::{ + corpus::{Corpus, InMemoryOnDiskCorpus, NopCorpus}, + events::{EventRestarter, SimpleRestartingEventManager}, + executors::ExitKind, + feedbacks::MaxMapFeedback, + fuzzer::StdFuzzer, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, + observers::{ConstMapObserver, HitcountsMapObserver}, + schedulers::QueueScheduler, + state::{HasCorpus, StdState}, + Error, +}; +use libafl_bolts::{ + core_affinity::Cores, + current_nanos, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, + AsMutSlice, AsSlice, +}; +use libafl_qemu::{ + edges::{QemuEdgeCoverageChildHelper, EDGES_MAP_PTR, EDGES_MAP_SIZE}, + elf::EasyElf, + emu::Emulator, + ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, QemuForkExecutor, QemuHooks, + Regs, +}; + +#[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_cmin-{}",env!("CPU_TARGET")), + version = Version::default(), + about, + long_about = "Tool for generating minimizing corpus using QEMU instrumentation" +)] +pub struct FuzzerOptions { + #[arg(long, help = "Output directory")] + output: 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 const MAX_INPUT_SIZE: usize = 1048576; // 1MB + +pub fn fuzz() -> Result<(), Error> { + let mut options = FuzzerOptions::parse(); + + let corpus_dir = PathBuf::from(options.input); + + let files = corpus_dir + .read_dir() + .expect("Failed to read corpus dir") + .map(|x| Ok(x?.path())) + .collect::, io::Error>>() + .expect("Failed to read dir entry"); + + let program = env::args().next().unwrap(); + log::debug!("Program: {program:}"); + + options.args.insert(0, program); + log::debug!("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"); + log::debug!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}"); + + emu.entry_break(test_one_input_ptr); + + let pc: GuestReg = emu.read_reg(Regs::Pc).unwrap(); + log::debug!("Break at {pc:#x}"); + + let ret_addr: GuestAddr = emu.read_return_address().unwrap(); + log::debug!("Return address = {ret_addr:#x}"); + emu.set_breakpoint(ret_addr); + + let input_addr = emu + .map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite) + .unwrap(); + log::debug!("Placing input at {input_addr:#x}"); + + let stack_ptr: GuestAddr = emu.read_reg(Regs::Sp).unwrap(); + + let mut shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + let monitor = SimpleMonitor::with_user_monitor( + |s| { + println!("{s}"); + }, + true, + ); + let (state, mut mgr) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider) + { + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {err}"); + } + }, + }; + + let mut edges_shmem = shmem_provider.new_shmem(EDGES_MAP_SIZE).unwrap(); + let edges = edges_shmem.as_mut_slice(); + unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() }; + + let edges_observer = unsafe { + HitcountsMapObserver::new(ConstMapObserver::<_, EDGES_MAP_SIZE>::from_mut_ptr( + "edges", + edges.as_mut_ptr(), + )) + }; + + let mut feedback = MaxMapFeedback::tracking(&edges_observer, true, false); + + #[allow(clippy::let_unit_value)] + let mut objective = (); + + let mut state = state.unwrap_or_else(|| { + StdState::new( + StdRand::with_seed(current_nanos()), + InMemoryOnDiskCorpus::new(PathBuf::from(options.output)).unwrap(), + NopCorpus::new(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + let scheduler = QueueScheduler::new(); + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + 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 len = len as GuestReg; + + unsafe { + emu.write_mem(input_addr, buf); + emu.write_reg(Regs::Pc, test_one_input_ptr).unwrap(); + emu.write_reg(Regs::Sp, stack_ptr).unwrap(); + emu.write_return_address(ret_addr).unwrap(); + emu.write_function_argument(CallingConvention::Cdecl, 0, input_addr) + .unwrap(); + emu.write_function_argument(CallingConvention::Cdecl, 1, len) + .unwrap(); + emu.run(); + } + + ExitKind::Ok + }; + + let mut hooks = QemuHooks::new(&emu, tuple_list!(QemuEdgeCoverageChildHelper::default(),)); + + let mut executor = QemuForkExecutor::new( + &mut hooks, + &mut harness, + tuple_list!(edges_observer), + &mut fuzzer, + &mut state, + &mut mgr, + shmem_provider, + )?; + + println!("Importing {} seeds...", files.len()); + + if state.must_load_initial_inputs() { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &files) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus"); + process::exit(0); + }); + println!("Imported {} seeds from disk.", state.corpus().count()); + } + + let size = state.corpus().count(); + println!( + "Removed {} duplicates from {} seeds", + files.len() - size, + files.len() + ); + + mgr.send_exiting()?; + Ok(()) +} diff --git a/fuzzers/qemu_cmin/src/main.rs b/fuzzers/qemu_cmin/src/main.rs new file mode 100644 index 0000000000..3821382d04 --- /dev/null +++ b/fuzzers/qemu_cmin/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().unwrap(); +} + +#[cfg(not(target_os = "linux"))] +pub fn main() { + panic!("qemu-user and libafl_qemu is only supported on linux!"); +}