Qemu tmin (#3118)
* Start on qemu_tmin * WIP * qemu_tmin working for single testcase. Also some comment improvements. * Add env_logger to baby_fuzzer * Remove old reference. * Added comment doc for qemu_tmin. * Slight reorder for parallelisation * Finished single-thread qemu_tmin * Finishing touches to single thread version. * A pre_commit.sh change I didn't notice. * Duplicate to attempt multi-threaded version * Fix taplo whine. Mark "fork" mode as broken. * Launcher for parallelisation implemented, but for one core. * Running in parallel. Now need tidy up. * Parallel version complete. * Add comment * Merged single-core/multi-core qemu_tmin into one crate * Removed forkexecutor mode. * Precommit fixes * Add qemu_tmin to build_and_test.yml * Clippy fixes * Change tmin test cores to 0.
This commit is contained in:
parent
6bbff51951
commit
c3475cd577
1
.github/workflows/build_and_test.yml
vendored
1
.github/workflows/build_and_test.yml
vendored
@ -384,6 +384,7 @@ jobs:
|
||||
fuzzer:
|
||||
# Binary only
|
||||
- ./fuzzers/binary_only/qemu_cmin
|
||||
- ./fuzzers/binary_only/qemu_tmin
|
||||
- ./fuzzers/binary_only/qemu_coverage
|
||||
- ./fuzzers/binary_only/qemu_launcher
|
||||
arch:
|
||||
|
@ -23,6 +23,7 @@ opt-level = 3
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.11.7"
|
||||
libafl = { path = "../../../libafl", features = ["tui_monitor"] }
|
||||
libafl_bolts = { path = "../../../libafl_bolts" }
|
||||
log = { version = "0.4.22", features = ["release_max_level_info"] }
|
||||
|
@ -35,6 +35,7 @@ fn signals_set(idx: usize) {
|
||||
|
||||
#[expect(clippy::manual_assert)]
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
// The closure that we want to fuzz
|
||||
let mut harness = |input: &BytesInput| {
|
||||
let target = input.target_bytes();
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||
//! A binary-only corpus minimizer using qemu, similar to AFL++ afl-cmin
|
||||
#[cfg(feature = "i386")]
|
||||
use core::mem::size_of;
|
||||
#[cfg(feature = "snapshot")]
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||
//! A binary-only corpus minimizer using qemu, similar to AFL++ afl-cmin
|
||||
#[cfg(target_os = "linux")]
|
||||
mod fuzzer;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||
//! A qemu test case runner to generate drcov coverage outputs
|
||||
#[cfg(feature = "i386")]
|
||||
use core::mem::size_of;
|
||||
use core::time::Duration;
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||
//! A qemu test case runner to generate drcov coverage outputs
|
||||
#[cfg(target_os = "linux")]
|
||||
mod fuzzer;
|
||||
|
||||
|
8
fuzzers/binary_only/qemu_tmin/.gitignore
vendored
Normal file
8
fuzzers/binary_only/qemu_tmin/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
libpng-*
|
||||
libpng_harness
|
||||
libpng_harness_crashing
|
||||
zlib-*
|
||||
crashes
|
||||
target
|
||||
output
|
||||
corpus/
|
3598
fuzzers/binary_only/qemu_tmin/Cargo.lock
generated
Normal file
3598
fuzzers/binary_only/qemu_tmin/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
fuzzers/binary_only/qemu_tmin/Cargo.toml
Normal file
51
fuzzers/binary_only/qemu_tmin/Cargo.toml
Normal file
@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "qemu_tmin"
|
||||
version = "0.15.1"
|
||||
authors = [
|
||||
"Andrea Fioraldi <andreafioraldi@gmail.com>",
|
||||
"Dominik Maier <domenukk@gmail.com>",
|
||||
"WorksButNotTested",
|
||||
"forky2",
|
||||
]
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
#lto = true
|
||||
#codegen-units = 1
|
||||
#opt-level = 3
|
||||
debug = true
|
||||
|
||||
[[bin]]
|
||||
name = "tmin_single_core"
|
||||
path = "src/tmin_single_core.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tmin_multi_core"
|
||||
path = "src/tmin_multi_core.rs"
|
||||
|
||||
[features]
|
||||
default = ["std", "snapshot"]
|
||||
fork = []
|
||||
snapshot = []
|
||||
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 = "9.0.1", features = ["build", "cargo", "rustc", "si"] }
|
||||
vergen-git2 = "1.0.1"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.18", features = ["derive", "string"] }
|
||||
env_logger = { version = "0.11.5" }
|
||||
libafl = { path = "../../../libafl" }
|
||||
libafl_bolts = { path = "../../../libafl_bolts" }
|
||||
libafl_qemu = { path = "../../../libafl_qemu", features = ["usermode"] }
|
||||
libafl_targets = { path = "../../../libafl_targets" }
|
||||
log = { version = "0.4.22", features = ["release_max_level_info"] }
|
||||
rangemap = { version = "1.5.1" }
|
58
fuzzers/binary_only/qemu_tmin/Justfile
Normal file
58
fuzzers/binary_only/qemu_tmin/Justfile
Normal file
@ -0,0 +1,58 @@
|
||||
import "../../../just/libafl-qemu-libpng.just"
|
||||
|
||||
FUZZER_NAME := ""
|
||||
FUZZER_SINGLE := BUILD_DIR / "tmin_single_core" + FUZZER_EXTENSION
|
||||
FUZZER_MULTI := BUILD_DIR / "tmin_multi_core" + FUZZER_EXTENSION
|
||||
HARNESS := TARGET_DIR / ("libpng-harness-" + PROFILE)
|
||||
|
||||
[unix]
|
||||
build:
|
||||
cargo build \
|
||||
--profile {{ PROFILE }} \
|
||||
--features {{ ARCH }} \
|
||||
--target-dir {{ TARGET_DIR }}
|
||||
|
||||
[unix]
|
||||
harness: libpng
|
||||
#!/bin/bash
|
||||
|
||||
source {{ DOTENV }}
|
||||
|
||||
$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"{{ HARNESS }}" \
|
||||
-lm -static
|
||||
|
||||
[unix]
|
||||
run_single: harness build
|
||||
{{ FUZZER_SINGLE }} \
|
||||
--output ./output \
|
||||
--input ./corpus \
|
||||
--verbose \
|
||||
-- {{ HARNESS }}
|
||||
|
||||
[unix]
|
||||
run_multi: harness build
|
||||
{{ FUZZER_MULTI }} \
|
||||
--output ./output \
|
||||
--input ./corpus \
|
||||
--cores 0 \
|
||||
--verbose \
|
||||
-- {{ HARNESS }}
|
||||
|
||||
[unix]
|
||||
test:
|
||||
ARCH=x86_64 just run_single
|
||||
ARCH=x86_64 just run_multi
|
||||
ARCH=arm just run_single
|
||||
ARCH=arm just run_multi
|
||||
|
||||
[unix]
|
||||
clean:
|
||||
cargo clean
|
45
fuzzers/binary_only/qemu_tmin/README.md
Normal file
45
fuzzers/binary_only/qemu_tmin/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
# qemu_tmin
|
||||
|
||||
QEMU testcase minimizer.
|
||||
|
||||
This folder contains an example fuzzer which runs each entry in the input corpus
|
||||
and minimizes the input, ensuring that coverage map remains the same. The output
|
||||
is a new corpus that may or may not be smaller than the original inputs, but
|
||||
will not be larger.
|
||||
|
||||
If some input files are idential, only one of each duplicate set will be kept
|
||||
for minimization.
|
||||
|
||||
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
|
||||
just run
|
||||
```
|
||||
|
||||
```bash
|
||||
just <arch>
|
||||
```
|
59
fuzzers/binary_only/qemu_tmin/build.rs
Normal file
59
fuzzers/binary_only/qemu_tmin/build.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use vergen::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder, SysinfoBuilder};
|
||||
use vergen_git2::Git2Builder;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_unique_feature {
|
||||
() => {};
|
||||
($first:tt $(,$rest:tt)*) => {
|
||||
$(
|
||||
#[cfg(all(not(doc), feature = $first, feature = $rest))]
|
||||
compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together"));
|
||||
)*
|
||||
assert_unique_feature!($($rest),*);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let build = BuildBuilder::all_build().unwrap();
|
||||
let cargo = CargoBuilder::all_cargo().unwrap();
|
||||
let git = Git2Builder::all_git().unwrap();
|
||||
let rustc = RustcBuilder::all_rustc().unwrap();
|
||||
let sysinfo = SysinfoBuilder::all_sysinfo().unwrap();
|
||||
|
||||
Emitter::default()
|
||||
.add_instructions(&build)
|
||||
.unwrap()
|
||||
.add_instructions(&cargo)
|
||||
.unwrap()
|
||||
.add_instructions(&git)
|
||||
.unwrap()
|
||||
.add_instructions(&rustc)
|
||||
.unwrap()
|
||||
.add_instructions(&sysinfo)
|
||||
.unwrap()
|
||||
.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/binary_only/qemu_tmin/corpus/not_kitty.png
Normal file
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 218 B |
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_alpha.png
Normal file
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 376 B |
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_alpha_long.png
Normal file
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_alpha_long.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_gamma.png
Normal file
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_gamma.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 B |
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_icc.png
Normal file
BIN
fuzzers/binary_only/qemu_tmin/corpus/not_kitty_icc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 427 B |
193
fuzzers/binary_only/qemu_tmin/harness.cc
Normal file
193
fuzzers/binary_only/qemu_tmin/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);
|
||||
}
|
372
fuzzers/binary_only/qemu_tmin/src/tmin_multi_core.rs
Normal file
372
fuzzers/binary_only/qemu_tmin/src/tmin_multi_core.rs
Normal file
@ -0,0 +1,372 @@
|
||||
//! A binary-only testcase minimizer using qemu, similar to AFL++ afl-tmin
|
||||
#[cfg(feature = "i386")]
|
||||
use core::mem::size_of;
|
||||
#[cfg(feature = "snapshot")]
|
||||
use core::time::Duration;
|
||||
use std::{env, fmt::Write, io, path::PathBuf, process, ptr::NonNull};
|
||||
|
||||
use clap::{builder::Str, Parser};
|
||||
use libafl::{
|
||||
corpus::{Corpus, CorpusId, HasCurrentCorpusId, InMemoryCorpus, InMemoryOnDiskCorpus},
|
||||
events::{
|
||||
launcher::Launcher, ClientDescription, EventConfig, LlmpRestartingEventManager, SendExiting,
|
||||
},
|
||||
executors::ExitKind,
|
||||
feedbacks::MaxMapFeedback,
|
||||
fuzzer::StdFuzzer,
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
monitors::MultiMonitor,
|
||||
mutators::{havoc_mutations, HavocScheduledMutator},
|
||||
observers::{ConstMapObserver, HitcountsMapObserver},
|
||||
schedulers::QueueScheduler,
|
||||
stages::{ObserverEqualityFactory, StagesTuple, StdTMinMutationalStage},
|
||||
state::{HasCorpus, StdState},
|
||||
Error,
|
||||
};
|
||||
use libafl_bolts::{
|
||||
core_affinity::Cores,
|
||||
os::unix_signals::Signal,
|
||||
rands::StdRand,
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
tuples::tuple_list,
|
||||
AsSlice, AsSliceMut,
|
||||
};
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
|
||||
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
|
||||
};
|
||||
#[cfg(feature = "snapshot")]
|
||||
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
|
||||
use libafl_targets::{EDGES_MAP_DEFAULT_SIZE, EDGES_MAP_PTR};
|
||||
|
||||
#[cfg(feature = "fork")]
|
||||
compile_error!("'fork' feature is currently not implemented; pending forkserver PR.");
|
||||
|
||||
#[cfg(all(feature = "fork", feature = "snapshot"))]
|
||||
compile_error!("Cannot enable both 'fork' and 'snapshot' features at the same time.");
|
||||
|
||||
#[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()
|
||||
.fold(String::new(), |mut output, (k, v)| {
|
||||
let _ = writeln!(output, "{k:25}: {v}");
|
||||
output
|
||||
});
|
||||
|
||||
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 = "Module for minimizing test cases 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<String>,
|
||||
}
|
||||
|
||||
pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB
|
||||
|
||||
pub fn fuzz() {
|
||||
// Initialise env_logger
|
||||
env_logger::init();
|
||||
|
||||
// Parse user options
|
||||
let mut options = FuzzerOptions::parse();
|
||||
let program = env::args().next().unwrap();
|
||||
log::info!("Program: {program:}");
|
||||
options.args.insert(0, program);
|
||||
log::info!("ARGS: {:#?}", options.args);
|
||||
|
||||
// Get all of the files supplied from the input corpus
|
||||
let corpus_dir = PathBuf::from(options.input);
|
||||
let files = corpus_dir
|
||||
.read_dir()
|
||||
.expect("Failed to read corpus dir")
|
||||
.map(|x| Ok(x?.path()))
|
||||
.collect::<Result<Vec<PathBuf>, io::Error>>()
|
||||
.expect("Failed to read dir entry");
|
||||
|
||||
// To run parallelised, we work out number of files to process per core.
|
||||
let num_files = files.len();
|
||||
let num_cores = options.cores.ids.len();
|
||||
let files_per_core = (num_files as f64 / num_cores as f64).ceil() as usize;
|
||||
|
||||
// Create a shared memory region for sharing coverage map between fuzzer and target
|
||||
// In snapshot mode, this is only required for the SimpleRestartingEventManager.
|
||||
// However, fork mode requires it to share memory between parent and child,
|
||||
// so we use it in both cases.
|
||||
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
|
||||
|
||||
// Clear LD_LIBRARY_PATH
|
||||
env::remove_var("LD_LIBRARY_PATH");
|
||||
|
||||
// The client closure is a 'fuzzing' process for a single core.
|
||||
// Because it is used inside the Launcher (below) it is a fork()ed separate
|
||||
// process from our main process here.
|
||||
let mut run_client = |state: Option<_>,
|
||||
mut mgr: LlmpRestartingEventManager<_, _, _, _, _>,
|
||||
client_description: ClientDescription| {
|
||||
let core_id = client_description.core_id();
|
||||
let core_idx = options
|
||||
.cores
|
||||
.position(core_id)
|
||||
.expect("Failed to get core index");
|
||||
|
||||
let files: Vec<PathBuf> = files
|
||||
.iter()
|
||||
.skip(files_per_core * core_idx)
|
||||
.take(files_per_core)
|
||||
.cloned()
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
if files.is_empty() {
|
||||
mgr.send_exiting()?;
|
||||
Err(Error::ShuttingDown)?
|
||||
}
|
||||
|
||||
let mut edges_shmem = shmem_provider
|
||||
.clone()
|
||||
.new_shmem(EDGES_MAP_DEFAULT_SIZE)
|
||||
.unwrap();
|
||||
let edges = edges_shmem.as_slice_mut();
|
||||
unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() };
|
||||
|
||||
// We use a HitcountsMapObserver to observe the coverage map
|
||||
let mut edges_observer = unsafe {
|
||||
HitcountsMapObserver::new(ConstMapObserver::from_mut_ptr(
|
||||
"edges",
|
||||
NonNull::new(edges.as_mut_ptr())
|
||||
.expect("The edge map pointer is null.")
|
||||
.cast::<[u8; EDGES_MAP_DEFAULT_SIZE]>(),
|
||||
))
|
||||
};
|
||||
|
||||
// In either fork/snapshot mode, we link the observer to QEMU
|
||||
#[cfg(feature = "snapshot")]
|
||||
let modules = tuple_list!(
|
||||
StdEdgeCoverageChildModule::builder()
|
||||
.const_map_observer(edges_observer.as_mut())
|
||||
.build()?,
|
||||
SnapshotModule::new()
|
||||
);
|
||||
|
||||
// Create our QEMU emulator
|
||||
let emulator = Emulator::empty()
|
||||
.qemu_parameters(options.args.clone())
|
||||
.modules(modules)
|
||||
.build()?;
|
||||
let qemu = emulator.qemu();
|
||||
|
||||
// Use ELF tools to get the target function
|
||||
let mut elf_buffer = Vec::new();
|
||||
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap();
|
||||
let test_one_input_ptr = elf
|
||||
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
|
||||
.expect("Symbol LLVMFuzzerTestOneInput not found");
|
||||
log::info!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}");
|
||||
|
||||
// Run target until the target function, and store important registers.
|
||||
// Set a breakpoint on target function return.
|
||||
qemu.entry_break(test_one_input_ptr);
|
||||
let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap();
|
||||
let ret_addr: GuestAddr = qemu.read_return_address().unwrap();
|
||||
let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap();
|
||||
log::info!("Break at {pc:#x}");
|
||||
log::info!("Return address = {ret_addr:#x}");
|
||||
qemu.set_breakpoint(ret_addr);
|
||||
|
||||
// Map a private region for input buffer
|
||||
let input_addr = qemu
|
||||
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
|
||||
.unwrap();
|
||||
log::info!("Placing input at {input_addr:#x}");
|
||||
|
||||
// Rust harness: this closure copies an input buffer to our private region
|
||||
// for target function input and updates registers to a single iteration
|
||||
// before telling QEMU to resume execution.
|
||||
#[cfg(feature = "snapshot")]
|
||||
let mut harness =
|
||||
|_emulator: &mut Emulator<_, _, _, _, _, _, _>, _state: &mut _, input: &BytesInput| {
|
||||
let target = input.target_bytes();
|
||||
let mut buf = target.as_slice();
|
||||
let mut len = buf.len();
|
||||
if len > MAX_INPUT_SIZE {
|
||||
buf = &buf[0..MAX_INPUT_SIZE];
|
||||
len = MAX_INPUT_SIZE;
|
||||
}
|
||||
let len = len as GuestReg;
|
||||
|
||||
unsafe {
|
||||
qemu.write_mem(input_addr, buf).expect("qemu write failed.");
|
||||
|
||||
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
|
||||
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
|
||||
qemu.write_return_address(ret_addr).unwrap();
|
||||
qemu.write_function_argument(0, input_addr).unwrap();
|
||||
qemu.write_function_argument(1, len).unwrap();
|
||||
|
||||
match qemu.run() {
|
||||
Ok(QemuExitReason::Breakpoint(_)) => {}
|
||||
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(
|
||||
Signal::SigInterrupt,
|
||||
))) => process::exit(0),
|
||||
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
|
||||
_ => panic!("Unexpected QEMU exit."),
|
||||
}
|
||||
}
|
||||
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Our fuzzer is a simple queue scheduler (FIFO), and has no corpus feedback
|
||||
// or objective feedback. This is important as we need the MaxMapFeedback
|
||||
// on the observer to be constrained by ObserverEqualityFactory which will
|
||||
// ensure interestingness is only true for identical coverage.
|
||||
let scheduler = QueueScheduler::new();
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, (), ());
|
||||
|
||||
// We define the stages that will be performed by the fuzzer. We have one
|
||||
// stage of the StdTMinMutationalStage which will run for n iterations.
|
||||
// The havoc mutator will generate mutations; only those shorter than the
|
||||
// current input will be tested; and the ObservreEqualityFactory will
|
||||
// provide an observer that ensures additions to the corpus have the same
|
||||
// coverage.
|
||||
let minimizer = HavocScheduledMutator::new(havoc_mutations());
|
||||
let factory = ObserverEqualityFactory::new(&edges_observer);
|
||||
let mut stages = tuple_list!(StdTMinMutationalStage::new(
|
||||
minimizer,
|
||||
factory,
|
||||
options.iterations
|
||||
),);
|
||||
|
||||
// Create a state instance. Unlike a typical fuzzer, we start with an empty
|
||||
// input corpus, and we don't care about 'solutions' so store in an
|
||||
// InMemoryCorpus.
|
||||
let mut feedback = MaxMapFeedback::new(&edges_observer);
|
||||
let mut objective = ();
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
StdState::new(
|
||||
StdRand::new(),
|
||||
InMemoryOnDiskCorpus::new(PathBuf::from(options.output.clone())).unwrap(),
|
||||
InMemoryCorpus::new(),
|
||||
&mut feedback,
|
||||
&mut objective,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
// The executor. Nothing exciting here.
|
||||
#[cfg(feature = "snapshot")]
|
||||
let mut executor = QemuExecutor::new(
|
||||
emulator,
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer),
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
Duration::from_millis(5000),
|
||||
)?;
|
||||
|
||||
// Load the input corpus
|
||||
state.load_initial_inputs_by_filenames_forced(
|
||||
&mut fuzzer,
|
||||
&mut executor,
|
||||
&mut mgr,
|
||||
&files,
|
||||
)?;
|
||||
log::info!("Processed {} inputs from disk.", files.len());
|
||||
|
||||
// Iterate over initial corpus_ids and minimize each.
|
||||
let corpus_ids: Vec<CorpusId> = state.corpus().ids().collect();
|
||||
for corpus_id in corpus_ids {
|
||||
state.set_corpus_id(corpus_id)?;
|
||||
stages.perform_all(&mut fuzzer, &mut executor, &mut state, &mut mgr)?;
|
||||
}
|
||||
|
||||
mgr.send_exiting()?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// The Launcher creates forks on the specified list of cores, and for each
|
||||
// one runs the run_client closure which performs the work.
|
||||
// It links the forks to a MultiMonitor which just prints out the events
|
||||
// it receives.
|
||||
match Launcher::builder()
|
||||
.shmem_provider(shmem_provider.clone())
|
||||
.broker_port(options.port)
|
||||
.configuration(EventConfig::from_build_id())
|
||||
.monitor(MultiMonitor::new(|s| println!("{s}")))
|
||||
.run_client(&mut run_client)
|
||||
.cores(&options.cores)
|
||||
.build()
|
||||
.launch()
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(Error::ShuttingDown) => {
|
||||
println!("Run finished successfully.");
|
||||
}
|
||||
Err(err) => panic!("Failed to run launcher: {err:?}"),
|
||||
}
|
||||
|
||||
// Here it would be nice to sum the number of cases processed by each fork
|
||||
// to provide an exit summary (as is done for the "snapshot" version), but
|
||||
// to do so would require more communication between child and parent. So
|
||||
// it is left undone.
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn main() {
|
||||
fuzz();
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn main() {
|
||||
panic!("qemu-user and libafl_qemu is only supported on linux!");
|
||||
}
|
319
fuzzers/binary_only/qemu_tmin/src/tmin_single_core.rs
Normal file
319
fuzzers/binary_only/qemu_tmin/src/tmin_single_core.rs
Normal file
@ -0,0 +1,319 @@
|
||||
//! A binary-only testcase minimizer using qemu, similar to AFL++ afl-tmin
|
||||
#[cfg(feature = "i386")]
|
||||
use core::mem::size_of;
|
||||
#[cfg(feature = "snapshot")]
|
||||
use core::time::Duration;
|
||||
use std::{env, fmt::Write, io, path::PathBuf, process, ptr::NonNull};
|
||||
|
||||
use clap::{builder::Str, Parser};
|
||||
use libafl::{
|
||||
corpus::{Corpus, CorpusId, HasCurrentCorpusId, InMemoryCorpus, InMemoryOnDiskCorpus},
|
||||
events::{SendExiting, SimpleRestartingEventManager},
|
||||
executors::ExitKind,
|
||||
feedbacks::MaxMapFeedback,
|
||||
fuzzer::StdFuzzer,
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
monitors::SimpleMonitor,
|
||||
mutators::{havoc_mutations, HavocScheduledMutator},
|
||||
observers::{ConstMapObserver, HitcountsMapObserver},
|
||||
schedulers::QueueScheduler,
|
||||
stages::{ObserverEqualityFactory, StagesTuple, StdTMinMutationalStage},
|
||||
state::{HasCorpus, StdState},
|
||||
Error,
|
||||
};
|
||||
use libafl_bolts::{
|
||||
os::unix_signals::Signal,
|
||||
rands::StdRand,
|
||||
shmem::{ShMemProvider, StdShMemProvider},
|
||||
tuples::tuple_list,
|
||||
AsSlice, AsSliceMut,
|
||||
};
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
|
||||
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
|
||||
};
|
||||
#[cfg(feature = "snapshot")]
|
||||
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
|
||||
use libafl_targets::{EDGES_MAP_DEFAULT_SIZE, EDGES_MAP_PTR};
|
||||
|
||||
#[cfg(feature = "fork")]
|
||||
compile_error!("'fork' feature is currently not implemented; pending forkserver PR.");
|
||||
|
||||
#[cfg(all(feature = "fork", feature = "snapshot"))]
|
||||
compile_error!("Cannot enable both 'fork' and 'snapshot' features at the same time.");
|
||||
|
||||
#[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()
|
||||
.fold(String::new(), |mut output, (k, v)| {
|
||||
let _ = writeln!(output, "{k:25}: {v}");
|
||||
output
|
||||
});
|
||||
|
||||
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 = "Module for minimizing test cases 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,
|
||||
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<String>,
|
||||
}
|
||||
|
||||
pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB
|
||||
|
||||
pub fn fuzz() -> Result<(), Error> {
|
||||
// Initialise env_logger
|
||||
env_logger::init();
|
||||
|
||||
// Parse user options
|
||||
let mut options = FuzzerOptions::parse();
|
||||
let program = env::args().next().unwrap();
|
||||
log::info!("Program: {program:}");
|
||||
options.args.insert(0, program);
|
||||
log::info!("ARGS: {:#?}", options.args);
|
||||
|
||||
// Get all of the files supplied from the input corpus
|
||||
let corpus_dir = PathBuf::from(options.input);
|
||||
let files = corpus_dir
|
||||
.read_dir()
|
||||
.expect("Failed to read corpus dir")
|
||||
.map(|x| Ok(x?.path()))
|
||||
.collect::<Result<Vec<PathBuf>, io::Error>>()
|
||||
.expect("Failed to read dir entry");
|
||||
|
||||
// Clear LD_LIBRARY_PATH
|
||||
env::remove_var("LD_LIBRARY_PATH");
|
||||
|
||||
// Create a shared memory region for sharing coverage map between fuzzer and target
|
||||
// In snapshot mode, this is only required for the SimpleRestartingEventManager.
|
||||
// However, fork mode requires it to share memory between parent and child,
|
||||
// so we use it in both cases.
|
||||
let mut shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
|
||||
let mut edges_shmem = shmem_provider.new_shmem(EDGES_MAP_DEFAULT_SIZE).unwrap();
|
||||
let edges = edges_shmem.as_slice_mut();
|
||||
unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() };
|
||||
|
||||
// We use a HitcountsMapObserver to observe the coverage map
|
||||
let mut edges_observer = unsafe {
|
||||
HitcountsMapObserver::new(ConstMapObserver::from_mut_ptr(
|
||||
"edges",
|
||||
NonNull::new(edges.as_mut_ptr())
|
||||
.expect("The edge map pointer is null.")
|
||||
.cast::<[u8; EDGES_MAP_DEFAULT_SIZE]>(),
|
||||
))
|
||||
};
|
||||
|
||||
#[cfg(feature = "snapshot")]
|
||||
let modules = tuple_list!(
|
||||
StdEdgeCoverageChildModule::builder()
|
||||
.const_map_observer(edges_observer.as_mut())
|
||||
.build()?,
|
||||
SnapshotModule::new()
|
||||
);
|
||||
|
||||
// Create our QEMU emulator
|
||||
let emulator = Emulator::empty()
|
||||
.qemu_parameters(options.args)
|
||||
.modules(modules)
|
||||
.build()?;
|
||||
let qemu = emulator.qemu();
|
||||
|
||||
// Use ELF tools to get the target function
|
||||
let mut elf_buffer = Vec::new();
|
||||
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap();
|
||||
let test_one_input_ptr = elf
|
||||
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
|
||||
.expect("Symbol LLVMFuzzerTestOneInput not found");
|
||||
log::info!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}");
|
||||
|
||||
// Run target until the target function, and store important registers.
|
||||
// Set a breakpoint on target function return.
|
||||
qemu.entry_break(test_one_input_ptr);
|
||||
let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap();
|
||||
let ret_addr: GuestAddr = qemu.read_return_address().unwrap();
|
||||
let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap();
|
||||
log::info!("Break at {pc:#x}");
|
||||
log::info!("Return address = {ret_addr:#x}");
|
||||
qemu.set_breakpoint(ret_addr);
|
||||
|
||||
// Map a private region for input buffer
|
||||
let input_addr = qemu
|
||||
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
|
||||
.unwrap();
|
||||
log::info!("Placing input at {input_addr:#x}");
|
||||
|
||||
// Rust harness: this closure copies an input buffer to our private region
|
||||
// for target function input and updates registers to a single iteration
|
||||
// before telling QEMU to resume execution.
|
||||
#[cfg(feature = "snapshot")]
|
||||
let mut harness =
|
||||
|_emulator: &mut Emulator<_, _, _, _, _, _, _>, _state: &mut _, input: &BytesInput| {
|
||||
let target = input.target_bytes();
|
||||
let mut buf = target.as_slice();
|
||||
let mut len = buf.len();
|
||||
if len > MAX_INPUT_SIZE {
|
||||
buf = &buf[0..MAX_INPUT_SIZE];
|
||||
len = MAX_INPUT_SIZE;
|
||||
}
|
||||
let len = len as GuestReg;
|
||||
|
||||
unsafe {
|
||||
qemu.write_mem(input_addr, buf).expect("qemu write failed.");
|
||||
|
||||
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
|
||||
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
|
||||
qemu.write_return_address(ret_addr).unwrap();
|
||||
qemu.write_function_argument(0, input_addr).unwrap();
|
||||
qemu.write_function_argument(1, len).unwrap();
|
||||
|
||||
match qemu.run() {
|
||||
Ok(QemuExitReason::Breakpoint(_)) => {}
|
||||
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(
|
||||
Signal::SigInterrupt,
|
||||
))) => process::exit(0),
|
||||
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
|
||||
_ => panic!("Unexpected QEMU exit."),
|
||||
}
|
||||
}
|
||||
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
// Set up the most basic monitor possible.
|
||||
let monitor = SimpleMonitor::with_user_monitor(|s| {
|
||||
println!("{s}");
|
||||
});
|
||||
let (state, mut mgr) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider)
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {err}");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Our fuzzer is a simple queue scheduler (FIFO), and has no corpus feedback
|
||||
// or objective feedback. This is important as we need the MaxMapFeedback
|
||||
// on the observer to be constrained by ObserverEqualityFactory which will
|
||||
// ensure interestingness is only true for identical coverage.
|
||||
let scheduler = QueueScheduler::new();
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, (), ());
|
||||
|
||||
// We define the stages that will be performed by the fuzzer. We have one
|
||||
// stage of the StdTMinMutationalStage which will run for n iterations.
|
||||
// The havoc mutator will generate mutations; only those shorter than the
|
||||
// current input will be tested; and the ObservreEqualityFactory will
|
||||
// provide an observer that ensures additions to the corpus have the same
|
||||
// coverage.
|
||||
let minimizer = HavocScheduledMutator::new(havoc_mutations());
|
||||
let factory = ObserverEqualityFactory::new(&edges_observer);
|
||||
let mut stages = tuple_list!(StdTMinMutationalStage::new(
|
||||
minimizer,
|
||||
factory,
|
||||
options.iterations
|
||||
),);
|
||||
|
||||
// Create a state instance. Unlike a typical fuzzer, we start with an empty
|
||||
// input corpus, and we don't care about 'solutions' so store in an
|
||||
// InMemoryCorpus.
|
||||
let mut feedback = MaxMapFeedback::new(&edges_observer);
|
||||
let mut objective = ();
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
StdState::new(
|
||||
StdRand::new(),
|
||||
InMemoryOnDiskCorpus::new(PathBuf::from(options.output)).unwrap(),
|
||||
InMemoryCorpus::new(),
|
||||
&mut feedback,
|
||||
&mut objective,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
// The executor. Nothing exciting here.
|
||||
#[cfg(feature = "snapshot")]
|
||||
let mut executor = QemuExecutor::new(
|
||||
emulator,
|
||||
&mut harness,
|
||||
tuple_list!(edges_observer),
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
Duration::from_millis(5000),
|
||||
)?;
|
||||
|
||||
// Load the input corpus
|
||||
state.load_initial_inputs_by_filenames_forced(&mut fuzzer, &mut executor, &mut mgr, &files)?;
|
||||
log::info!("Processed {} inputs from disk.", files.len());
|
||||
|
||||
// Iterate over initial corpus_ids and minimize each.
|
||||
let corpus_ids: Vec<CorpusId> = state.corpus().ids().collect();
|
||||
for corpus_id in corpus_ids {
|
||||
state.set_corpus_id(corpus_id)?;
|
||||
stages.perform_all(&mut fuzzer, &mut executor, &mut state, &mut mgr)?;
|
||||
}
|
||||
|
||||
// We end up with equivalent output corpus, hopefully smaller than the
|
||||
// input, but certainly no larger.
|
||||
let size = state.corpus().count();
|
||||
println!("Corpus size: {size}");
|
||||
|
||||
mgr.send_exiting()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn main() {
|
||||
fuzz().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn main() {
|
||||
panic!("qemu-user and libafl_qemu is only supported on linux!");
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||
//! A libfuzzer-like fuzzer using Nyx for binary-only coverage
|
||||
#[cfg(target_os = "linux")]
|
||||
mod client;
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||
//! A binary-only systemmode fuzzer using qemu for binary-only coverage
|
||||
#[cfg(all(target_os = "linux", feature = "low_level"))]
|
||||
mod fuzzer_low_level;
|
||||
|
||||
|
@ -574,7 +574,7 @@ where
|
||||
Ok(()) => (),
|
||||
Err(e) => log::error!("Failed to send tcp message {e:#?}"),
|
||||
}
|
||||
log::debug!("Asking he broker to be disconnected");
|
||||
log::debug!("Asking the broker to be disconnected");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* 1.87.0-nightly */
|
||||
/* 1.88.0-nightly */
|
||||
/* qemu git hash: 97bef506eed24ee8d0eda4a07c4419c55dae4acb */
|
||||
/* automatically generated by rust-bindgen 0.71.1 */
|
||||
|
||||
@ -305,6 +305,7 @@ impl Default for __pthread_mutex_s {
|
||||
pub struct __pthread_cond_s {
|
||||
pub __wseq: __atomic_wide_counter,
|
||||
pub __g1_start: __atomic_wide_counter,
|
||||
pub __g_refs: [::std::os::raw::c_uint; 2usize],
|
||||
pub __g_size: [::std::os::raw::c_uint; 2usize],
|
||||
pub __g1_orig_size: ::std::os::raw::c_uint,
|
||||
pub __wrefs: ::std::os::raw::c_uint,
|
||||
@ -312,20 +313,22 @@ pub struct __pthread_cond_s {
|
||||
}
|
||||
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
|
||||
const _: () = {
|
||||
["Size of __pthread_cond_s"][::std::mem::size_of::<__pthread_cond_s>() - 40usize];
|
||||
["Size of __pthread_cond_s"][::std::mem::size_of::<__pthread_cond_s>() - 48usize];
|
||||
["Alignment of __pthread_cond_s"][::std::mem::align_of::<__pthread_cond_s>() - 8usize];
|
||||
["Offset of field: __pthread_cond_s::__wseq"]
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __wseq) - 0usize];
|
||||
["Offset of field: __pthread_cond_s::__g1_start"]
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g1_start) - 8usize];
|
||||
["Offset of field: __pthread_cond_s::__g_refs"]
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g_refs) - 16usize];
|
||||
["Offset of field: __pthread_cond_s::__g_size"]
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g_size) - 16usize];
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g_size) - 24usize];
|
||||
["Offset of field: __pthread_cond_s::__g1_orig_size"]
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g1_orig_size) - 24usize];
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g1_orig_size) - 32usize];
|
||||
["Offset of field: __pthread_cond_s::__wrefs"]
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __wrefs) - 28usize];
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __wrefs) - 36usize];
|
||||
["Offset of field: __pthread_cond_s::__g_signals"]
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g_signals) - 32usize];
|
||||
[::std::mem::offset_of!(__pthread_cond_s, __g_signals) - 40usize];
|
||||
};
|
||||
impl Default for __pthread_cond_s {
|
||||
fn default() -> Self {
|
||||
@ -340,9 +343,10 @@ impl ::std::fmt::Debug for __pthread_cond_s {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"__pthread_cond_s {{ __wseq: {:?}, __g1_start: {:?}, __g_size: {:?}, __g1_orig_size: {:?}, __wrefs: {:?}, __g_signals: {:?} }}",
|
||||
"__pthread_cond_s {{ __wseq: {:?}, __g1_start: {:?}, __g_refs: {:?}, __g_size: {:?}, __g1_orig_size: {:?}, __wrefs: {:?}, __g_signals: {:?} }}",
|
||||
self.__wseq,
|
||||
self.__g1_start,
|
||||
self.__g_refs,
|
||||
self.__g_size,
|
||||
self.__g1_orig_size,
|
||||
self.__wrefs,
|
||||
@ -450,9 +454,7 @@ pub struct _IO_FILE {
|
||||
pub _markers: *mut _IO_marker,
|
||||
pub _chain: *mut _IO_FILE,
|
||||
pub _fileno: ::std::os::raw::c_int,
|
||||
pub _bitfield_align_1: [u32; 0],
|
||||
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 3usize]>,
|
||||
pub _short_backupbuf: [::std::os::raw::c_char; 1usize],
|
||||
pub _flags2: ::std::os::raw::c_int,
|
||||
pub _old_offset: __off_t,
|
||||
pub _cur_column: ::std::os::raw::c_ushort,
|
||||
pub _vtable_offset: ::std::os::raw::c_schar,
|
||||
@ -463,7 +465,7 @@ pub struct _IO_FILE {
|
||||
pub _wide_data: *mut _IO_wide_data,
|
||||
pub _freeres_list: *mut _IO_FILE,
|
||||
pub _freeres_buf: *mut ::std::os::raw::c_void,
|
||||
pub _prevchain: *mut *mut _IO_FILE,
|
||||
pub __pad5: usize,
|
||||
pub _mode: ::std::os::raw::c_int,
|
||||
pub _unused2: [::std::os::raw::c_char; 20usize],
|
||||
}
|
||||
@ -497,8 +499,7 @@ const _: () = {
|
||||
["Offset of field: _IO_FILE::_markers"][::std::mem::offset_of!(_IO_FILE, _markers) - 96usize];
|
||||
["Offset of field: _IO_FILE::_chain"][::std::mem::offset_of!(_IO_FILE, _chain) - 104usize];
|
||||
["Offset of field: _IO_FILE::_fileno"][::std::mem::offset_of!(_IO_FILE, _fileno) - 112usize];
|
||||
["Offset of field: _IO_FILE::_short_backupbuf"]
|
||||
[::std::mem::offset_of!(_IO_FILE, _short_backupbuf) - 119usize];
|
||||
["Offset of field: _IO_FILE::_flags2"][::std::mem::offset_of!(_IO_FILE, _flags2) - 116usize];
|
||||
["Offset of field: _IO_FILE::_old_offset"]
|
||||
[::std::mem::offset_of!(_IO_FILE, _old_offset) - 120usize];
|
||||
["Offset of field: _IO_FILE::_cur_column"]
|
||||
@ -516,8 +517,7 @@ const _: () = {
|
||||
[::std::mem::offset_of!(_IO_FILE, _freeres_list) - 168usize];
|
||||
["Offset of field: _IO_FILE::_freeres_buf"]
|
||||
[::std::mem::offset_of!(_IO_FILE, _freeres_buf) - 176usize];
|
||||
["Offset of field: _IO_FILE::_prevchain"]
|
||||
[::std::mem::offset_of!(_IO_FILE, _prevchain) - 184usize];
|
||||
["Offset of field: _IO_FILE::__pad5"][::std::mem::offset_of!(_IO_FILE, __pad5) - 184usize];
|
||||
["Offset of field: _IO_FILE::_mode"][::std::mem::offset_of!(_IO_FILE, _mode) - 192usize];
|
||||
["Offset of field: _IO_FILE::_unused2"][::std::mem::offset_of!(_IO_FILE, _unused2) - 196usize];
|
||||
};
|
||||
@ -530,50 +530,6 @@ impl Default for _IO_FILE {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl _IO_FILE {
|
||||
#[inline]
|
||||
pub fn _flags2(&self) -> ::std::os::raw::c_int {
|
||||
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 24u8) as u32) }
|
||||
}
|
||||
#[inline]
|
||||
pub fn set__flags2(&mut self, val: ::std::os::raw::c_int) {
|
||||
unsafe {
|
||||
let val: u32 = ::std::mem::transmute(val);
|
||||
self._bitfield_1.set(0usize, 24u8, val as u64)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn _flags2_raw(this: *const Self) -> ::std::os::raw::c_int {
|
||||
unsafe {
|
||||
::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_get(
|
||||
::std::ptr::addr_of!((*this)._bitfield_1),
|
||||
0usize,
|
||||
24u8,
|
||||
) as u32)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn set__flags2_raw(this: *mut Self, val: ::std::os::raw::c_int) {
|
||||
unsafe {
|
||||
let val: u32 = ::std::mem::transmute(val);
|
||||
<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_set(
|
||||
::std::ptr::addr_of_mut!((*this)._bitfield_1),
|
||||
0usize,
|
||||
24u8,
|
||||
val as u64,
|
||||
)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_bitfield_1(_flags2: ::std::os::raw::c_int) -> __BindgenBitfieldUnit<[u8; 3usize]> {
|
||||
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 3usize]> = Default::default();
|
||||
__bindgen_bitfield_unit.set(0usize, 24u8, {
|
||||
let _flags2: u32 = unsafe { ::std::mem::transmute(_flags2) };
|
||||
_flags2 as u64
|
||||
});
|
||||
__bindgen_bitfield_unit
|
||||
}
|
||||
}
|
||||
pub type __jmp_buf = [::std::os::raw::c_long; 8usize];
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
|
@ -661,19 +661,17 @@ impl Default for _IO_FILE {
|
||||
impl _IO_FILE {
|
||||
#[inline]
|
||||
pub fn _flags2(&self) -> ::std::os::raw::c_int {
|
||||
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 24u8) as u32) }
|
||||
u32::cast_signed(self._bitfield_1.get(0usize, 24u8) as u32)
|
||||
}
|
||||
#[inline]
|
||||
pub fn set__flags2(&mut self, val: ::std::os::raw::c_int) {
|
||||
unsafe {
|
||||
let val: u32 = ::std::mem::transmute(val);
|
||||
let val: u32 = i32::cast_unsigned(val);
|
||||
self._bitfield_1.set(0usize, 24u8, val as u64)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn _flags2_raw(this: *const Self) -> ::std::os::raw::c_int {
|
||||
unsafe {
|
||||
::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_get(
|
||||
u32::cast_signed(<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_get(
|
||||
::std::ptr::addr_of!((*this)._bitfield_1),
|
||||
0usize,
|
||||
24u8,
|
||||
@ -683,7 +681,7 @@ impl _IO_FILE {
|
||||
#[inline]
|
||||
pub unsafe fn set__flags2_raw(this: *mut Self, val: ::std::os::raw::c_int) {
|
||||
unsafe {
|
||||
let val: u32 = ::std::mem::transmute(val);
|
||||
let val: u32 = i32::cast_unsigned(val);
|
||||
<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_set(
|
||||
::std::ptr::addr_of_mut!((*this)._bitfield_1),
|
||||
0usize,
|
||||
@ -696,7 +694,7 @@ impl _IO_FILE {
|
||||
pub fn new_bitfield_1(_flags2: ::std::os::raw::c_int) -> __BindgenBitfieldUnit<[u8; 3usize]> {
|
||||
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 3usize]> = Default::default();
|
||||
__bindgen_bitfield_unit.set(0usize, 24u8, {
|
||||
let _flags2: u32 = unsafe { ::std::mem::transmute(_flags2) };
|
||||
let _flags2: u32 = i32::cast_unsigned(_flags2);
|
||||
_flags2 as u64
|
||||
});
|
||||
__bindgen_bitfield_unit
|
||||
|
@ -40,7 +40,7 @@ pub fn debug_print(emu: &Unicorn<()>, thumb_mode: bool) {
|
||||
|
||||
let pc = emu.pc_read().unwrap();
|
||||
|
||||
log::debug!("PC: {:X}", pc);
|
||||
log::debug!("PC: {pc:X}");
|
||||
let arch = emu.get_arch();
|
||||
match arch {
|
||||
Arch::ARM => {
|
||||
@ -132,7 +132,7 @@ pub fn debug_print(emu: &Unicorn<()>, thumb_mode: bool) {
|
||||
}
|
||||
|
||||
for i in insns.as_ref() {
|
||||
log::debug!("{}", i);
|
||||
log::debug!("{i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user