This commit is contained in:
Dominik Maier 2021-03-22 16:16:25 +01:00
commit 15955f0af9
22 changed files with 1158 additions and 183 deletions

View File

@ -10,6 +10,27 @@ env:
CARGO_TERM_COLOR: always
jobs:
lint:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Cache cargo registry
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
key: clippy-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Add clippy
run: rustup component add clippy
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all
ubuntu:
runs-on: ubuntu-latest
steps:

View File

@ -1,4 +1,3 @@
[profile.release]
lto = true
codegen-units = 1
@ -12,6 +11,7 @@ members = [
#example fuzzers
"fuzzers/libfuzzer_libpng",
"fuzzers/frida_libpng",
"fuzzers/libfuzzer_libmozjpeg",
"fuzzers/libfuzzer_libpng_cmpalloc",
]

1
fuzzers/frida_libpng/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
libpng-*

View File

@ -0,0 +1,39 @@
[package]
name = "frida_libpng"
version = "0.1.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["std", "frida"]
std = []
frida = ["frida-gum", "frida-gum-sys"]
#[profile.release]
#lto = true
#codegen-units = 1
#opt-level = 3
#debug = true
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
num_cpus = "1.0"
[dependencies]
libafl = { path = "../../libafl/" }
#frida-gum = { path = "../../../frida-rust/frida-gum", version = "0.2.3", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] }
#frida-gum-sys = { path = "../../../frida-rust/frida-gum-sys", version = "0.2.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] }
frida-gum = { version = "0.3.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] }
frida-gum-sys = { version = "0.2.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] }
lazy_static = "1.4.0"
libc = "0.2"
libloading = "0.7.0"
[[example]]
name = "frida_libpng"
path = "./src/fuzzer.rs"
test = false
bench = false

View File

@ -0,0 +1,25 @@
# Libfuzzer for libpng
This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection.
To show off crash detection, we added a ud2 instruction to the harness, edit harness.cc if you want a non-crashing example.
It has been tested on Linux.
## Build
To build this example, run `cargo build --example libfuzzer_libpng --release`.
This will call (the build.rs)[./builld.rs], which in turn downloads a libpng archive from the web.
Then, it will link (the fuzzer)[./src/fuzzer.rs] against (the C++ harness)[./harness.cc] and the instrumented `libpng`.
Afterwards, the fuzzer will be ready to run, from `../../target/examples/libfuzzer_libpng`.
## Run
The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel.
Each following execution will run a fuzzer client.
As this example uses in-process fuzzing, we added a Restarting Event Manager (`setup_restarting_mgr`).
This means each client will start itself again to listen for crashes and timeouts.
By restarting the actual fuzzer, it can recover from these exit conditions.
In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet).
For convenience, you may just run `./test.sh` in this folder to test it.

View File

@ -0,0 +1,114 @@
// build.rs
use std::{
env,
path::Path,
process::{exit, Command},
};
const LIBPNG_URL: &str =
"https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz";
fn main() {
if cfg!(windows) {
println!("cargo:warning=Skipping libpng example on Windows");
exit(0);
}
let out_dir = env::var_os("OUT_DIR").unwrap();
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
let out_dir = out_dir.to_string_lossy().to_string();
let out_dir_path = Path::new(&out_dir);
std::fs::create_dir_all(&out_dir).expect(&format!("Failed to create {}", &out_dir));
println!("cargo:rerun-if-changed=../libfuzzer_runtime/rt.c",);
println!("cargo:rerun-if-changed=harness.cc");
let libpng = format!("{}/libpng-1.6.37", &out_dir);
let libpng_path = Path::new(&libpng);
let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd);
// Enforce clang for its -fsanitize-coverage support.
std::env::set_var("CC", "clang");
std::env::set_var("CXX", "clang++");
let ldflags = match env::var("LDFLAGS") {
Ok(val) => val,
Err(_) => "".to_string(),
};
// println!("cargo:warning=output path is {}", libpng);
if !libpng_path.is_dir() {
if !Path::new(&libpng_tar).is_file() {
println!("cargo:warning=Libpng not found, downloading...");
// Download libpng
Command::new("wget")
.arg("-c")
.arg(LIBPNG_URL)
.arg("-O")
.arg(&libpng_tar)
.status()
.unwrap();
}
Command::new("tar")
.current_dir(&out_dir_path)
.arg("xvf")
.arg(&libpng_tar)
.status()
.unwrap();
Command::new(format!("{}/configure", &libpng))
.current_dir(&libpng_path)
.args(&[
"--disable-shared",
&format!("--host={}", env::var("TARGET").unwrap())[..],
])
.env("CC", "clang")
.env("CXX", "clang++")
.env(
"CFLAGS",
"-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer",
)
.env(
"CXXFLAGS",
"-O3 -g -D_DEFAULT_SOURCE -fPIC -fno-omit-frame-pointer",
)
.env(
"LDFLAGS",
//format!("-g -fPIE -fsanitize=address {}", ldflags),
format!("-g -fPIE {}", ldflags),
)
.status()
.unwrap();
Command::new("make")
.current_dir(&libpng_path)
.status()
.unwrap();
}
let status = cc::Build::new()
.cpp(true)
.get_compiler()
.to_command()
.current_dir(&cwd)
.arg("-I")
.arg(format!("{}", &libpng))
//.arg("-D")
//.arg("HAS_DUMMY_CRASH=1")
.arg("-fPIC")
.arg("-shared")
.arg(if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" {
"-static-libstdc++"
} else {
""
})
.arg("-o")
.arg(format!("{}/libpng-harness.so", &out_dir))
.arg("./harness.cc")
.arg(format!("{}/.libs/libpng16.a", &libpng))
.arg("-l")
.arg("z")
.status()
.unwrap();
assert!(status.success());
println!("cargo:rerun-if-changed=build.rs");
}

View File

@ -0,0 +1,201 @@
// 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;
extern "C" int afl_libfuzzer_init() {
return 0;
}
// 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
#ifdef __aarch64__
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;
}

View File

@ -0,0 +1,524 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for libpng.
use libafl::{
bolts::{
shmem::UnixShMem,
tuples::{tuple_list, Named},
},
corpus::{
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
QueueCorpusScheduler,
},
events::{setup_restarting_mgr, EventManager},
executors::{inprocess::InProcessExecutor, Executor, ExitKind, HasObservers},
feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
inputs::{HasTargetBytes, Input},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver},
stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State},
stats::SimpleStats,
utils::{current_nanos, StdRand},
Error,
};
#[cfg(target_arch = "x86_64")]
use frida_gum::instruction_writer::X86Register;
#[cfg(target_arch = "aarch64")]
use frida_gum::instruction_writer::{Aarch64Register, IndexMode};
use frida_gum::{
instruction_writer::InstructionWriter,
stalker::{NoneEventSink, Stalker, Transformer},
};
use frida_gum::{Gum, MemoryRange, Module, NativePointer, PageProtection};
use libloading;
use std::{cell::RefCell, env, ffi::c_void, path::PathBuf};
/// An helper that feeds FridaInProcessExecutor with user-supplied instrumentation
pub trait FridaHelper<'a> {
fn transformer(&self) -> &Transformer<'a>;
}
const MAP_SIZE: usize = 64 * 1024;
/// An helper that feeds FridaInProcessExecutor with edge-coverage instrumentation
struct FridaEdgeCoverageHelper<'a> {
map: [u8; MAP_SIZE],
previous_pc: RefCell<u64>,
base_address: u64,
size: usize,
current_log_impl: u64,
/// Transformer that has to be passed to FridaInProcessExecutor
transformer: Option<Transformer<'a>>,
}
impl<'a> FridaHelper<'a> for FridaEdgeCoverageHelper<'a> {
fn transformer(&self) -> &Transformer<'a> {
self.transformer.as_ref().unwrap()
}
}
/// Helper function to get the size of a module's CODE section from frida
pub fn get_module_size(module_name: &str) -> usize {
let mut code_size = 0;
let code_size_ref = &mut code_size;
Module::enumerate_ranges(module_name, PageProtection::ReadExecute, move |details| {
*code_size_ref = details.memory_range().size() as usize;
true
});
code_size
}
/// A minimal maybe_log implementation. We insert this into the transformed instruction stream
/// every time we need a copy that is within a direct branch of the start of the transformed basic
/// block.
#[cfg(target_arch = "x86_64")]
const MAYBE_LOG_CODE: [u8; 69] = [
0x9c, // pushfq
0x50, // push rax
0x51, // push rcx
0x52, // push rdx
0x56, // push rsi
0x89, 0xf8, // mov eax, edi
0xc1, 0xe0, 0x08, // shl eax, 8
0xc1, 0xef, 0x04, // shr edi, 4
0x31, 0xc7, // xor edi, eax
0x0f, 0xb7, 0xc7, // movzx eax, di
0x48, 0x8d, 0x0d, 0x34, 0x00, 0x00, 0x00, // lea rcx, sym._afl_area_ptr_ptr
0x48, 0x8b, 0x09, // mov rcx, qword [rcx]
0x48, 0x8d, 0x15, 0x22, 0x00, 0x00, 0x00, // lea rdx, sym._afl_prev_loc_ptr
0x48, 0x8b, 0x32, // mov rsi, qword [rdx]
0x48, 0x8b, 0x36, // mov rsi, qword [rsi]
0x48, 0x31, 0xc6, // xor rsi, rax
0x48, 0x81, 0xe6, 0xff, 0x1f, 0x00,
0x00, // and rsi, 0x1fff (8 * 1024 - 1) TODO: make this variable
0xfe, 0x04, 0x31, // inc byte [rcx + rsi]
0x48, 0xd1, 0xe8, // shr rax, 1
0x48, 0x8b, 0x0a, // mov rcx, qword [rdx]
0x48, 0x89, 0x01, // mov qword [rcx], rax
0x5e, // pop rsi
0x5a, // pop rdx
0x59, // pop rcx
0x58, // pop rax
0x9d, // popfq
0xc3, // ret
// Read-only data goes here:
// uint64_t* afl_prev_loc_ptr
// uint8_t** afl_area_ptr_ptr
// unsigned int afl_instr_rms
];
#[cfg(target_arch = "aarch64")]
const MAYBE_LOG_CODE: [u8; 104] = [
0xE1, 0x0B, 0xBF, 0xA9, // stp x1, x2, [sp, -0x10]!
0xE3, 0x13, 0xBF, 0xA9, // stp x3, x4, [sp, -0x10]!
0xE1, 0x03, 0x00, 0xAA, // mov x1, x0
0x00, 0xDC, 0x78, 0xD3, // lsl x0, x0, #8
0x21, 0xFC, 0x44, 0xD3, // lsr x1, x1, #4
0x00, 0x00, 0x01, 0xCA, // eor x0, x0, x1
0x00, 0x3C, 0x00, 0x53, // uxth w0, w0
0xa1, 0x02, 0x00, 0x58, // ldr x1, =area_ptr
0x42, 0x02, 0x00, 0x58, // ldr x2, =pc_ptr
0x43, 0x00, 0x40, 0xF9, // ldr x3, [x2]
0x63, 0x00, 0x00, 0xCA, // eor x3, x3, x0
0x63, 0x40, 0x40, 0x92, // and x3, x3, #0x1ffff
0x21, 0x00, 0x03, 0x8B, // add x1, x1, x3
0x24, 0x00, 0x40, 0x39, // ldrb w4, [x1, #0
0x84, 0x04, 0x00, 0x91, // add x4, x4, #1
0x24, 0x00, 0x00, 0x39, // strb w4, [x1, #0]
0x00, 0xFC, 0x41, 0xD3, // lsr x0, x0, #1
0x40, 0x00, 0x00, 0xF9, // str x0, [x2]
0xE3, 0x13, 0xc1, 0xA8, // ldp x3, x4, [sp], #0x10
0xE1, 0x0B, 0xc1, 0xA8, // ldp x1, x2, [sp], #0x10
0xC0, 0x03, 0x5F, 0xD6, // ret
0x1f, 0x20, 0x03, 0xD5, // nop
0x1f, 0x20, 0x03, 0xD5, // nop
0x1f, 0x20, 0x03, 0xD5, // nop
0x1f, 0x20, 0x03, 0xD5, // nop
0x1f, 0x20, 0x03, 0xD5, // nop
];
/// The implementation of the FridaEdgeCoverageHelper
impl<'a> FridaEdgeCoverageHelper<'a> {
/// Constructor function to create a new FridaEdgeCoverageHelper, given a module_name.
pub fn new(gum: &'a Gum, module_name: &str) -> Self {
let mut helper = Self {
map: [0u8; MAP_SIZE],
previous_pc: RefCell::new(0x0),
base_address: Module::find_base_address(module_name).0 as u64,
size: get_module_size(module_name),
current_log_impl: 0,
transformer: None,
};
let transformer = Transformer::from_callback(gum, |basic_block, _output| {
let mut first = true;
for instruction in basic_block {
if first {
first = false;
let address = unsafe { (*instruction.instr()).address };
if address >= helper.base_address
&& address <= helper.base_address + helper.size as u64
{
let writer = _output.writer();
if helper.current_log_impl == 0
|| !writer.can_branch_directly_to(helper.current_log_impl)
|| !writer.can_branch_directly_between(
writer.pc() + 128,
helper.current_log_impl,
)
{
let after_log_impl = writer.code_offset() + 1;
#[cfg(target_arch = "x86_64")]
writer.put_jmp_near_label(after_log_impl);
#[cfg(target_arch = "aarch64")]
writer.put_b_label(after_log_impl);
helper.current_log_impl = writer.pc();
writer.put_bytes(&MAYBE_LOG_CODE);
let prev_loc_pointer = helper.previous_pc.as_ptr() as *mut _ as usize;
let map_pointer = helper.map.as_ptr() as usize;
writer.put_bytes(&prev_loc_pointer.to_ne_bytes());
writer.put_bytes(&map_pointer.to_ne_bytes());
writer.put_label(after_log_impl);
}
#[cfg(target_arch = "x86_64")]
{
println!("here");
writer.put_lea_reg_reg_offset(
X86Register::Rsp,
X86Register::Rsp,
-(frida_gum_sys::GUM_RED_ZONE_SIZE as i32),
);
writer.put_push_reg(X86Register::Rdi);
writer.put_mov_reg_address(X86Register::Rdi, address);
writer.put_call_address(helper.current_log_impl);
writer.put_pop_reg(X86Register::Rdi);
writer.put_lea_reg_reg_offset(
X86Register::Rsp,
X86Register::Rsp,
frida_gum_sys::GUM_RED_ZONE_SIZE as i32,
);
}
#[cfg(target_arch = "aarch64")]
{
writer.put_stp_reg_reg_reg_offset(
Aarch64Register::Lr,
Aarch64Register::X0,
Aarch64Register::Sp,
-(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64,
IndexMode::PreAdjust,
);
writer.put_ldr_reg_u64(Aarch64Register::X0, address);
writer.put_bl_imm(helper.current_log_impl);
writer.put_ldp_reg_reg_reg_offset(
Aarch64Register::Lr,
Aarch64Register::X0,
Aarch64Register::Sp,
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64,
IndexMode::PostAdjust,
);
}
}
}
instruction.keep()
}
});
helper.transformer = Some(transformer);
helper
}
}
struct FridaInProcessExecutor<'a, FH, H, I, OT>
where
FH: FridaHelper<'a>,
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
base: InProcessExecutor<'a, H, I, OT>,
/// Frida's dynamic rewriting engine
stalker: Stalker<'a>,
/// User provided callback for instrumentation
helper: &'a FH,
followed: bool,
}
impl<'a, FH, H, I, OT> Executor<I> for FridaInProcessExecutor<'a, FH, H, I, OT>
where
FH: FridaHelper<'a>,
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
/// Called right before exexution starts
#[inline]
fn pre_exec<EM, S>(&mut self, state: &mut S, event_mgr: &mut EM, input: &I) -> Result<(), Error>
where
EM: EventManager<I, S>,
{
if !self.followed {
self.followed = true;
self.stalker
.follow_me::<NoneEventSink>(self.helper.transformer(), None);
} else {
self.stalker.activate(NativePointer(
self.base.harness_mut() as *mut _ as *mut c_void
))
}
self.base.pre_exec(state, event_mgr, input)
}
/// Instruct the target about the input and run
#[inline]
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
self.base.run_target(input)
}
/// Called right after execution finished.
#[inline]
fn post_exec<EM, S>(
&mut self,
state: &mut S,
event_mgr: &mut EM,
input: &I,
) -> Result<(), Error>
where
EM: EventManager<I, S>,
{
self.stalker.deactivate();
self.base.post_exec(state, event_mgr, input)
}
}
impl<'a, FH, H, I, OT> HasObservers<OT> for FridaInProcessExecutor<'a, FH, H, I, OT>
where
FH: FridaHelper<'a>,
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
#[inline]
fn observers(&self) -> &OT {
self.base.observers()
}
#[inline]
fn observers_mut(&mut self) -> &mut OT {
self.base.observers_mut()
}
}
impl<'a, FH, H, I, OT> Named for FridaInProcessExecutor<'a, FH, H, I, OT>
where
FH: FridaHelper<'a>,
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
fn name(&self) -> &str {
self.base.name()
}
}
impl<'a, FH, H, I, OT> FridaInProcessExecutor<'a, FH, H, I, OT>
where
FH: FridaHelper<'a>,
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
pub fn new(gum: &'a Gum, base: InProcessExecutor<'a, H, I, OT>, helper: &'a FH) -> Self {
let mut stalker = Stalker::new(gum);
// Let's exclude the main module and libc.so at least:
stalker.exclude(&MemoryRange::new(
Module::find_base_address(&env::args().next().unwrap()),
get_module_size(&env::args().next().unwrap()),
));
stalker.exclude(&MemoryRange::new(
Module::find_base_address("libc.so"),
get_module_size("libc.so"),
));
Self {
base: base,
stalker: stalker,
helper: helper,
followed: false,
}
}
}
/// The main fn, usually parsing parameters, and starting the fuzzer
pub fn main() {
// Registry the metadata types used in this fuzzer
// Needed only on no_std
//RegistryBuilder::register::<Tokens>();
println!(
"Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string()
);
unsafe {
fuzz(
&env::args().nth(1).expect("no module specified"),
&env::args().nth(2).expect("no symbol specified"),
vec![PathBuf::from("./corpus")],
PathBuf::from("./crashes"),
1337,
)
.expect("An error occurred while fuzzing");
}
}
/// Not supported on windows right now
#[cfg(windows)]
fn fuzz(
_module_name: &str,
_symbol_name: &str,
_corpus_dirs: Vec<PathBuf>,
_objective_dir: PathBuf,
_broker_port: u16,
) -> Result<(), ()> {
todo!("Example not supported on Windows");
}
/// The actual fuzzer
#[cfg(unix)]
unsafe fn fuzz(
module_name: &str,
symbol_name: &str,
corpus_dirs: Vec<PathBuf>,
objective_dir: PathBuf,
broker_port: u16,
) -> Result<(), Error> {
// 'While the stats are state, they are usually used in the broker - which is likely never restarted
let stats = SimpleStats::new(|s| println!("{}", s));
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
let (state, mut restarting_mgr) =
match setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) {
Ok(res) => res,
Err(err) => match err {
Error::ShuttingDown => {
return Ok(());
}
_ => {
panic!("Failed to setup the restarter: {}", err);
}
},
};
let gum = Gum::obtain();
let lib = libloading::Library::new(module_name).unwrap();
let target_func: libloading::Symbol<unsafe extern "C" fn(data: *const u8, size: usize) -> i32> =
lib.get(symbol_name.as_bytes()).unwrap();
let mut frida_helper = FridaEdgeCoverageHelper::new(&gum, module_name);
// Create an observation channel using the coverage map
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr(
"edges",
frida_helper.map.as_mut_ptr(),
MAP_SIZE,
));
let mut frida_harness = move |buf: &[u8]| {
(target_func)(buf.as_ptr(), buf.len());
ExitKind::Ok
};
// If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| {
State::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Feedbacks to rate the interestingness of an input
tuple_list!(MaxMapFeedback::new_with_observer_track(
&edges_observer,
true,
false
)),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(objective_dir).unwrap(),
// Feedbacks to recognize an input as solution
tuple_list!(CrashFeedback::new()),
)
});
println!("We're a client, let's fuzz :)");
// Create a PNG dictionary if not existing
if state.metadata().get::<Tokens>().is_none() {
state.add_metadata(Tokens::new(vec![
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
}
// Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default();
let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage));
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new(
&gum,
InProcessExecutor::new(
"in-process(edges)",
&mut frida_harness,
tuple_list!(edges_observer),
&mut state,
&mut restarting_mgr,
)?,
&frida_helper,
);
// Let's exclude the main module and libc.so at least:
executor.stalker.exclude(&MemoryRange::new(
Module::find_base_address(&env::args().next().unwrap()),
get_module_size(&env::args().next().unwrap()),
));
executor.stalker.exclude(&MemoryRange::new(
Module::find_base_address("libc.so"),
get_module_size("libc.so"),
));
// In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 {
state
.load_initial_inputs(
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!(
"Failed to load initial corpus at {:?}",
&corpus_dirs
));
println!("We imported {} inputs from disk.", state.corpus().count());
}
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?;
// Never reached
Ok(())
}

View File

@ -15,7 +15,7 @@ fn main() {
}
let out_dir = env::var_os("OUT_DIR").unwrap();
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
//let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
let out_dir = out_dir.to_string_lossy().to_string();
let out_dir_path = Path::new(&out_dir);
@ -24,7 +24,7 @@ fn main() {
let libmozjpeg = format!("{}/mozjpeg-4.0.3", &out_dir);
let libmozjpeg_path = Path::new(&libmozjpeg);
let libmozjpeg_tar = format!("{}/v4.0.3.tar.gz", &cwd);
let libmozjpeg_tar = format!("{}/v4.0.3.tar.gz", &out_dir);
// Enforce clang for its -fsanitize-coverage support.
std::env::set_var("CC", "clang");
@ -48,16 +48,13 @@ fn main() {
.arg(&libmozjpeg_tar)
.status()
.unwrap();
Command::new(format!("{}/cmake", &libmozjpeg))
.current_dir(&out_dir_path)
.args(&[
"-G\"Unix Makefiles\"",
"--disable-shared",
&libmozjpeg,
"CC=clang",
"CFLAGS=-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
"LDFLAGS=-g -fPIE -fsanitize-coverage=trace-pc-guard",
])
//println!("cargo:warning=Running cmake on {}", &libmozjpeg);
Command::new("cmake")
.current_dir(&libmozjpeg_path)
.args(&["-G", "Unix Makefiles", "--disable-shared"])
.arg(&libmozjpeg)
.env("OPT_LEVEL", "3")
.env("CC", "clang")
.env("CXX", "clang++")
.env(
@ -68,13 +65,17 @@ fn main() {
"CXXFLAGS",
"-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
)
.env("LDFLAGS", "-g -fPIE -fsanitize-coverage=trace-pc-guard");
.env("LDFLAGS", "-g -fPIE -fsanitize-coverage=trace-pc-guard")
.status()
.unwrap();
Command::new("make")
.current_dir(&libmozjpeg_path)
//.arg(&format!("-j{}", num_cpus::get()))
.args(&[
"CC=clang",
"CXX=clang++",
"OPT_LEVEL=3",
"CFLAGS=-O3 -g -D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",
"LDFLAGS=-g -fPIE -fsanitize-coverage=trace-pc-guard",
"CXXFLAGS=-D_DEFAULT_SOURCE -fPIE -fsanitize-coverage=trace-pc-guard",

View File

@ -8,10 +8,9 @@ use libafl::{
bolts::{shmem::UnixShMem, tuples::tuple_list},
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler},
events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, Executor, ExitKind},
executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
inputs::Input,
mutators::scheduled::HavocBytesMutator,
mutators::token_mutations::Tokens,
observers::StdMapObserver,
@ -36,20 +35,6 @@ extern "C" {
static __lafl_max_edges_size: u32;
}
/// The wrapped harness function, calling out to the LLVM-style harness
#[cfg(unix)]
fn harness<E, I>(_executor: &E, buf: &[u8]) -> ExitKind
where
E: Executor<I>,
I: Input,
{
// println!("{:?}", buf);
unsafe {
LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len());
}
ExitKind::Ok
}
/// The main fn, usually parsing parameters, and starting the fuzzer
pub fn main() {
// Registry the metadata types used in this fuzzer
@ -121,10 +106,16 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// A fuzzer with just one stage and a random policy to get testcasess from the corpus
let fuzzer = StdFuzzer::new(RandCorpusScheduler::new(), tuple_list!(stage));
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| {
unsafe { LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()) };
ExitKind::Ok
};
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = InProcessExecutor::new(
"in-process(edges)",
harness,
&mut harness,
tuple_list!(edges_observer),
&mut state,
&mut restarting_mgr,

View File

@ -12,10 +12,9 @@ use libafl::{
QueueCorpusScheduler,
},
events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, Executor, ExitKind, TimeoutExecutor},
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
inputs::Input,
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage,
@ -39,20 +38,6 @@ extern "C" {
static __lafl_max_edges_size: u32;
}
/// The wrapped harness function, calling out to the LLVM-style harness
#[cfg(unix)]
fn harness<E, I>(_executor: &E, buf: &[u8]) -> ExitKind
where
E: Executor<I>,
I: Input,
{
// println!("{:?}", buf);
unsafe {
LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len());
}
ExitKind::Ok
}
/// The main fn, usually parsing parameters, and starting the fuzzer
pub fn main() {
// Registry the metadata types used in this fuzzer
@ -143,11 +128,17 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage));
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| {
unsafe { LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()) };
ExitKind::Ok
};
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = TimeoutExecutor::new(
InProcessExecutor::new(
"in-process(edges)",
harness,
&mut harness,
tuple_list!(edges_observer, TimeObserver::new("time")),
&mut state,
&mut restarting_mgr,

View File

@ -16,7 +16,6 @@ fn main() {
}
let out_dir = env::var_os("OUT_DIR").unwrap();
let cwd = env::current_dir().unwrap().to_string_lossy().to_string();
let out_dir = out_dir.to_string_lossy().to_string();
let out_dir_path = Path::new(&out_dir);
@ -25,7 +24,7 @@ fn main() {
let libpng = format!("{}/libpng-1.6.37", &out_dir);
let libpng_path = Path::new(&libpng);
let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &cwd);
let libpng_tar = format!("{}/libpng-1.6.37.tar.xz", &out_dir);
// Enforce clang for its -fsanitize-coverage support.
std::env::set_var("CC", "clang");

View File

@ -11,10 +11,9 @@ use libafl::{
QueueCorpusScheduler,
},
events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, Executor, ExitKind},
executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
inputs::Input,
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage,
@ -41,20 +40,6 @@ extern "C" {
static __lafl_max_edges_size: u32;
}
/// The wrapped harness function, calling out to the LLVM-style harness
#[cfg(unix)]
fn harness<E, I>(_executor: &E, buf: &[u8]) -> ExitKind
where
E: Executor<I>,
I: Input,
{
// println!("{:?}", buf);
unsafe {
LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len());
}
ExitKind::Ok
}
/// The main fn, usually parsing parameters, and starting the fuzzer
pub fn main() {
// Registry the metadata types used in this fuzzer
@ -154,10 +139,16 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage));
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| {
unsafe { LLVMFuzzerTestOneInput(buf.as_ptr(), buf.len()) };
ExitKind::Ok
};
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = InProcessExecutor::new(
"in-process(edges,cmps,allocs)",
harness,
&mut harness,
tuple_list!(
edges_observer,
cmps_observer,

View File

@ -89,10 +89,10 @@ use std::{
},
};
#[cfg(all(feature = "std", unix))]
#[cfg(all(unix, feature = "std"))]
use libc::c_char;
#[cfg(unix)]
#[cfg(all(unix, feature = "std"))]
use uds::{UnixListenerExt, UnixSocketAddr, UnixStreamExt};
#[cfg(unix)]
@ -966,7 +966,7 @@ where
unsafe fn recv(&mut self) -> Result<Option<*mut LlmpMsg>, Error> {
/* DBG("recv %p %p\n", page, last_msg); */
compiler_fence(Ordering::SeqCst);
let page = self.current_recv_map.page_mut();
let mut page = self.current_recv_map.page_mut();
let last_msg = self.last_msg_recvd;
let current_msg_id = ptr::read_volatile(&(*page).current_msg_id);
@ -1004,7 +1004,7 @@ where
}
LLMP_TAG_END_OF_PAGE => {
#[cfg(feature = "std")]
dbg!("Got end of page, allocing next");
println!("Received end of page, allocating next");
// Handle end of page
if (*msg).buf_len < size_of::<LlmpPayloadSharedMapInfo>() as u64 {
panic!(
@ -1015,18 +1015,19 @@ where
}
let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo;
/* We can reuse the map mem space, no need to free and calloc.
However, the pageinfo points to the map we're about to unmap.
Clone the contents first to be safe (probably fine in rust eitner way). */
let pageinfo_cpy = (*pageinfo).clone();
/* The pageinfo points to the map we're about to unmap.
Copy the contents first to be safe (probably fine in rust either way). */
let pageinfo_cpy = *pageinfo;
// Mark the old page save to unmap, in case we didn't so earlier.
ptr::write_volatile(&mut (*page).save_to_unmap, 1);
// Map the new page. The old one should be unmapped by Drop
self.current_recv_map = LlmpSharedMap::existing(SH::existing_from_shm_slice(
&pageinfo_cpy.shm_str,
pageinfo_cpy.map_size,
)?);
page = self.current_recv_map.page_mut();
// Mark the new page save to unmap also (it's mapped by us, the broker now)
ptr::write_volatile(&mut (*page).save_to_unmap, 1);
@ -1434,7 +1435,9 @@ where
// to read from the initial map id.
let client_out_map_mem = &self.llmp_out.out_maps.first().unwrap().shmem;
let broadcast_str_initial = client_out_map_mem.shm_slice().clone();
let broadcast_map_description = postcard::to_allocvec(&client_out_map_mem.description())?;
let mut incoming_map_description_serialized = vec![0u8; broadcast_map_description.len()];
let llmp_tcp_id = self.llmp_clients.len() as u32;
@ -1462,33 +1465,37 @@ where
match listener.accept() {
ListenerStream::Tcp(mut stream, addr) => {
dbg!("New connection", addr, stream.peer_addr().unwrap());
match stream.write(&broadcast_str_initial) {
match stream.write(&broadcast_map_description) {
Ok(_) => {} // fire & forget
Err(e) => {
dbg!("Could not send to shmap to client", e);
continue;
}
};
let mut new_client_map_str: [u8; 20] = Default::default();
match stream.read_exact(&mut new_client_map_str) {
match stream.read_exact(&mut incoming_map_description_serialized) {
Ok(()) => (),
Err(e) => {
dbg!("Ignoring failed read from client", e);
continue;
}
};
unsafe {
let msg = new_client_sender
.alloc_next(size_of::<LlmpPayloadSharedMapInfo>())
.expect("Could not allocate a new message in shared map.");
(*msg).tag = LLMP_TAG_NEW_SHM_CLIENT;
let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo;
(*pageinfo).shm_str = new_client_map_str;
(*pageinfo).map_size = LLMP_CFG_INITIAL_MAP_SIZE;
match new_client_sender.send(msg) {
Ok(()) => (),
Err(e) => println!("Error forwarding client on map: {:?}", e),
};
if let Ok(incoming_map_description) = postcard::from_bytes::<ShMemDescription>(
&incoming_map_description_serialized,
) {
unsafe {
let msg = new_client_sender
.alloc_next(size_of::<LlmpPayloadSharedMapInfo>())
.expect("Could not allocate a new message in shared map.");
(*msg).tag = LLMP_TAG_NEW_SHM_CLIENT;
let pageinfo =
(*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo;
(*pageinfo).shm_str = incoming_map_description.str_bytes;
(*pageinfo).map_size = incoming_map_description.size;
match new_client_sender.send(msg) {
Ok(()) => (),
Err(e) => println!("Error forwarding client on map: {:?}", e),
};
}
}
}
#[cfg(unix)]
@ -1496,14 +1503,14 @@ where
dbg!("New connection", addr);
let broadcast_fd_initial: i32 =
CStr::from_ptr(broadcast_str_initial.as_ptr() as *const c_char)
CStr::from_ptr(broadcast_map_description.as_ptr() as *const c_char)
.to_string_lossy()
.into_owned()
.parse()
.unwrap_or_else(|_| {
panic!(
"ShmId is not a valid int file descriptor: {:?}",
broadcast_str_initial
broadcast_map_description
)
});
@ -1856,15 +1863,24 @@ where
let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port))?;
println!("Connected to port {}", port);
let mut new_broker_map_str: [u8; 20] = Default::default();
// First, get the serialized description size by serializing a dummy.
let dummy_description = ShMemDescription {
size: 0,
str_bytes: Default::default(),
};
let mut new_broker_map_str = postcard::to_allocvec(&dummy_description)?;
stream.read_exact(&mut new_broker_map_str)?;
let ret = Self::new(LlmpSharedMap::existing(SH::existing_from_shm_slice(
&new_broker_map_str,
LLMP_CFG_INITIAL_MAP_SIZE,
let broker_map_description: ShMemDescription = postcard::from_bytes(&new_broker_map_str)?;
let ret = Self::new(LlmpSharedMap::existing(SH::existing_from_description(
&broker_map_description,
)?))?;
stream.write_all(ret.sender.out_maps.first().unwrap().shmem.shm_slice())?;
let own_map_description_bytes =
postcard::to_allocvec(&ret.sender.out_maps.first().unwrap().shmem.description())?;
stream.write_all(&own_map_description_bytes)?;
Ok(ret)
}
}

View File

@ -20,9 +20,9 @@ use crate::Error;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct ShMemDescription {
/// Size of this map
size: usize,
pub size: usize,
/// of name of this map, as fixed 20 bytes c-string
str_bytes: [u8; 20],
pub str_bytes: [u8; 20],
}
/// A Shared map
@ -370,7 +370,7 @@ pub mod unix_shmem {
// Not set or not initialized;
return;
}
(*shm).shm_str[0 as usize] = 0u8;
(*shm).shm_str[0_usize] = 0u8;
shmctl((*shm).shm_id, 0 as c_int, ptr::null_mut());
(*shm).map = ptr::null_mut();
}

View File

@ -25,26 +25,25 @@ use crate::{
Error,
};
/// The inmem executor harness
type HarnessFunction<E> = fn(&E, &[u8]) -> ExitKind;
/// The inmem executor simply calls a target function, then returns afterwards.
pub struct InProcessExecutor<I, OT>
pub struct InProcessExecutor<'a, H, I, OT>
where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
/// The name of this executor instance, to address it from other components
name: &'static str,
/// The harness function, being executed for each fuzzing loop execution
harness_fn: HarnessFunction<Self>,
harness_fn: &'a mut H,
/// The observers, observing each run
observers: OT,
phantom: PhantomData<I>,
}
impl<I, OT> Executor<I> for InProcessExecutor<I, OT>
impl<'a, H, I, OT> Executor<I> for InProcessExecutor<'a, H, I, OT>
where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
@ -95,7 +94,7 @@ where
#[inline]
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
let bytes = input.target_bytes();
let ret = (self.harness_fn)(self, bytes.as_slice());
let ret = (self.harness_fn)(bytes.as_slice());
Ok(ret)
}
@ -126,8 +125,9 @@ where
}
}
impl<I, OT> Named for InProcessExecutor<I, OT>
impl<'a, H, I, OT> Named for InProcessExecutor<'a, H, I, OT>
where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
@ -136,8 +136,9 @@ where
}
}
impl<I, OT> HasObservers<OT> for InProcessExecutor<I, OT>
impl<'a, H, I, OT> HasObservers<OT> for InProcessExecutor<'a, H, I, OT>
where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
@ -152,8 +153,9 @@ where
}
}
impl<I, OT> InProcessExecutor<I, OT>
impl<'a, H, I, OT> InProcessExecutor<'a, H, I, OT>
where
H: FnMut(&[u8]) -> ExitKind,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
@ -166,7 +168,7 @@ where
/// This may return an error on unix, if signal handler setup fails
pub fn new<EM, OC, OFT, S>(
name: &'static str,
harness_fn: HarnessFunction<Self>,
harness_fn: &'a mut H,
observers: OT,
_state: &mut S,
_event_mgr: &mut EM,
@ -215,6 +217,18 @@ where
phantom: PhantomData,
})
}
/// Retrieve the harness function.
#[inline]
pub fn harness(&self) -> &H {
self.harness_fn
}
/// Retrieve the harness function for a mutable reference.
#[inline]
pub fn harness_mut(&mut self) -> &mut H {
self.harness_fn
}
}
#[cfg(unix)]
@ -223,10 +237,7 @@ mod unix_signal_handler {
use core::ptr;
use libc::{c_void, siginfo_t};
#[cfg(feature = "std")]
use std::{
fs,
io::{stdout, Write},
};
use std::io::{stdout, Write};
use crate::{
bolts::os::unix_signals::{Handler, Signal},
@ -443,7 +454,7 @@ mod unix_signal_handler {
}
// let's yolo-cat the maps for debugging, if possible.
#[cfg(all(target_os = "linux", feature = "std"))]
match fs::read_to_string("/proc/self/maps") {
match std::fs::read_to_string("/proc/self/maps") {
Ok(maps) => println!("maps:\n{}", maps),
Err(e) => println!("Couldn't load mappings: {:?}", e),
};
@ -630,19 +641,15 @@ mod tests {
use crate::{
bolts::tuples::tuple_list,
executors::{Executor, ExitKind, InProcessExecutor},
inputs::Input,
inputs::NopInput,
};
fn test_harness_fn_nop<E: Executor<I>, I: Input>(_executor: &E, _buf: &[u8]) -> ExitKind {
ExitKind::Ok
}
#[test]
fn test_inmem_exec() {
use crate::inputs::NopInput;
let mut harness = |_buf: &[u8]| ExitKind::Ok;
let mut in_process_executor = InProcessExecutor::<NopInput, ()> {
harness_fn: test_harness_fn_nop,
let mut in_process_executor = InProcessExecutor::<_, NopInput, ()> {
harness_fn: &mut harness,
observers: tuple_list!(),
name: "main",
phantom: PhantomData,

View File

@ -6,10 +6,15 @@ use crate::{
observers::ObserversTuple,
stages::StagesTuple,
state::HasExecutions,
utils::{current_milliseconds, current_time},
utils::current_time,
Error,
};
use core::marker::PhantomData;
use alloc::string::ToString;
use core::{marker::PhantomData, time::Duration};
/// Send a stats update all 6 (or more) seconds
const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_millis(6 * 1000);
/// Holds a set of stages
pub trait HasStages<CS, E, EM, I, S, ST>
@ -38,9 +43,55 @@ where
/// The main fuzzer trait.
pub trait Fuzzer<E, EM, S> {
/// Fuzz for a single iteration
/// Returns the index of the last fuzzed corpus item
fn fuzz_one(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<usize, Error>;
fn fuzz_loop(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<usize, Error>;
/// Fuzz forever (or until stopped)
fn fuzz_loop(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<(), Error> {
let mut last = current_time();
let stats_timeout = STATS_TIMEOUT_DEFAULT;
loop {
self.fuzz_one(state, executor, manager)?;
last = Self::maybe_report_stats(state, manager, last, stats_timeout)?;
}
}
/// Fuzz for n iterations
/// Returns the index of the last fuzzed corpus item
fn fuzz_loop_for(
&self,
state: &mut S,
executor: &mut E,
manager: &mut EM,
iters: u64,
) -> Result<usize, Error> {
if iters == 0 {
return Err(Error::IllegalArgument(
"Cannot fuzz for 0 iterations!".to_string(),
));
}
let mut ret = 0;
let mut last = current_time();
let stats_timeout = STATS_TIMEOUT_DEFAULT;
for _ in 0..iters {
ret = self.fuzz_one(state, executor, manager)?;
last = Self::maybe_report_stats(state, manager, last, stats_timeout)?;
}
Ok(ret)
}
/// Given the last time, if stats_timeout seconds passed, send off an info/stats/heartbeat message to the broker.
/// Returns the new `last` time (so the old one, unless `stats_timeout` time has passed and stats have been sent)
/// Will return an Error, if the stats could not be sent.
fn maybe_report_stats(
state: &mut S,
manager: &mut EM,
last: Duration,
stats_timeout: Duration,
) -> Result<Duration, Error>;
}
/// Your default fuzzer instance, for everyday use.
@ -102,6 +153,31 @@ where
OT: ObserversTuple,
I: Input,
{
#[inline]
fn maybe_report_stats(
state: &mut S,
manager: &mut EM,
last: Duration,
stats_timeout: Duration,
) -> Result<Duration, Error> {
let cur = current_time();
if cur - last > stats_timeout {
//println!("Fire {:?} {:?} {:?}", cur, last, stats_timeout);
manager.fire(
state,
Event::UpdateStats {
executions: *state.executions(),
time: cur,
phantom: PhantomData,
},
)?;
Ok(cur)
} else {
if cur.as_millis() % 1000 == 0 {}
Ok(last)
}
}
fn fuzz_one(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<usize, Error> {
let idx = self.scheduler().next(state)?;
@ -111,25 +187,6 @@ where
manager.process(state, executor, self.scheduler())?;
Ok(idx)
}
fn fuzz_loop(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<usize, Error> {
let mut last = current_milliseconds();
loop {
self.fuzz_one(state, executor, manager)?;
let cur = current_milliseconds();
if cur - last > 60 * 100 {
last = cur;
manager.fire(
state,
Event::UpdateStats {
executions: *state.executions(),
time: current_time(),
phantom: PhantomData,
},
)?
}
}
}
}
impl<CS, ST, E, EM, I, OT, S> StdFuzzer<CS, ST, E, EM, I, OT, S>

View File

@ -137,8 +137,8 @@ mod tests {
use crate::{
bolts::tuples::tuple_list,
corpus::{Corpus, InMemoryCorpus, RandCorpusScheduler, Testcase},
executors::{Executor, ExitKind, InProcessExecutor},
inputs::{BytesInput, Input},
executors::{ExitKind, InProcessExecutor},
inputs::BytesInput,
mutators::{mutation_bitflip, ComposedByMutations, StdScheduledMutator},
stages::StdMutationalStage,
state::{HasCorpus, State},
@ -150,10 +150,6 @@ mod tests {
#[cfg(feature = "std")]
use crate::events::SimpleEventManager;
fn harness<E: Executor<I>, I: Input>(_executor: &E, _buf: &[u8]) -> ExitKind {
ExitKind::Ok
}
#[test]
fn test_fuzzer() {
let rand = StdRand::with_seed(0);
@ -175,9 +171,10 @@ mod tests {
});
let mut event_manager = SimpleEventManager::new(stats);
let mut harness = |_buf: &[u8]| ExitKind::Ok;
let mut executor = InProcessExecutor::new(
"main",
harness,
&mut harness,
tuple_list!(),
//Box::new(|_, _, _, _, _| ()),
&mut state,

View File

@ -752,13 +752,13 @@ where
// Converts a hex u8 to its u8 value: 'A' -> 10 etc.
fn from_hex(hex: u8) -> Result<u8, Error> {
if hex >= 48 && hex <= 57 {
if (48..=57).contains(&hex) {
return Ok(hex - 48);
}
if hex >= 65 && hex <= 70 {
if (65..=70).contains(&hex) {
return Ok(hex - 55);
}
if hex >= 97 && hex <= 102 {
if (97..=102).contains(&hex) {
return Ok(hex - 87);
}
Err(Error::IllegalArgument("".to_owned()))
@ -781,17 +781,15 @@ pub fn str_decode(item: &str) -> Result<Vec<u8>, Error> {
decoded += from_hex(c)?;
token.push(decoded);
take_next_two = 0;
} else {
if c != backslash || take_next {
if take_next && (c == 120 || c == 88) {
take_next_two = 1;
} else {
token.push(c);
}
take_next = false;
} else if c != backslash || take_next {
if take_next && (c == 120 || c == 88) {
take_next_two = 1;
} else {
take_next = true;
token.push(c);
}
take_next = false;
} else {
take_next = true;
}
}

View File

@ -206,6 +206,7 @@ mod tests {
#[cfg(feature = "std")]
use std::fs;
#[cfg(feature = "std")]
use super::Tokens;
#[cfg(feature = "std")]

View File

@ -1,4 +1,7 @@
use alloc::string::{String, ToString};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use serde::{Deserialize, Serialize};
use crate::{
@ -126,6 +129,16 @@ where
}
}
/// Creates a new MapObserver with an owned map
pub fn new_owned(name: &'static str, map: Vec<T>) -> Self {
let initial = if map.is_empty() { T::default() } else { map[0] };
Self {
map: ArrayMut::Owned(map),
name: name.to_string(),
initial,
}
}
/// Creates a new MapObserver from a raw pointer
/// # Safety
/// Will dereference the map_ptr with up to len elements.

View File

@ -160,28 +160,16 @@ pub fn current_time() -> time::Duration {
time::Duration::from_millis(1)
}
#[cfg(feature = "std")]
#[inline]
/// Gets current nanoseconds since UNIX_EPOCH
#[inline]
pub fn current_nanos() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
current_time().as_nanos() as u64
}
#[cfg(feature = "std")]
/// Gets current milliseconds since UNIX_EPOCH
#[inline]
pub fn current_milliseconds() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}
#[cfg(not(feature = "std"))]
pub fn current_milliseconds() -> u64 {
1000
current_time().as_millis() as u64
}
/// XXH3 Based, hopefully speedy, rnd implementation