From 49519b5d4d349f769e6f9784c6e296bf379f2c51 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 6 Oct 2023 17:16:30 +0100 Subject: [PATCH 1/2] Added qemu_tmin --- fuzzers/qemu_tmin/.gitignore | 7 + fuzzers/qemu_tmin/Cargo.toml | 33 ++ fuzzers/qemu_tmin/Makefile.toml | 323 +++++++++++++++++++ fuzzers/qemu_tmin/README.md | 38 +++ fuzzers/qemu_tmin/build.rs | 47 +++ fuzzers/qemu_tmin/corpus/not_kitty.png | Bin 0 -> 218 bytes fuzzers/qemu_tmin/corpus/not_kitty_alpha.png | Bin 0 -> 376 bytes fuzzers/qemu_tmin/corpus/not_kitty_gamma.png | Bin 0 -> 228 bytes fuzzers/qemu_tmin/corpus/not_kitty_icc.png | Bin 0 -> 427 bytes fuzzers/qemu_tmin/harness.cc | 193 +++++++++++ fuzzers/qemu_tmin/src/fuzzer.rs | 301 +++++++++++++++++ fuzzers/qemu_tmin/src/main.rs | 13 + 12 files changed, 955 insertions(+) create mode 100644 fuzzers/qemu_tmin/.gitignore create mode 100644 fuzzers/qemu_tmin/Cargo.toml create mode 100644 fuzzers/qemu_tmin/Makefile.toml create mode 100644 fuzzers/qemu_tmin/README.md create mode 100644 fuzzers/qemu_tmin/build.rs create mode 100644 fuzzers/qemu_tmin/corpus/not_kitty.png create mode 100644 fuzzers/qemu_tmin/corpus/not_kitty_alpha.png create mode 100644 fuzzers/qemu_tmin/corpus/not_kitty_gamma.png create mode 100644 fuzzers/qemu_tmin/corpus/not_kitty_icc.png create mode 100644 fuzzers/qemu_tmin/harness.cc create mode 100644 fuzzers/qemu_tmin/src/fuzzer.rs create mode 100644 fuzzers/qemu_tmin/src/main.rs diff --git a/fuzzers/qemu_tmin/.gitignore b/fuzzers/qemu_tmin/.gitignore new file mode 100644 index 00000000000..eb0ade05d18 --- /dev/null +++ b/fuzzers/qemu_tmin/.gitignore @@ -0,0 +1,7 @@ +libpng-* +libpng_harness +libpng_harness_crashing +zlib-* +crashes +target +output diff --git a/fuzzers/qemu_tmin/Cargo.toml b/fuzzers/qemu_tmin/Cargo.toml new file mode 100644 index 00000000000..051f6bf1034 --- /dev/null +++ b/fuzzers/qemu_tmin/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "qemu_tmin" +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_tmin/Makefile.toml b/fuzzers/qemu_tmin/Makefile.toml new file mode 100644 index 00000000000..bdf9f372db9 --- /dev/null +++ b/fuzzers/qemu_tmin/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_tmin-${CARGO_MAKE_PROFILE} +mv ${TARGET_DIR}/${PROFILE_DIR}/qemu_tmin ${TARGET_DIR}/${PROFILE_DIR}/qemu_tmin-${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_tmin-${CARGO_MAKE_PROFILE}" +args = [ + "--input", "./corpus", + "--output", "./output", + "--iterations", "100", + "--", + "${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_tmin/README.md b/fuzzers/qemu_tmin/README.md new file mode 100644 index 00000000000..992be8ed47c --- /dev/null +++ b/fuzzers/qemu_tmin/README.md @@ -0,0 +1,38 @@ +# qemu_tmin + +This folder contains a test case minimizer using QEMU instrumentation. 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_tmin/build.rs b/fuzzers/qemu_tmin/build.rs new file mode 100644 index 00000000000..16317b15607 --- /dev/null +++ b/fuzzers/qemu_tmin/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_tmin/corpus/not_kitty.png b/fuzzers/qemu_tmin/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_tmin/corpus/not_kitty_gamma.png b/fuzzers/qemu_tmin/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_tmin/corpus/not_kitty_icc.png b/fuzzers/qemu_tmin/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_tmin/harness.cc b/fuzzers/qemu_tmin/harness.cc new file mode 100644 index 00000000000..d9e149d5cb3 --- /dev/null +++ b/fuzzers/qemu_tmin/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_tmin/src/fuzzer.rs b/fuzzers/qemu_tmin/src/fuzzer.rs new file mode 100644 index 00000000000..88c5e33e3b6 --- /dev/null +++ b/fuzzers/qemu_tmin/src/fuzzer.rs @@ -0,0 +1,301 @@ +//! 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, ptr::addr_of_mut}; + +use clap::{builder::Str, Parser}; +use libafl::{ + corpus::{Corpus, CorpusId, NopCorpus, OnDiskCorpus}, + events::{launcher::Launcher, EventConfig, EventRestarter}, + executors::{ExitKind, TimeoutExecutor}, + feedbacks::MaxMapFeedback, + fuzzer::StdFuzzer, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + mutators::{havoc_mutations, StdScheduledMutator}, + observers::{HitcountsMapObserver, VariableMapObserver}, + prelude::LlmpRestartingEventManager, + schedulers::QueueScheduler, + stages::{MapEqualityFactory, StagesTuple, StdTMinMutationalStage}, + state::{HasCorpus, StdState}, + Error, +}; +use libafl_bolts::{ + core_affinity::Cores, + current_nanos, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::tuple_list, + AsSlice, +}; +use libafl_qemu::{ + edges::{edges_map_mut_slice, MAX_EDGES_NUM}, + elf::EasyElf, + emu::Emulator, + ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, QemuEdgeCoverageHelper, + QemuExecutor, 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_tmin-{}",env!("CPU_TARGET")), + version = Version::default(), + about, + long_about = "Tool for test case minimization 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, + + #[arg( + long, + help = "Number of iterations for minimization", + default_value_t = 1024_usize + )] + iterations: usize, + + #[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() { + 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(); + 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); + + for m in emu.mappings() { + log::debug!( + "Mapping: 0x{:016x}-0x{:016x}, {}", + m.start(), + m.end(), + m.path().unwrap_or("") + ); + } + + 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 reset = |buf: &[u8], len: GuestReg| -> Result<(), String> { + unsafe { + emu.write_mem(input_addr, buf); + emu.write_reg(Regs::Pc, test_one_input_ptr)?; + emu.write_reg(Regs::Sp, stack_ptr)?; + emu.write_return_address(ret_addr)?; + emu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?; + emu.write_function_argument(CallingConvention::Cdecl, 1, len)?; + emu.run(); + Ok(()) + } + }; + + 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; + reset(buf, len).unwrap(); + 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)? + } + + let edges_observer = unsafe { + HitcountsMapObserver::new(VariableMapObserver::from_mut_slice( + "edges", + edges_map_mut_slice(), + addr_of_mut!(MAX_EDGES_NUM), + )) + }; + + let feedback_factory = MapEqualityFactory::with_observer(&edges_observer); + 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()), + OnDiskCorpus::new(PathBuf::from(options.output.clone())).unwrap(), + NopCorpus::new(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + let scheduler = QueueScheduler::new(); + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut hooks = QemuHooks::new(&emu, tuple_list!(QemuEdgeCoverageHelper::default())); + + let executor = QemuExecutor::new( + &mut hooks, + &mut harness, + tuple_list!(edges_observer), + &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); + }); + log::debug!("We imported {} inputs from disk.", state.corpus().count()); + } + + let mutator = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdTMinMutationalStage::new( + mutator, + feedback_factory, + options.iterations + )); + + stages.perform_all( + &mut fuzzer, + &mut executor, + &mut state, + &mut mgr, + CorpusId::from(0_usize), + )?; + + log::debug!("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_tmin/src/main.rs b/fuzzers/qemu_tmin/src/main.rs new file mode 100644 index 00000000000..bc1e80f7670 --- /dev/null +++ b/fuzzers/qemu_tmin/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!"); +} From 2b8a659e49762c2f4b4b7e7d2081926c5de7578b Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 10 Oct 2023 18:30:16 +0100 Subject: [PATCH 2/2] Seems to only generate one output per file, but doesn't seem to reduce size? --- fuzzers/qemu_tmin/Makefile.toml | 2 +- fuzzers/qemu_tmin/corpus/not_kitty_alpha.png | Bin 376 -> 0 bytes .../qemu_tmin/corpus/not_kitty_alpha_long.png | Bin 0 -> 524664 bytes fuzzers/qemu_tmin/src/fuzzer.rs | 64 ++++++++---------- 4 files changed, 30 insertions(+), 36 deletions(-) delete mode 100644 fuzzers/qemu_tmin/corpus/not_kitty_alpha.png create mode 100644 fuzzers/qemu_tmin/corpus/not_kitty_alpha_long.png diff --git a/fuzzers/qemu_tmin/Makefile.toml b/fuzzers/qemu_tmin/Makefile.toml index bdf9f372db9..d654a7000d3 100644 --- a/fuzzers/qemu_tmin/Makefile.toml +++ b/fuzzers/qemu_tmin/Makefile.toml @@ -230,7 +230,7 @@ command = "${TARGET_DIR}/${PROFILE_DIR}/qemu_tmin-${CARGO_MAKE_PROFILE}" args = [ "--input", "./corpus", "--output", "./output", - "--iterations", "100", + "--iterations", "10000", "--", "${TARGET_DIR}/libpng-harness-${CARGO_MAKE_PROFILE}", ] diff --git a/fuzzers/qemu_tmin/corpus/not_kitty_alpha.png b/fuzzers/qemu_tmin/corpus/not_kitty_alpha.png deleted file mode 100644 index 2fb8da2c8f570ee1fbf8a2d0e743324d69ce3056..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv(Ey(i*Z(j86qxz{Oxl?<#xv6x zGFHw3@|jA4{DK))n@`^!0pu|zdAqwXbg;^L06Clm9+AaB8pQTsa66f82V|ssx;Tbt zoZox#G;f1~fNNk9XV`-435++IObj?h5}5u@e~@q7tSw$OU42hWLvl-9L!b5Wq(i=| z_1MghE;(h-93k+5ZHBqd6_<+>x+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_tmin/corpus/not_kitty_alpha_long.png b/fuzzers/qemu_tmin/corpus/not_kitty_alpha_long.png new file mode 100644 index 0000000000000000000000000000000000000000..92134133bf0fef76504d38ad3ec92115fe39c92c GIT binary patch literal 524664 zcmeIu%S)6|6aer~EkVJM=wU|SMvBb%Mh$COa7CvT1BV@`Z!WLrYq6i{4 znGn>XO$cp^UJD__5=029O%$~4u?-6QhG^lYzu^PQeUilZ%!_3NJW^u88CKC)jyc_j}xz6^kV777kad*@e z^dD_W1lQJXYmJ(cbFDoWqMWZ=gCh%P4@SjcTcRnsW9h~6o3in$^5zr$B`5E{>n(Uc z(GZqw=q>y`{V7|2wPy3>>Fv)a#|I|I#&7PQ@1Hw9ySupY)q~fEin_~x6yIsw^)|75 z>qht9mQ7zOdY-JS7|Nd7T7K_i`^&Mp(-%Mg{Lt|ATgy~s_4;Suqm|XU=I&goFPDxF zjHII!)P=DR==p{8f#_4v*4&