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:
WorksButNotTested 2023-06-06 10:50:38 +01:00 committed by GitHub
parent fa1e3fd504
commit 747a636f4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1318 additions and 4 deletions

View File

@ -193,7 +193,7 @@ jobs:
- uses: lyricwulf/abc@v1 - uses: lyricwulf/abc@v1
with: with:
# todo: remove afl++-clang when nyx support samcov_pcguard # 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` # update bash for macos to support `declare -A` command`
macos: llvm libpng nasm coreutils z3 bash wget macos: llvm libpng nasm coreutils z3 bash wget
- name: pip install - name: pip install
@ -218,6 +218,10 @@ jobs:
submodules: true # recursively checkout submodules submodules: true # recursively checkout submodules
fetch-depth: 0 # to diff with origin/main fetch-depth: 0 # to diff with origin/main
- uses: Swatinem/rust-cache@v2 - 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) - name: Build and run example fuzzers (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: RUN_ON_CI=1 ./scripts/test_all_fuzzers.sh run: RUN_ON_CI=1 ./scripts/test_all_fuzzers.sh

7
fuzzers/qemu_coverage/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
libpng-*
libpng_harness
libpng_harness_crashing
zlib-*
crashes
target
drcov.log

View 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" }

View 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"
]

View 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>
```

View 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}");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

View 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);
}

View 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:?}"),
}
}

View 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!");
}

View File

@ -25,8 +25,10 @@ pub use cached::CachedOnDiskCorpus;
pub mod minimizer; pub mod minimizer;
use core::{cell::RefCell, fmt}; use core::{cell::RefCell, fmt};
pub mod nop;
#[cfg(feature = "cmin")] #[cfg(feature = "cmin")]
pub use minimizer::*; pub use minimizer::*;
pub use nop::NopCorpus;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{inputs::UsesInput, Error}; use crate::{inputs::UsesInput, Error};

139
libafl/src/corpus/nop.rs Normal file
View 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 {},
}
}
}

View File

@ -22,6 +22,7 @@ i386 = ["libafl_qemu_sys/i386"] # build qemu for i386
arm = ["libafl_qemu_sys/arm"] # build qemu for arm arm = ["libafl_qemu_sys/arm"] # build qemu for arm
aarch64 = ["libafl_qemu_sys/aarch64"] # build qemu for aarch64 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) 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"] be = ["libafl_qemu_sys/be"]

View File

@ -28,6 +28,8 @@ pub fn build() {
"i386".to_string() "i386".to_string()
} else if cfg!(feature = "mips") { } else if cfg!(feature = "mips") {
"mips".to_string() "mips".to_string()
} else if cfg!(feature = "ppc") {
"ppc".to_string()
} else { } else {
env::var("CPU_TARGET").unwrap_or_else(|_| { env::var("CPU_TARGET").unwrap_or_else(|_| {
"x86_64".to_string() "x86_64".to_string()

View File

@ -18,6 +18,7 @@ i386 = [] # build qemu for i386
arm = [] # build qemu for arm arm = [] # build qemu for arm
aarch64 = [] # build qemu for aarch64 aarch64 = [] # build qemu for aarch64
mips = [] # build qemu for mips (el, use with the 'be' feature of mips be) mips = [] # build qemu for mips (el, use with the 'be' feature of mips be)
ppc = [] # build qemu for powerpc
be = [] be = []

View File

@ -38,7 +38,7 @@ pub fn build() {
// Make sure we have at most one architecutre feature set // Make sure we have at most one architecutre feature set
// Else, we default to `x86_64` - having a default makes CI easier :) // 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 // 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 // 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() "i386".to_string()
} else if cfg!(feature = "mips") { } else if cfg!(feature = "mips") {
"mips".to_string() "mips".to_string()
} else if cfg!(feature = "ppc") {
"ppc".to_string()
} else { } else {
env::var("CPU_TARGET").unwrap_or_else(|_| { env::var("CPU_TARGET").unwrap_or_else(|_| {
println!( println!(

View File

@ -274,6 +274,13 @@ where
ret_addr 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); // log::info!("RET @ 0x{:#x}", ret_addr);
let mut collectors = if let Some(h) = hooks.helpers_mut().match_first_type_mut::<Self>() { let mut collectors = if let Some(h) = hooks.helpers_mut().match_first_type_mut::<Self>() {

View File

@ -50,6 +50,11 @@ pub mod mips;
#[cfg(cpu_target = "mips")] #[cfg(cpu_target = "mips")]
pub use mips::*; pub use mips::*;
#[cfg(cpu_target = "ppc")]
pub mod ppc;
#[cfg(cpu_target = "ppc")]
pub use ppc::*;
pub mod elf; pub mod elf;
pub mod helper; pub mod helper;

View File

@ -40,6 +40,8 @@ pub enum Regs {
Sp = 29, Sp = 29,
Fp = 30, Fp = 30,
Ra = 31, Ra = 31,
Pc = 37,
} }
/// alias registers /// alias registers

103
libafl_qemu/src/ppc.rs Normal file
View 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()
}

View File

@ -14,7 +14,12 @@ use crate::SYS_fstatat64;
use crate::SYS_mmap; use crate::SYS_mmap;
#[cfg(any(cpu_target = "arm", cpu_target = "mips"))] #[cfg(any(cpu_target = "arm", cpu_target = "mips"))]
use crate::SYS_mmap2; 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::SYS_newfstatat;
use crate::{ use crate::{
emu::{Emulator, MmapPerms, SyscallHookResult}, emu::{Emulator, MmapPerms, SyscallHookResult},
@ -638,7 +643,12 @@ where
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();
h.access(a0 as GuestAddr, a3 as usize); 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 => { SYS_newfstatat => {
if a2 != 0 { if a2 != 0 {
let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap(); let h = hooks.match_helper_mut::<QemuSnapshotHelper>().unwrap();