Implement coverage accounting (BB metric atm) (#507)

* bb accounting llvm pass

* bb metric

* accoutning corpus scheduler

* fix warnings

* alloc

* clippy

* fix dockerfile

* clippy

* coverage accounting example

* finish CoverageAccountingCorpusScheduler

* fmt

* --libs in llvm-config

* merge
This commit is contained in:
Andrea Fioraldi 2022-02-01 14:08:38 +01:00 committed by GitHub
parent 6810e6085b
commit dd002a081b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1356 additions and 85 deletions

View File

@ -43,9 +43,9 @@ COPY libafl_sugar/Cargo.toml libafl_sugar/
COPY scripts/dummy.rs libafl_sugar/src/lib.rs COPY scripts/dummy.rs libafl_sugar/src/lib.rs
COPY libafl_cc/Cargo.toml libafl_cc/Cargo.toml COPY libafl_cc/Cargo.toml libafl_cc/Cargo.toml
COPY scripts/dummy.rs libafl_cc/src/lib.rs
COPY libafl_cc/build.rs libafl_cc/build.rs COPY libafl_cc/build.rs libafl_cc/build.rs
COPY libafl_cc/src/cmplog-routines-pass.cc libafl_cc/src/cmplog-routines-pass.cc COPY libafl_cc/src libafl_cc/src
COPY scripts/dummy.rs libafl_cc/src/lib.rs
COPY libafl_targets/Cargo.toml libafl_targets/build.rs libafl_targets/ COPY libafl_targets/Cargo.toml libafl_targets/build.rs libafl_targets/
COPY libafl_targets/src libafl_targets/src COPY libafl_targets/src libafl_targets/src

View File

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

View File

@ -0,0 +1,32 @@
[package]
name = "libfuzzer_libpng_launcher"
version = "0.7.1"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2021"
[features]
default = ["std"]
std = []
[profile.release]
lto = true
codegen-units = 1
opt-level = 3
debug = true
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
which = { version = "4.0.2" }
num_cpus = "1.0"
[dependencies]
libafl = { path = "../../libafl/", features = ["std", "derive", "llmp_compression", "introspection"] }
libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts", "libfuzzer"] }
# TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" }
clap = { version = "3.0", features = ["derive"] }
mimalloc = { version = "*", default-features = false }
[lib]
name = "libfuzzer_libpng"
crate-type = ["staticlib"]

View File

@ -0,0 +1,50 @@
FUZZER_NAME="fuzzer_libpng"
PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
UNAME := $(shell uname)
PHONY: all
all: fuzzer
libpng-1.6.37:
wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar -xvf libpng-1.6.37.tar.xz
target/release/libafl_cxx: src/* src/bin/*
# Build the libpng libfuzzer library
cargo build --release
libafl_cxx: target/release/libafl_cxx
libafl_cc: target/release/libafl_cxx
libpng-1.6.37/.libs/libpng16.a: libpng-1.6.37 libafl_cc
cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes
$(MAKE) -C libpng-1.6.37 CC="$(PROJECT_DIR)/target/release/libafl_cc" CXX="$(PROJECT_DIR)/target/release/libafl_cxx"
fuzzer: libpng-1.6.37/.libs/libpng16.a libafl_cxx
# Build the libpng libfuzzer library
cargo build --release
# Build the libpng harness
target/release/libafl_cxx \
$(PROJECT_DIR)/harness.cc \
$(PROJECT_DIR)/libpng-1.6.37/.libs/libpng16.a \
-I$(PROJECT_DIR)/libpng-1.6.37/ \
-o $(FUZZER_NAME) \
-lm -lz
clean:
rm ./$(FUZZER_NAME)
$(MAKE) -C libpng-1.6.37 clean
run: all
./$(FUZZER_NAME) --cores 0 --input ./corpus &
short_test: all
rm -rf libafl_unix_shmem_server || true
timeout 10s ./$(FUZZER_NAME) --cores 0 --input ./corpus &
test: all
timeout 60s ./$(FUZZER_NAME) --cores 0 --input ./corpus &

View File

@ -0,0 +1,47 @@
# Libfuzzer for libpng, with launcher
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.
In contrast to the normal libfuzzer libpng example, this uses the `launcher` feature, that automatically spawns `n` child processes, and binds them to a free core.
## Build
To build this example, run
```bash
cargo build --release
```
This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback.
In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target.
Then download libpng, and unpack the archive:
```bash
wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar -xvf libpng-1.6.37.tar.xz
```
Now compile libpng, using the libafl_cc compiler wrapper:
```bash
cd libpng-1.6.37
./configure
make CC=../target/release/libafl_cc CXX=../target/release/libafl_cxx -j `nproc`
```
You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`.
Now, we have to build the libfuzzer harness and link all together to create our fuzzer binary.
```
cd ..
./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm
```
Afterwards, the fuzzer will be ready to run.
## Run
Just run once, the launcher feature should do the rest.

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

View File

@ -0,0 +1,197 @@
// 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
#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,36 @@
use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses};
use std::env;
pub fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
let mut dir = env::current_exe().unwrap();
let wrapper_name = dir.file_name().unwrap().to_str().unwrap();
let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() {
"cc" => false,
"++" | "pp" | "xx" => true,
_ => panic!("Could not figure out if c or c++ warpper was called. Expected {:?} to end with c or cxx", dir),
};
dir.pop();
let mut cc = ClangWrapper::new();
if let Some(code) = cc
.cpp(is_cpp)
// silence the compiler wrapper output, needed for some configure scripts.
.silence(true)
.parse_args(&args)
.expect("Failed to parse the command line")
.link_staticlib(&dir, "libfuzzer_libpng")
.add_arg("-fsanitize-coverage=trace-pc-guard")
.add_pass(LLVMPasses::CoverageAccounting)
.run()
.expect("Failed to run the wrapped compiler")
{
std::process::exit(code);
}
} else {
panic!("LibAFL CC: No Arguments given");
}
}

View File

@ -0,0 +1,5 @@
pub mod libafl_cc;
fn main() {
libafl_cc::main()
}

View File

@ -0,0 +1,272 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for libpng.
//! In this example, you will see the use of the `launcher` feature.
//! The `launcher` will spawn new processes for each cpu core.
use mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
use clap::{self, StructOpt};
use core::time::Duration;
use std::{env, net::SocketAddr, path::PathBuf};
use libafl::{
bolts::{
current_nanos,
launcher::Launcher,
os::Cores,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
tuples::{tuple_list, Merge},
AsSlice,
},
corpus::{
Corpus, CoverageAccountingCorpusScheduler, InMemoryCorpus, OnDiskCorpus,
QueueCorpusScheduler,
},
events::EventConfig,
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes},
monitors::MultiMonitor,
mutators::scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, StdState},
Error,
};
use libafl_targets::{
libfuzzer_initialize, libfuzzer_test_one_input, ACCOUNTING_MEMOP_MAP, EDGES_MAP, MAX_EDGES_NUM,
};
/// Parse a millis string to a [`Duration`]. Used for arg parsing.
fn timeout_from_millis_str(time: &str) -> Result<Duration, Error> {
Ok(Duration::from_millis(time.parse()?))
}
/// The commandline args this fuzzer accepts
#[derive(Debug, StructOpt)]
#[clap(
name = "libfuzzer_libpng_launcher",
about = "A libfuzzer-like fuzzer for libpng with llmp-multithreading support and a launcher",
author = "Andrea Fioraldi <andreafioraldi@gmail.com>, Dominik Maier <domenukk@gmail.com>"
)]
struct Opt {
#[clap(
short,
long,
parse(try_from_str = Cores::from_cmdline),
help = "Spawn a client in each of the provided cores. Broker runs in the 0th core. 'all' to select all available cores. 'none' to run a client without binding to any core. eg: '1,2-4,6' selects the cores 1,2,3,4,6.",
name = "CORES"
)]
cores: Cores,
#[clap(
short = 'p',
long,
help = "Choose the broker TCP port, default is 1337",
name = "PORT",
default_value = "1337"
)]
broker_port: u16,
#[clap(
parse(try_from_str),
short = 'a',
long,
help = "Specify a remote broker",
name = "REMOTE"
)]
remote_broker_addr: Option<SocketAddr>,
#[clap(
parse(try_from_str),
short,
long,
help = "Set an initial corpus directory",
name = "INPUT"
)]
input: Vec<PathBuf>,
#[clap(
short,
long,
parse(try_from_str),
help = "Set the output directory, default is ./out",
name = "OUTPUT",
default_value = "./out"
)]
output: PathBuf,
#[clap(
parse(try_from_str = timeout_from_millis_str),
short,
long,
help = "Set the exeucution timeout in milliseconds, default is 10000",
name = "TIMEOUT",
default_value = "10000"
)]
timeout: Duration,
/*
/// This fuzzer has hard-coded tokens
#[clap(
parse(from_os_str),
short = "x",
long,
help = "Feed the fuzzer with an user-specified list of tokens (often called \"dictionary\"",
name = "TOKENS",
multiple = true
)]
tokens: Vec<PathBuf>,
*/
}
/// The main fn, `no_mangle` as it is a C symbol
#[no_mangle]
pub fn libafl_main() {
// Registry the metadata types used in this fuzzer
// Needed only on no_std
//RegistryBuilder::register::<Tokens>();
let opt = Opt::parse();
let broker_port = opt.broker_port;
let cores = opt.cores;
println!(
"Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string()
);
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let monitor = MultiMonitor::new(|s| println!("{}", s));
let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut restarting_mgr, _core_id| {
// Create an observation channel using the coverage map
let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] };
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges));
// Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");
// The state of the edges feedback.
let feedback_state = MapFeedbackState::with_observer(&edges_observer);
// Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR
let feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state
MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false),
// Time feedback, this one does not need a feedback state
TimeFeedback::new_with_observer(&time_observer)
);
// A feedback to choose if an input is a solution or not
let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
// If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| {
StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(&opt.output).unwrap(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
tuple_list!(feedback_state),
)
});
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::from(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 = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// A minimization+queue policy to get testcasess from the corpus
let scheduler = CoverageAccountingCorpusScheduler::new(
&mut state,
QueueCorpusScheduler::new(),
unsafe { &ACCOUNTING_MEMOP_MAP },
);
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target.as_slice();
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// Create the executor for an in-process function with one observer for edge coverage and one for the execution time
let mut executor = TimeoutExecutor::new(
InProcessExecutor::new(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
)?,
// 10 seconds timeout
opt.timeout,
);
// The actual target run starts here.
// Call LLVMFUzzerInitialize() if present.
let args: Vec<String> = env::args().collect();
if libfuzzer_initialize(&args) == -1 {
println!("Warning: LLVMFuzzerInitialize failed with -1")
}
// In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut restarting_mgr, &opt.input)
.unwrap_or_else(|e| {
panic!("Failed to load initial corpus at {:?} {:?}", &opt.input, e)
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
Ok(())
};
match Launcher::builder()
.shmem_provider(shmem_provider)
.configuration(EventConfig::from_name("default"))
.monitor(monitor)
.run_client(&mut run_client)
.cores(&cores)
.broker_port(broker_port)
.remote_broker_addr(opt.remote_broker_addr)
//.stdout_file(Some("/dev/null"))
.build()
.launch()
{
Ok(()) => (),
Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."),
Err(err) => panic!("Failed to run launcher: {:?}", err),
}
}

View File

@ -31,7 +31,6 @@ use libafl::{
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes}, inputs::{BytesInput, HasTargetBytes},
monitors::tui::TuiMonitor, monitors::tui::TuiMonitor,
monitors::MultiMonitor,
mutators::scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, mutators::scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
mutators::token_mutations::Tokens, mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
@ -142,7 +141,6 @@ pub fn libafl_main() {
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
//let monitor = MultiMonitor::new(|s| println!("{}", s));
let monitor = TuiMonitor::new("Test fuzzer on libpng".into(), true); let monitor = TuiMonitor::new("Test fuzzer on libpng".into(), true);
let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut restarting_mgr, _core_id| { let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut restarting_mgr, _core_id| {

Binary file not shown.

View File

@ -0,0 +1,291 @@
//! Coverage accounting corpus scheduler, more details at <https://www.ndss-symposium.org/wp-content/uploads/2020/02/24422-paper.pdf>
use crate::{
bolts::{rands::Rand, AsMutSlice, AsSlice, HasLen, HasRefCnt},
corpus::{
minimizer::{
IsFavoredMetadata, LenTimeMulFavFactor, MinimizerCorpusScheduler,
DEFAULT_SKIP_NON_FAVORED_PROB,
},
Corpus, CorpusScheduler, Testcase,
},
feedbacks::MapIndexesMetadata,
inputs::Input,
state::{HasCorpus, HasMetadata, HasRand},
Error,
};
use alloc::vec::Vec;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
/// A testcase metadata holding a list of indexes of a map
#[derive(Debug, Serialize, Deserialize)]
pub struct AccountingIndexesMetadata {
/// The list of indexes.
pub list: Vec<usize>,
/// A refcount used to know when remove this meta
pub tcref: isize,
}
crate::impl_serdeany!(AccountingIndexesMetadata);
impl AsSlice<usize> for AccountingIndexesMetadata {
/// Convert to a slice
fn as_slice(&self) -> &[usize] {
self.list.as_slice()
}
}
impl AsMutSlice<usize> for AccountingIndexesMetadata {
/// Convert to a slice
fn as_mut_slice(&mut self) -> &mut [usize] {
self.list.as_mut_slice()
}
}
impl HasRefCnt for AccountingIndexesMetadata {
fn refcnt(&self) -> isize {
self.tcref
}
fn refcnt_mut(&mut self) -> &mut isize {
&mut self.tcref
}
}
impl AccountingIndexesMetadata {
/// Creates a new [`struct@AccountingIndexesMetadata`].
#[must_use]
pub fn new(list: Vec<usize>) -> Self {
Self { list, tcref: 0 }
}
/// Creates a new [`struct@AccountingIndexesMetadata`] specifying the refcount.
#[must_use]
pub fn with_tcref(list: Vec<usize>, tcref: isize) -> Self {
Self { list, tcref }
}
}
/// A state metadata holding a map of favoreds testcases for each map entry
#[derive(Debug, Serialize, Deserialize)]
pub struct TopAccountingMetadata {
/// map index -> corpus index
pub map: HashMap<usize, usize>,
/// If changed sicne the previous add to the corpus
pub changed: bool,
/// The max accounting seen so far
pub max_accounting: Vec<u32>,
}
crate::impl_serdeany!(TopAccountingMetadata);
impl TopAccountingMetadata {
/// Creates a new [`struct@TopAccountingMetadata`]
#[must_use]
pub fn new(acc_len: usize) -> Self {
Self {
map: HashMap::default(),
changed: false,
max_accounting: vec![0; acc_len],
}
}
}
/// A minimizer scheduler using coverage accounting
#[derive(Debug)]
pub struct CoverageAccountingCorpusScheduler<'a, CS, I, S>
where
CS: CorpusScheduler<I, S>,
I: Input + HasLen,
S: HasCorpus<I> + HasMetadata + HasRand,
{
accounting_map: &'a [u32],
skip_non_favored_prob: u64,
inner: MinimizerCorpusScheduler<CS, LenTimeMulFavFactor<I>, I, MapIndexesMetadata, S>,
}
impl<'a, CS, I, S> CorpusScheduler<I, S> for CoverageAccountingCorpusScheduler<'a, CS, I, S>
where
CS: CorpusScheduler<I, S>,
I: Input + HasLen,
S: HasCorpus<I> + HasMetadata + HasRand,
{
fn on_add(&self, state: &mut S, idx: usize) -> Result<(), Error> {
self.update_accounting_score(state, idx)?;
self.inner.on_add(state, idx)
}
fn on_replace(&self, state: &mut S, idx: usize, testcase: &Testcase<I>) -> Result<(), Error> {
self.inner.on_replace(state, idx, testcase)
}
fn on_remove(
&self,
state: &mut S,
idx: usize,
testcase: &Option<Testcase<I>>,
) -> Result<(), Error> {
self.inner.on_remove(state, idx, testcase)
}
fn next(&self, state: &mut S) -> Result<usize, Error> {
if state
.metadata()
.get::<TopAccountingMetadata>()
.map_or(false, |x| x.changed)
{
self.accounting_cull(state)?;
} else {
self.inner.cull(state)?;
}
let mut idx = self.inner.base().next(state)?;
while {
let has = !state
.corpus()
.get(idx)?
.borrow()
.has_metadata::<IsFavoredMetadata>();
has
} && state.rand_mut().below(100) < self.skip_non_favored_prob
{
idx = self.inner.base().next(state)?;
}
Ok(idx)
}
}
impl<'a, CS, I, S> CoverageAccountingCorpusScheduler<'a, CS, I, S>
where
CS: CorpusScheduler<I, S>,
I: Input + HasLen,
S: HasCorpus<I> + HasMetadata + HasRand,
{
/// Update the `Corpus` score
#[allow(clippy::unused_self)]
#[allow(clippy::cast_possible_wrap)]
pub fn update_accounting_score(&self, state: &mut S, idx: usize) -> Result<(), Error> {
let mut indexes = vec![];
let mut new_favoreds = vec![];
{
for idx in 0..self.accounting_map.len() {
if self.accounting_map[idx] == 0 {
continue;
}
indexes.push(idx);
let mut equal_score = false;
{
let top_acc = state.metadata().get::<TopAccountingMetadata>().unwrap();
if let Some(old_idx) = top_acc.map.get(&idx) {
if top_acc.max_accounting[idx] > self.accounting_map[idx] {
continue;
}
if top_acc.max_accounting[idx] >= self.accounting_map[idx] {
equal_score = true;
}
let mut old = state.corpus().get(*old_idx)?.borrow_mut();
let must_remove = {
let old_meta = old.metadata_mut().get_mut::<AccountingIndexesMetadata>().ok_or_else(|| {
Error::KeyNotFound(format!(
"AccountingIndexesMetadata, needed by CoverageAccountingCorpusScheduler, not found in testcase #{}",
old_idx
))
})?;
*old_meta.refcnt_mut() -= 1;
old_meta.refcnt() <= 0
};
if must_remove {
drop(old.metadata_mut().remove::<AccountingIndexesMetadata>());
}
}
}
let top_acc = state
.metadata_mut()
.get_mut::<TopAccountingMetadata>()
.unwrap();
// if its accounting is equal to others', it's not favored
if equal_score {
top_acc.map.remove(&idx);
} else if top_acc.max_accounting[idx] < self.accounting_map[idx] {
new_favoreds.push(idx);
top_acc.max_accounting[idx] = self.accounting_map[idx];
}
}
}
if new_favoreds.is_empty() {
return Ok(());
}
state.corpus().get(idx)?.borrow_mut().metadata_mut().insert(
AccountingIndexesMetadata::with_tcref(indexes, new_favoreds.len() as isize),
);
let top_acc = state
.metadata_mut()
.get_mut::<TopAccountingMetadata>()
.unwrap();
top_acc.changed = true;
for elem in new_favoreds {
top_acc.map.insert(elem, idx);
}
Ok(())
}
/// Cull the `Corpus`
#[allow(clippy::unused_self)]
pub fn accounting_cull(&self, state: &mut S) -> Result<(), Error> {
let top_rated = match state.metadata().get::<TopAccountingMetadata>() {
None => return Ok(()),
Some(val) => val,
};
for (_key, idx) in &top_rated.map {
let mut entry = state.corpus().get(*idx)?.borrow_mut();
if entry.fuzzed() {
continue;
}
entry.add_metadata(IsFavoredMetadata {});
}
Ok(())
}
/// Creates a new [`CoverageAccountingCorpusScheduler`] that wraps a `base` [`CorpusScheduler`]
/// and has a default probability to skip non-faved [`Testcase`]s of [`DEFAULT_SKIP_NON_FAVORED_PROB`].
pub fn new(state: &mut S, base: CS, accounting_map: &'a [u32]) -> Self {
state.add_metadata(TopAccountingMetadata::new(accounting_map.len()));
Self {
accounting_map,
inner: MinimizerCorpusScheduler::new(base),
skip_non_favored_prob: DEFAULT_SKIP_NON_FAVORED_PROB,
}
}
/// Creates a new [`CoverageAccountingCorpusScheduler`] that wraps a `base` [`CorpusScheduler`]
/// and has a non-default probability to skip non-faved [`Testcase`]s using (`skip_non_favored_prob`).
pub fn with_skip_prob(
state: &mut S,
base: CS,
skip_non_favored_prob: u64,
accounting_map: &'a [u32],
) -> Self {
state.add_metadata(TopAccountingMetadata::new(accounting_map.len()));
Self {
accounting_map,
inner: MinimizerCorpusScheduler::with_skip_prob(base, skip_non_favored_prob),
skip_non_favored_prob,
}
}
}

View File

@ -257,6 +257,11 @@ where
Ok(()) Ok(())
} }
/// Get a reference to the base scheduler
pub fn base(&self) -> &CS {
&self.base
}
/// Creates a new [`MinimizerCorpusScheduler`] that wraps a `base` [`CorpusScheduler`] /// Creates a new [`MinimizerCorpusScheduler`] that wraps a `base` [`CorpusScheduler`]
/// and has a default probability to skip non-faved [`Testcase`]s of [`DEFAULT_SKIP_NON_FAVORED_PROB`]. /// and has a default probability to skip non-faved [`Testcase`]s of [`DEFAULT_SKIP_NON_FAVORED_PROB`].
pub fn new(base: CS) -> Self { pub fn new(base: CS) -> Self {

View File

@ -19,6 +19,9 @@ pub use cached::CachedOnDiskCorpus;
pub mod queue; pub mod queue;
pub use queue::QueueCorpusScheduler; pub use queue::QueueCorpusScheduler;
pub mod accounting;
pub use accounting::*;
pub mod minimizer; pub mod minimizer;
pub use minimizer::{ pub use minimizer::{
FavFactor, IndexesLenTimeMinimizerCorpusScheduler, IsFavoredMetadata, FavFactor, IndexesLenTimeMinimizerCorpusScheduler, IsFavoredMetadata,

View File

@ -13,7 +13,7 @@ use crate::{
}; };
/// An entry in the Testcase Corpus /// An entry in the Testcase Corpus
#[derive(Default, Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "I: serde::de::DeserializeOwned")] #[serde(bound = "I: serde::de::DeserializeOwned")]
pub struct Testcase<I> pub struct Testcase<I>
where where
@ -31,6 +31,8 @@ where
cached_len: Option<usize>, cached_len: Option<usize>,
/// Number of executions done at discovery time /// Number of executions done at discovery time
executions: usize, executions: usize,
/// If it has been fuzzed
fuzzed: bool,
} }
impl<I> HasMetadata for Testcase<I> impl<I> HasMetadata for Testcase<I>
@ -152,6 +154,18 @@ where
&mut self.executions &mut self.executions
} }
/// Get if it was fuzzed
#[inline]
pub fn fuzzed(&self) -> bool {
self.fuzzed
}
/// Set if it was fuzzed
#[inline]
pub fn set_fuzzed(&mut self, fuzzed: bool) {
self.fuzzed = fuzzed;
}
/// Create a new Testcase instace given an input /// Create a new Testcase instace given an input
#[inline] #[inline]
pub fn new<T>(input: T) -> Self pub fn new<T>(input: T) -> Self
@ -160,11 +174,7 @@ where
{ {
let mut slf = Testcase { let mut slf = Testcase {
input: Some(input.into()), input: Some(input.into()),
filename: None, ..Testcase::default()
metadata: SerdeAnyMap::new(),
exec_time: None,
cached_len: None,
executions: 0,
}; };
slf.input.as_mut().unwrap().wrapped_as_testcase(); slf.input.as_mut().unwrap().wrapped_as_testcase();
slf slf
@ -177,10 +187,7 @@ where
Testcase { Testcase {
input: Some(input), input: Some(input),
filename: Some(filename), filename: Some(filename),
metadata: SerdeAnyMap::new(), ..Testcase::default()
exec_time: None,
cached_len: None,
executions: 0,
} }
} }
@ -190,18 +197,19 @@ where
input.wrapped_as_testcase(); input.wrapped_as_testcase();
Testcase { Testcase {
input: Some(input), input: Some(input),
filename: None,
metadata: SerdeAnyMap::new(),
exec_time: None,
cached_len: None,
executions, executions,
..Testcase::default()
} }
} }
}
/// Create a new, empty, [`Testcase`]. impl<I> Default for Testcase<I>
#[must_use] where
I: Input,
{
/// Create a new default Testcase
#[inline] #[inline]
pub fn default() -> Self { fn default() -> Self {
Testcase { Testcase {
input: None, input: None,
filename: None, filename: None,
@ -209,6 +217,7 @@ where
exec_time: None, exec_time: None,
cached_len: None, cached_len: None,
executions: 0, executions: 0,
fuzzed: false,
} }
} }
} }

View File

@ -73,6 +73,7 @@ fn find_llvm_config() -> String {
}) })
} }
#[allow(clippy::too_many_lines)]
fn main() { fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir); let out_dir = Path::new(&out_dir);
@ -80,6 +81,7 @@ fn main() {
println!("cargo:rerun-if-env-changed=LLVM_CONFIG"); println!("cargo:rerun-if-env-changed=LLVM_CONFIG");
println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_SIZE"); println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_SIZE");
println!("cargo:rerun-if-env-changed=LIBAFL_ACCOUNTING_MAP_SIZE");
let mut custom_flags = vec![]; let mut custom_flags = vec![];
@ -91,6 +93,11 @@ fn main() {
.expect("Could not parse LIBAFL_EDGES_MAP_SIZE"); .expect("Could not parse LIBAFL_EDGES_MAP_SIZE");
custom_flags.push(format!("-DLIBAFL_EDGES_MAP_SIZE={}", edges_map_size)); custom_flags.push(format!("-DLIBAFL_EDGES_MAP_SIZE={}", edges_map_size));
let acc_map_size: usize = option_env!("LIBAFL_ACCOUNTING_MAP_SIZE")
.map_or(Ok(65536), str::parse)
.expect("Could not parse LIBAFL_ACCOUNTING_MAP_SIZE");
custom_flags.push(format!("-DLIBAFL_ACCOUNTING_MAP_SIZE={}", acc_map_size));
let llvm_config = find_llvm_config(); let llvm_config = find_llvm_config();
if let Ok(output) = Command::new(&llvm_config).args(&["--bindir"]).output() { if let Ok(output) = Command::new(&llvm_config).args(&["--bindir"]).output() {
@ -99,7 +106,6 @@ fn main() {
.expect("Invalid llvm-config output") .expect("Invalid llvm-config output")
.trim(), .trim(),
); );
write!( write!(
clang_constants_file, clang_constants_file,
"// These constants are autogenerated by build.rs "// These constants are autogenerated by build.rs
@ -111,10 +117,14 @@ fn main() {
/// The size of the edges map /// The size of the edges map
pub const EDGES_MAP_SIZE: usize = {}; pub const EDGES_MAP_SIZE: usize = {};
/// The size of the accounting maps
pub const ACCOUNTING_MAP_SIZE: usize = {};
", ",
llvm_bindir.join("clang"), llvm_bindir.join("clang"),
llvm_bindir.join("clang++"), llvm_bindir.join("clang++"),
edges_map_size edges_map_size,
acc_map_size
) )
.expect("Could not write file"); .expect("Could not write file");
@ -126,6 +136,7 @@ fn main() {
let output = Command::new(&llvm_config) let output = Command::new(&llvm_config)
.args(&["--ldflags"]) .args(&["--ldflags"])
.args(&["--libs"])
.output() .output()
.expect("Failed to execute llvm-config"); .expect("Failed to execute llvm-config");
let ldflags = str::from_utf8(&output.stdout).expect("Invalid llvm-config output"); let ldflags = str::from_utf8(&output.stdout).expect("Invalid llvm-config output");
@ -140,11 +151,13 @@ fn main() {
ldflags.push("dynamic_lookup"); ldflags.push("dynamic_lookup");
}; };
println!("cargo:rerun-if-changed=src/common-llvm.h");
println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc"); println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc");
println!("cargo:rerun-if-changed=src/afl-coverage-pass.cc"); println!("cargo:rerun-if-changed=src/afl-coverage-pass.cc");
println!("cargo:rerun-if-changed=src/autotokens-pass.cc"); println!("cargo:rerun-if-changed=src/autotokens-pass.cc");
println!("cargo:rerun-if-changed=src/coverage-accounting-pass.cc");
let _ = Command::new(llvm_bindir.join("clang++")) assert!(Command::new(llvm_bindir.join("clang++"))
.args(&cxxflags) .args(&cxxflags)
.args(&custom_flags) .args(&custom_flags)
.arg(src_dir.join("cmplog-routines-pass.cc")) .arg(src_dir.join("cmplog-routines-pass.cc"))
@ -152,9 +165,10 @@ fn main() {
.args(&["-fPIC", "-shared", "-o"]) .args(&["-fPIC", "-shared", "-o"])
.arg(out_dir.join(format!("cmplog-routines-pass.{}", dll_extension()))) .arg(out_dir.join(format!("cmplog-routines-pass.{}", dll_extension())))
.status() .status()
.expect("Failed to compile cmplog-routines-pass.cc"); .expect("Failed to compile cmplog-routines-pass.cc")
.success());
let _ = Command::new(llvm_bindir.join("clang++")) assert!(Command::new(llvm_bindir.join("clang++"))
.args(&cxxflags) .args(&cxxflags)
.args(&custom_flags) .args(&custom_flags)
.arg(src_dir.join("afl-coverage-pass.cc")) .arg(src_dir.join("afl-coverage-pass.cc"))
@ -162,9 +176,10 @@ fn main() {
.args(&["-fPIC", "-shared", "-o"]) .args(&["-fPIC", "-shared", "-o"])
.arg(out_dir.join(format!("afl-coverage-pass.{}", dll_extension()))) .arg(out_dir.join(format!("afl-coverage-pass.{}", dll_extension())))
.status() .status()
.expect("Failed to compile afl-coverage-pass.cc"); .expect("Failed to compile afl-coverage-pass.cc")
.success());
let _ = Command::new(llvm_bindir.join("clang++")) assert!(Command::new(llvm_bindir.join("clang++"))
.args(&cxxflags) .args(&cxxflags)
.args(&custom_flags) .args(&custom_flags)
.arg(src_dir.join("autotokens-pass.cc")) .arg(src_dir.join("autotokens-pass.cc"))
@ -172,7 +187,19 @@ fn main() {
.args(&["-fPIC", "-shared", "-o"]) .args(&["-fPIC", "-shared", "-o"])
.arg(out_dir.join(format!("autotokens-pass.{}", dll_extension()))) .arg(out_dir.join(format!("autotokens-pass.{}", dll_extension())))
.status() .status()
.expect("Failed to compile autotokens-pass.cc"); .expect("Failed to compile autotokens-pass.cc")
.success());
assert!(Command::new(llvm_bindir.join("clang++"))
.args(&cxxflags)
.args(&custom_flags)
.arg(src_dir.join("coverage-accounting-pass.cc"))
.args(&ldflags)
.args(&["-fPIC", "-shared", "-o"])
.arg(out_dir.join(format!("coverage-accounting-pass.{}", dll_extension())))
.status()
.expect("Failed to compile coverage-accounting-pass.cc")
.success());
} else { } else {
write!( write!(
clang_constants_file, clang_constants_file,

View File

@ -26,33 +26,16 @@
*/ */
#include <stdio.h> #include "common-llvm.h"
#include <stdlib.h>
#include <time.h> #include <time.h>
#include <list> #include <list>
#include <string> #include <string>
#include <fstream> #include <fstream>
#include "llvm/Config/llvm-config.h"
#if LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR < 5
typedef long double max_align_t;
#endif
#if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */
//#define USE_NEW_PM 1
#endif
#include "llvm/Support/CommandLine.h" #include "llvm/Support/CommandLine.h"
#include "llvm/IR/IRBuilder.h" #include "llvm/IR/IRBuilder.h"
#ifdef USE_NEW_PM
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/IR/PassManager.h"
#else
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#endif
#include "llvm/IR/BasicBlock.h" #include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Module.h" #include "llvm/IR/Module.h"
#include "llvm/Support/Debug.h" #include "llvm/Support/Debug.h"
@ -77,8 +60,6 @@ typedef uint32_t prev_loc_t;
#define MAP_SIZE LIBAFL_EDGES_MAP_SIZE #define MAP_SIZE LIBAFL_EDGES_MAP_SIZE
#define FATAL(...) do { fprintf(stderr, "FATAL: " __VA_ARGS__); exit(1); } while (0)
using namespace llvm; using namespace llvm;
static cl::opt<bool> Debug("debug", cl::desc("Debug prints"), cl::init(false), cl::NotHidden); static cl::opt<bool> Debug("debug", cl::desc("Debug prints"), cl::init(false), cl::NotHidden);
@ -156,36 +137,6 @@ llvmGetPassPluginInfo() {
char AFLCoverage::ID = 0; char AFLCoverage::ID = 0;
#endif #endif
static uint32_t RandBelow(uint32_t max) {
return (uint32_t)rand() % (max +1);
}
/* needed up to 3.9.0 */
#if LLVM_VERSION_MAJOR == 3 && \
(LLVM_VERSION_MINOR < 9 || \
(LLVM_VERSION_MINOR == 9 && LLVM_VERSION_PATCH < 1))
static uint64_t PowerOf2Ceil(unsigned in) {
uint64_t in64 = in - 1;
in64 |= (in64 >> 1);
in64 |= (in64 >> 2);
in64 |= (in64 >> 4);
in64 |= (in64 >> 8);
in64 |= (in64 >> 16);
in64 |= (in64 >> 32);
return in64 + 1;
}
#endif
/* #if LLVM_VERSION_STRING >= "4.0.1" */
#if LLVM_VERSION_MAJOR > 4 || \
(LLVM_VERSION_MAJOR == 4 && LLVM_VERSION_PATCH >= 1)
#define HAVE_VECTOR_INTRINSICS 1
#endif
#ifdef USE_NEW_PM #ifdef USE_NEW_PM
PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) { PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) {
#else #else

View File

@ -33,6 +33,8 @@ pub enum LLVMPasses {
AFLCoverage, AFLCoverage,
/// The Autotoken pass /// The Autotoken pass
AutoTokens, AutoTokens,
/// The Coverage Accouting (BB metric) pass
CoverageAccounting,
} }
impl LLVMPasses { impl LLVMPasses {
@ -47,6 +49,8 @@ impl LLVMPasses {
LLVMPasses::AutoTokens => { LLVMPasses::AutoTokens => {
PathBuf::from(env!("OUT_DIR")).join(format!("autotokens-pass.{}", dll_extension())) PathBuf::from(env!("OUT_DIR")).join(format!("autotokens-pass.{}", dll_extension()))
} }
LLVMPasses::CoverageAccounting => PathBuf::from(env!("OUT_DIR"))
.join(format!("coverage-accounting-pass.{}", dll_extension())),
} }
} }
} }

View File

@ -0,0 +1,55 @@
#ifndef LIBAFL_COMMON_LLVM_H
#define LIBAFL_COMMON_LLVM_H
#include <stdio.h>
#include <stdlib.h>
#include "llvm/Config/llvm-config.h"
#if LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR < 5
typedef long double max_align_t;
#endif
#if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */
//#define USE_NEW_PM 1
#endif
/* #if LLVM_VERSION_STRING >= "4.0.1" */
#if LLVM_VERSION_MAJOR > 4 || \
(LLVM_VERSION_MAJOR == 4 && LLVM_VERSION_PATCH >= 1)
#define HAVE_VECTOR_INTRINSICS 1
#endif
#ifdef USE_NEW_PM
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/IR/PassManager.h"
#else
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#endif
#define FATAL(...) do { fprintf(stderr, "FATAL: " __VA_ARGS__); exit(1); } while (0)
static uint32_t RandBelow(uint32_t max) {
return (uint32_t)rand() % (max +1);
}
/* needed up to 3.9.0 */
#if LLVM_VERSION_MAJOR == 3 && \
(LLVM_VERSION_MINOR < 9 || \
(LLVM_VERSION_MINOR == 9 && LLVM_VERSION_PATCH < 1))
static uint64_t PowerOf2Ceil(unsigned in) {
uint64_t in64 = in - 1;
in64 |= (in64 >> 1);
in64 |= (in64 >> 2);
in64 |= (in64 >> 4);
in64 |= (in64 >> 8);
in64 |= (in64 >> 16);
in64 |= (in64 >> 32);
return in64 + 1;
}
#endif
#endif // LIBAFL_COMMON_LLVM_H

View File

@ -0,0 +1,272 @@
/*
american fuzzy lop++ - LLVM-mode instrumentation pass
---------------------------------------------------
Written by Laszlo Szekeres <lszekeres@google.com>,
Adrian Herrera <adrian.herrera@anu.edu.au>,
Michal Zalewski
LLVM integration design comes from Laszlo Szekeres. C bits copied-and-pasted
from afl-as.c are Michal's fault.
NGRAM previous location coverage comes from Adrian Herrera.
Copyright 2015, 2016 Google Inc. All rights reserved.
Copyright 2019-2020 AFLplusplus Project. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
This library is plugged into LLVM when invoking clang through afl-clang-fast.
It tells the compiler to add code roughly equivalent to the bits discussed
in ../afl-as.h.
*/
#include "common-llvm.h"
#include <time.h>
#include <list>
#include <string>
#include <fstream>
#include "llvm/Support/CommandLine.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
#if LLVM_VERSION_MAJOR > 3 || \
(LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR > 4)
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/CFG.h"
#else
#include "llvm/DebugInfo.h"
#include "llvm/Support/CFG.h"
#endif
typedef uint32_t prev_loc_t;
#define MAP_SIZE LIBAFL_ACCOUNTING_MAP_SIZE
using namespace llvm;
static cl::opt<bool> Debug("debug", cl::desc("Debug prints"), cl::init(false), cl::NotHidden);
static cl::opt<uint32_t> InstRatio("inst_ratio", cl::desc("Instrumentation ratio in percentage"), cl::init(100), cl::NotHidden);
static cl::opt<bool> ThreadSafe("thread_safe", cl::desc("Use the thread safe instrumentation"), cl::init(false), cl::NotHidden);
namespace {
#ifdef USE_NEW_PM
class AFLCoverage : public PassInfoMixin<AFLCoverage> {
public:
AFLCoverage() {
#else
class AFLCoverage : public ModulePass {
public:
static char ID;
AFLCoverage() : ModulePass(ID) {
#endif
// initInstrumentList();
}
#ifdef USE_NEW_PM
PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
#else
bool runOnModule(Module &M) override;
#endif
protected:
uint32_t map_size = MAP_SIZE;
uint32_t function_minimum_size = 1;
};
} // namespace
#ifdef USE_NEW_PM
extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK
llvmGetPassPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION, "AFLCoverageAccounting", "v0.1",
/* lambda to insert our pass into the pass pipeline. */
[](PassBuilder &PB) {
#if 1
using OptimizationLevel = typename PassBuilder::OptimizationLevel;
PB.registerOptimizerLastEPCallback(
[](ModulePassManager &MPM, OptimizationLevel OL) {
MPM.addPass(AFLCoverage());
}
);
/* TODO LTO registration */
#else
using PipelineElement = typename PassBuilder::PipelineElement;
PB.registerPipelineParsingCallback(
[](StringRef Name, ModulePassManager &MPM, ArrayRef<PipelineElement>) {
if ( Name == "AFLCoverageAccounting" ) {
MPM.addPass(AFLCoverage());
return true;
} else {
return false;
}
}
);
#endif
}
};
}
#else
char AFLCoverage::ID = 0;
#endif
#ifdef USE_NEW_PM
PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) {
#else
bool AFLCoverage::runOnModule(Module &M) {
#endif
LLVMContext &C = M.getContext();
IntegerType *Int32Ty = IntegerType::getInt32Ty(C);
uint32_t rand_seed;
unsigned int cur_loc = 0;
#ifdef USE_NEW_PM
auto PA = PreservedAnalyses::all();
#endif
/* Setup random() so we get Actually Random(TM) */
rand_seed = time(NULL);
srand(rand_seed);
/* Decide instrumentation ratio */
if (!InstRatio || InstRatio > 100)
FATAL("Bad value of the instrumentation ratio (must be between 1 and 100)");
/* Get globals for the SHM region and the previous location. Note that
__afl_acc_prev_loc is thread-local. */
GlobalVariable *AFLMemOpPtr =
new GlobalVariable(M, PointerType::get(Int32Ty, 0), false,
GlobalValue::ExternalLinkage, 0, "__afl_acc_memop_ptr");
GlobalVariable *AFLPrevLoc;
#if defined(__ANDROID__) || defined(__HAIKU__)
AFLPrevLoc = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_acc_prev_loc");
#else
AFLPrevLoc = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_acc_prev_loc", 0,
GlobalVariable::GeneralDynamicTLSModel, 0, false);
#endif
/* Instrument all the things! */
int inst_blocks = 0;
// scanForDangerousFunctions(&M);
for (auto &F : M) {
int has_calls = 0;
if (Debug)
fprintf(stderr, "FUNCTION: %s (%zu)\n", F.getName().str().c_str(),
F.size());
// if (!isInInstrumentList(&F)) { continue; }
if (F.size() < function_minimum_size) { continue; }
std::list<Value *> todo;
for (auto &BB : F) {
BasicBlock::iterator IP = BB.getFirstInsertionPt();
IRBuilder<> IRB(&(*IP));
if (RandBelow(100) >= InstRatio) continue;
// Start with 1 to implicitly track edge coverage too
uint32_t MemCnt = 1;
for (auto &I : BB) {
if (I.mayReadFromMemory() || I.mayWriteToMemory())
++MemCnt;
}
/* Make up cur_loc */
cur_loc = RandBelow(map_size);
ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc);
/* Load prev_loc */
LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc);
PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
/* Load SHM pointer */
LoadInst *MemReadPtr = IRB.CreateLoad(AFLMemOpPtr);
MemReadPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
Value *MemReadPtrIdx = IRB.CreateGEP(MemReadPtr, IRB.CreateXor(PrevLoc, CurLoc));
/* Update bitmap */
LoadInst *MemReadCount = IRB.CreateLoad(MemReadPtrIdx);
MemReadCount->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
Value *MemReadIncr = IRB.CreateAdd(MemReadCount, ConstantInt::get(Int32Ty, MemCnt));
IRB.CreateStore(MemReadIncr, MemReadPtrIdx)
->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
/* Update prev_loc */
StoreInst * Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1),
AFLPrevLoc);
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
inst_blocks++;
}
}
if (Debug) {
if (!inst_blocks)
fprintf(stderr, "No instrumentation targets found.\n");
else
fprintf(stderr, "Instrumented %d locations (ratio %u%%).\n", inst_blocks, (unsigned)InstRatio);
}
#ifdef USE_NEW_PM
return PA;
#else
return true;
#endif
}
#ifndef USE_NEW_PM
static void registerAFLPass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
PM.add(new AFLCoverage());
}
static RegisterStandardPasses RegisterAFLPass(
PassManagerBuilder::EP_OptimizerLast, registerAFLPass);
static RegisterStandardPasses RegisterAFLPass0(
PassManagerBuilder::EP_EnabledOnOptLevel0, registerAFLPass);
#endif

View File

@ -23,6 +23,9 @@ fn main() {
let cmplog_map_h: usize = option_env!("LIBAFL_CMPLOG_MAP_H") let cmplog_map_h: usize = option_env!("LIBAFL_CMPLOG_MAP_H")
.map_or(Ok(32), str::parse) .map_or(Ok(32), str::parse)
.expect("Could not parse LIBAFL_CMPLOG_MAP_H"); .expect("Could not parse LIBAFL_CMPLOG_MAP_H");
let acc_map_size: usize = option_env!("LIBAFL_ACCOUNTING_MAP_SIZE")
.map_or(Ok(65536), str::parse)
.expect("Could not parse LIBAFL_ACCOUNTING_MAP_SIZE");
write!( write!(
constants_file, constants_file,
@ -36,8 +39,10 @@ fn main() {
pub const CMPLOG_MAP_W: usize = {}; pub const CMPLOG_MAP_W: usize = {};
/// The height of the `CmpLog` map /// The height of the `CmpLog` map
pub const CMPLOG_MAP_H: usize = {}; pub const CMPLOG_MAP_H: usize = {};
/// The size of the accounting maps
pub const ACCOUNTING_MAP_SIZE: usize = {};
", ",
edges_map_size, cmp_map_size, cmplog_map_w, cmplog_map_h edges_map_size, cmp_map_size, cmplog_map_w, cmplog_map_h, acc_map_size
) )
.expect("Could not write file"); .expect("Could not write file");
@ -45,6 +50,7 @@ fn main() {
println!("cargo:rerun-if-env-changed=LIBAFL_CMP_MAP_SIZE"); println!("cargo:rerun-if-env-changed=LIBAFL_CMP_MAP_SIZE");
println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_W"); println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_W");
println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_H"); println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_H");
println!("cargo:rerun-if-env-changed=LIBAFL_ACCOUNTING_MAP_SIZE");
//std::env::set_var("CC", "clang"); //std::env::set_var("CC", "clang");
//std::env::set_var("CXX", "clang++"); //std::env::set_var("CXX", "clang++");
@ -95,6 +101,7 @@ fn main() {
cc::Build::new() cc::Build::new()
.file(src_dir.join("coverage.c")) .file(src_dir.join("coverage.c"))
.define("EDGES_MAP_SIZE", Some(&*format!("{}", edges_map_size))) .define("EDGES_MAP_SIZE", Some(&*format!("{}", edges_map_size)))
.define("ACCOUNTING_MAP_SIZE", Some(&*format!("{}", acc_map_size)))
.compile("coverage"); .compile("coverage");
println!("cargo:rerun-if-changed=src/cmplog.h"); println!("cargo:rerun-if-changed=src/cmplog.h");

View File

@ -9,9 +9,10 @@ typedef uint32_t prev_loc_t;
#define CTX_MAX_K 32U #define CTX_MAX_K 32U
extern uint8_t __afl_area_ptr_local[EDGES_MAP_SIZE]; extern uint8_t __afl_area_ptr_local[EDGES_MAP_SIZE];
uint8_t* __afl_area_ptr = __afl_area_ptr_local; uint8_t* __afl_area_ptr = __afl_area_ptr_local;
extern uint32_t __afl_acc_memop_ptr_local[ACCOUNTING_MAP_SIZE];
uint32_t* __afl_acc_memop_ptr = __afl_acc_memop_ptr_local;
// Weak symbols, LLVM Passes overwrites them if we really use it // Weak symbols, LLVM Passes overwrites them if we really use it
#ifdef __linux__ #ifdef __linux__
@ -23,9 +24,8 @@ uint8_t* __token_start = &__start_libafl_token;
uint8_t* __token_stop = &__stop_libafl_token; uint8_t* __token_stop = &__stop_libafl_token;
#endif #endif
//#if defined(__ANDROID__) || defined(__HAIKU__) //#if defined(__ANDROID__) || defined(__HAIKU__)
MAYBE_THREAD_LOCAL prev_loc_t __afl_prev_loc[NGRAM_SIZE_MAX]; MAYBE_THREAD_LOCAL prev_loc_t __afl_prev_loc[NGRAM_SIZE_MAX];
MAYBE_THREAD_LOCAL prev_loc_t __afl_prev_caller[CTX_MAX_K]; MAYBE_THREAD_LOCAL prev_loc_t __afl_prev_caller[CTX_MAX_K];
MAYBE_THREAD_LOCAL uint32_t __afl_prev_ctx; MAYBE_THREAD_LOCAL uint32_t __afl_prev_ctx;
MAYBE_THREAD_LOCAL prev_loc_t __afl_acc_prev_loc;

View File

@ -1,6 +1,6 @@
//! Coverage maps as static mut array //! Coverage maps as static mut array
use crate::EDGES_MAP_SIZE; use crate::{ACCOUNTING_MAP_SIZE, EDGES_MAP_SIZE};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use libafl::{mutators::Tokens, Error}; use libafl::{mutators::Tokens, Error};
@ -9,6 +9,11 @@ use libafl::{mutators::Tokens, Error};
pub static mut __afl_area_ptr_local: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE]; pub static mut __afl_area_ptr_local: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE];
pub use __afl_area_ptr_local as EDGES_MAP; pub use __afl_area_ptr_local as EDGES_MAP;
/// The map for accounting mem writes.
#[no_mangle]
pub static mut __afl_acc_memop_ptr_local: [u32; ACCOUNTING_MAP_SIZE] = [0; ACCOUNTING_MAP_SIZE];
pub use __afl_acc_memop_ptr_local as ACCOUNTING_MEMOP_MAP;
/// The max count of edges tracked. /// The max count of edges tracked.
pub static mut MAX_EDGES_NUM: usize = 0; pub static mut MAX_EDGES_NUM: usize = 0;
@ -16,6 +21,9 @@ extern "C" {
/// The area pointer points to the edges map. /// The area pointer points to the edges map.
pub static mut __afl_area_ptr: *mut u8; pub static mut __afl_area_ptr: *mut u8;
/// The area pointer points to the accounting mem operations map.
pub static mut __afl_acc_memop_ptr: *mut u32;
/// Start of libafl token section /// Start of libafl token section
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub static __token_start: *const u8; pub static __token_start: *const u8;
@ -24,6 +32,7 @@ extern "C" {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub static __token_stop: *const u8; pub static __token_stop: *const u8;
} }
pub use __afl_acc_memop_ptr as ACCOUNTING_MEMOP_MAP_PTR;
pub use __afl_area_ptr as EDGES_MAP_PTR; pub use __afl_area_ptr as EDGES_MAP_PTR;
/// Return Tokens from the compile-time token section /// Return Tokens from the compile-time token section