Add sample fuzzer which collects DrCov coverage for various architect… (#1300)
* 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 <you@example.com>
This commit is contained in:
parent
fa1e3fd504
commit
747a636f4f
6
.github/workflows/build_and_test.yml
vendored
6
.github/workflows/build_and_test.yml
vendored
@ -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
|
||||
|
7
fuzzers/qemu_coverage/.gitignore
vendored
Normal file
7
fuzzers/qemu_coverage/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
libpng-*
|
||||
libpng_harness
|
||||
libpng_harness_crashing
|
||||
zlib-*
|
||||
crashes
|
||||
target
|
||||
drcov.log
|
32
fuzzers/qemu_coverage/Cargo.toml
Normal file
32
fuzzers/qemu_coverage/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "qemu-coverage"
|
||||
version = "0.10.1"
|
||||
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>", "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" }
|
318
fuzzers/qemu_coverage/Makefile.toml
Normal file
318
fuzzers/qemu_coverage/Makefile.toml
Normal file
@ -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"
|
||||
]
|
39
fuzzers/qemu_coverage/README.md
Normal file
39
fuzzers/qemu_coverage/README.md
Normal file
@ -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 <arch>
|
||||
```
|
48
fuzzers/qemu_coverage/build.rs
Normal file
48
fuzzers/qemu_coverage/build.rs
Normal file
@ -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}");
|
||||
}
|
BIN
fuzzers/qemu_coverage/corpus/not_kitty.png
Normal file
BIN
fuzzers/qemu_coverage/corpus/not_kitty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 218 B |
BIN
fuzzers/qemu_coverage/corpus/not_kitty_alpha.png
Normal file
BIN
fuzzers/qemu_coverage/corpus/not_kitty_alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 376 B |
BIN
fuzzers/qemu_coverage/corpus/not_kitty_gamma.png
Normal file
BIN
fuzzers/qemu_coverage/corpus/not_kitty_gamma.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 B |
BIN
fuzzers/qemu_coverage/corpus/not_kitty_icc.png
Normal file
BIN
fuzzers/qemu_coverage/corpus/not_kitty_icc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 427 B |
193
fuzzers/qemu_coverage/harness.cc
Normal file
193
fuzzers/qemu_coverage/harness.cc
Normal file
@ -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 <string.h>" 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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<BufState *>(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<unsigned char> 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_bytep>(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);
|
||||
}
|
386
fuzzers/qemu_coverage/src/fuzzer.rs
Normal file
386
fuzzers/qemu_coverage/src/fuzzer.rs
Normal file
@ -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<Version> 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::<String>();
|
||||
|
||||
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<String>,
|
||||
}
|
||||
|
||||
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::<Result<Vec<DirEntry>, 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("<EMPTY>")
|
||||
);
|
||||
}
|
||||
|
||||
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::<u32>() as u32), &input_addr_bytes);
|
||||
|
||||
let len_bytes = len.to_le_bytes();
|
||||
emu.write_mem(stack_ptr + ((2 * size_of::<u32>()) 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::<Vec<PathBuf>>();
|
||||
|
||||
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::<usize, (u16, String)>::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:?}"),
|
||||
}
|
||||
}
|
13
fuzzers/qemu_coverage/src/main.rs
Normal file
13
fuzzers/qemu_coverage/src/main.rs
Normal file
@ -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!");
|
||||
}
|
@ -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};
|
||||
|
139
libafl/src/corpus/nop.rs
Normal file
139
libafl/src/corpus/nop.rs
Normal file
@ -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<I> {
|
||||
empty: Option<CorpusId>,
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I> UsesInput for NopCorpus<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
type Input = I;
|
||||
}
|
||||
|
||||
impl<I> Corpus for NopCorpus<I>
|
||||
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<I>) -> Result<CorpusId, Error> {
|
||||
Err(Error::Unsupported(
|
||||
"Unsupported".into(),
|
||||
ErrorBacktrace::new(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Replaces the testcase at the given idx
|
||||
#[inline]
|
||||
fn replace(&mut self, _idx: CorpusId, _testcase: Testcase<I>) -> Result<Testcase<I>, 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<Testcase<I>, Error> {
|
||||
Err(Error::Unsupported(
|
||||
"Unsupported".into(),
|
||||
ErrorBacktrace::new(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Get by id
|
||||
#[inline]
|
||||
fn get(&self, _idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
|
||||
Err(Error::Unsupported(
|
||||
"Unsupported".into(),
|
||||
ErrorBacktrace::new(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Current testcase scheduled
|
||||
#[inline]
|
||||
fn current(&self) -> &Option<CorpusId> {
|
||||
&self.empty
|
||||
}
|
||||
|
||||
/// Current testcase scheduled (mutable)
|
||||
#[inline]
|
||||
fn current_mut(&mut self) -> &mut Option<CorpusId> {
|
||||
&mut self.empty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn next(&self, _idx: CorpusId) -> Option<CorpusId> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn prev(&self, _idx: CorpusId) -> Option<CorpusId> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn first(&self) -> Option<CorpusId> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn last(&self) -> Option<CorpusId> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn nth(&self, _nth: usize) -> CorpusId {
|
||||
CorpusId::from(0_usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn load_input_into(&self, _testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
|
||||
Err(Error::Unsupported(
|
||||
"Unsupported".into(),
|
||||
ErrorBacktrace::new(),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn store_input_from(&self, _testcase: &Testcase<Self::Input>) -> Result<(), Error> {
|
||||
Err(Error::Unsupported(
|
||||
"Unsupported".into(),
|
||||
ErrorBacktrace::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> NopCorpus<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
/// Creates a new [`NopCorpus`].
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
empty: None,
|
||||
phantom: PhantomData {},
|
||||
}
|
||||
}
|
||||
}
|
@ -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"]
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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 = []
|
||||
|
||||
|
@ -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!(
|
||||
|
@ -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::<Self>() {
|
||||
|
@ -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;
|
||||
|
@ -40,6 +40,8 @@ pub enum Regs {
|
||||
Sp = 29,
|
||||
Fp = 30,
|
||||
Ra = 31,
|
||||
|
||||
Pc = 37,
|
||||
}
|
||||
|
||||
/// alias registers
|
||||
|
103
libafl_qemu/src/ppc.rs
Normal file
103
libafl_qemu/src/ppc.rs
Normal file
@ -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<PyObject> 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()
|
||||
}
|
@ -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::<QemuSnapshotHelper>().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::<QemuSnapshotHelper>().unwrap();
|
||||
|
Loading…
x
Reference in New Issue
Block a user