More LLVM passes from AFL++ (#394)

* afl coverage pass

* working ctx coverage

* MAYBE_THREAD_LOCAL

* doh

* fix for msvc

* ci

* clippy

* atheris

* thread_local
This commit is contained in:
Andrea Fioraldi 2021-12-01 13:27:39 +01:00 committed by GitHub
parent cf5b4dfb18
commit 2fb1c3520a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1587 additions and 89 deletions

View File

@ -63,10 +63,10 @@ jobs:
# cargo-hack's --feature-powerset would be nice here but libafl has a too many knobs
- name: Check each feature
# Skipping python as it has to be built with the `maturin` tool
run: cargo hack check --feature-powerset --depth=2 --exclude-features=agpl,nautilus,python,sancov_pcguard_edges,sancov_pcguard_edges_ptr --no-dev-deps
run: cargo hack check --feature-powerset --depth=2 --exclude-features=agpl,nautilus,python,sancov_pcguard_edges --no-dev-deps
# pcguard edges and pcguard hitcounts are not compatible and we need to build them seperately
- name: Check pcguard edges
run: cargo check --features=sancov_pcguard_edges,sancov_pcguard_edges_ptr
run: cargo check --features=sancov_pcguard_edges
- name: Build examples
run: cargo build --examples --verbose
- uses: actions/checkout@v2

View File

@ -21,7 +21,7 @@ num_cpus = "1.0"
[dependencies]
libafl = { path = "../../libafl/" }
libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts_ptr", "sancov_cmplog", "libfuzzer"] }
libafl_targets = { path = "../../libafl_targets/", features = ["pointer_maps", "sancov_cmplog", "libfuzzer"] }
clap = { version = "3.0.0-beta.4", features = ["default", "yaml"] }
[lib]

View File

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

View File

@ -0,0 +1,31 @@
[package]
name = "libfuzzer_libpng_launcher"
version = "0.6.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", "anymap_debug", "derive", "llmp_compression", "introspection"] }
libafl_targets = { path = "../../libafl_targets/", features = ["libfuzzer"] }
# TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" }
clap = { version = "3.0.0-beta.2", features = ["yaml"] }
[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 &
short_test: all
rm -rf libafl_unix_shmem_server || true
timeout 10s ./$(FUZZER_NAME) --cores 0 &
test: all
timeout 60s ./$(FUZZER_NAME) --cores 0 &

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,37 @@
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)
.from_args(&args)
.expect("Failed to parse the command line")
.add_pass(LLVMPasses::AFLCoverage)
.add_arg("-mllvm")
.add_arg("-ctx") // Context sensitive coverage
.link_staticlib(&dir, "libfuzzer_libpng")
.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,12 @@
name: libfuzzer libpng
version: "0.1.0"
author: "Andrea Fioraldi <andreafioraldi@gmail.com>, Dominik Maier <domenukk@gmail.com>"
about: A clone of libfuzzer using libafl for a libpng harness.
args:
- cores:
short: c
long: cores
about: "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."
value_name: CORES
required: true
takes_value: true

View File

@ -0,0 +1,183 @@
//! 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 clap::{load_yaml, App};
use core::time::Duration;
use std::{env, path::PathBuf};
use libafl::{
bolts::{
current_nanos,
launcher::Launcher,
os::parse_core_bind_arg,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
tuples::{tuple_list, Merge},
},
corpus::{
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, 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::{edges_map_from_ptr, libfuzzer_initialize, libfuzzer_test_one_input};
/// 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 yaml = load_yaml!("clap-config.yaml");
let matches = App::from(yaml).get_matches();
let broker_port = 1337;
let cores = parse_core_bind_arg(matches.value_of("cores").unwrap())
.expect("No valid core count given!");
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| {
let corpus_dirs = &[PathBuf::from("./corpus")];
let objective_dir = PathBuf::from("./crashes");
// Create an observation channel using the coverage map
let edges = edges_map_from_ptr();
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(objective_dir).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::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 = 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 = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
// 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
Duration::new(10, 0),
);
// 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, corpus_dirs)
.unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", corpus_dirs));
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)
.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

@ -62,9 +62,17 @@ fn main() {
let out_dir = Path::new(&out_dir);
let src_dir = Path::new("src");
let mut custom_flags = vec![];
let dest_path = Path::new(&out_dir).join("clang_constants.rs");
let mut clang_constants_file = File::create(&dest_path).expect("Could not create file");
let edges_map_size: usize = option_env!("LIBAFL_EDGES_MAP_SIZE")
.map_or(Ok(65536), str::parse)
.expect("Could not parse LIBAFL_EDGES_MAP_SIZE");
println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_SIZE");
custom_flags.push(format!("-DLIBAFL_EDGES_MAP_SIZE={}", edges_map_size));
let llvm_config = find_llvm_config();
if let Ok(output) = Command::new(&llvm_config).args(&["--bindir"]).output() {
@ -80,14 +88,16 @@ fn main() {
pub const CLANG_PATH: &str = {:?};
pub const CLANGXX_PATH: &str = {:?};
/// The size of the edges map
pub const EDGES_MAP_SIZE: usize = {};
",
llvm_bindir.join("clang"),
llvm_bindir.join("clang++")
llvm_bindir.join("clang++"),
edges_map_size
)
.expect("Could not write file");
println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc");
let output = Command::new(&llvm_config)
.args(&["--cxxflags"])
.output()
@ -110,14 +120,28 @@ fn main() {
ldflags.push("dynamic_lookup");
};
println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc");
println!("cargo:rerun-if-changed=src/afl-coverage-pass.cc");
let _ = Command::new(llvm_bindir.join("clang++"))
.args(&cxxflags)
.args(&custom_flags)
.arg(src_dir.join("cmplog-routines-pass.cc"))
.args(&ldflags)
.args(&["-fPIC", "-shared", "-o"])
.arg(out_dir.join(format!("cmplog-routines-pass.{}", dll_extension())))
.status()
.expect("Failed to compile cmplog-routines-pass.cc");
let _ = Command::new(llvm_bindir.join("clang++"))
.args(&cxxflags)
.args(&custom_flags)
.arg(src_dir.join("afl-coverage-pass.cc"))
.args(&ldflags)
.args(&["-fPIC", "-shared", "-o"])
.arg(out_dir.join(format!("afl-coverage-pass.{}", dll_extension())))
.status()
.expect("Failed to compile afl-coverage-pass.cc");
} else {
write!(
&mut clang_constants_file,

View File

@ -0,0 +1,842 @@
/*
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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <list>
#include <string>
#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/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/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;
/* Maximum ngram size */
#define NGRAM_SIZE_MAX 16U
/* Maximum K for top-K context sensitivity */
#define CTX_MAX_K 32U
#define MAP_SIZE LIBAFL_EDGES_MAP_SIZE
#define FATAL(...) do { fprintf(stderr, "FATAL: " __VA_ARGS__); exit(1); } while (0)
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> NotZero("not_zero", cl::desc("Never hit 0 again in the hitcount"), cl::init(true), cl::NotHidden);
static cl::opt<uint32_t> Ngram("ngram", cl::desc("Size of the Ngram instrumentation (0 to disable)"), cl::init(0), cl::NotHidden);
static cl::opt<uint32_t> CtxK("ctx_k", cl::desc("Size of the context for K-Ctx context sensitivity (0 to disable)"), cl::init(0), cl::NotHidden);
static cl::opt<bool> Ctx("ctx", cl::desc("Enable full context sensitive coverage"), cl::init(false), 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, "AFLCoverage", "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 == "AFLCoverage" ) {
MPM.addPass(AFLCoverage());
return true;
} else {
return false;
}
}
);
#endif
}
};
}
#else
char AFLCoverage::ID = 0;
#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
PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) {
#else
bool AFLCoverage::runOnModule(Module &M) {
#endif
LLVMContext &C = M.getContext();
IntegerType *Int8Ty = IntegerType::getInt8Ty(C);
IntegerType *Int32Ty = IntegerType::getInt32Ty(C);
#ifdef HAVE_VECTOR_INTRINSICS
IntegerType *IntLocTy =
IntegerType::getIntNTy(C, sizeof(prev_loc_t) * CHAR_BIT);
#endif
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);
/*
char *ptr;
if ((ptr = getenv("AFL_MAP_SIZE")) || (ptr = getenv("AFL_MAPSIZE"))) {
map_size = atoi(ptr);
if (map_size < 8 || map_size > (1 << 29))
FATAL("illegal AFL_MAP_SIZE %u, must be between 2^3 and 2^30",
map_size); if (map_size % 8) map_size = (((map_size >> 3) + 1) << 3);
}
*/
/* Decide instrumentation ratio */
if (!InstRatio || InstRatio > 100)
FATAL("Bad value of the instrumentation ratio (must be between 1 and 100)");
unsigned PrevLocSize = 0;
unsigned PrevCallerSize = 0;
bool instrument_ctx = Ctx || CtxK > 0;
bool instrument_caller = false;
#ifdef HAVE_VECTOR_INTRINSICS
/* Decide previous location vector size (must be a power of two) */
VectorType *PrevLocTy = NULL;
if (Ngram && (Ngram < 2 || Ngram > NGRAM_SIZE_MAX))
FATAL(
"Bad value of the Ngram size (must be between 2 and NGRAM_SIZE_MAX "
"(%u))",
NGRAM_SIZE_MAX);
if (Ngram)
PrevLocSize = Ngram - 1;
else
PrevLocSize = 1;
/* Decide K-Ctx vector size (must be a power of two) */
VectorType *PrevCallerTy = NULL;
if (CtxK > CTX_MAX_K)
FATAL("Bad value of K for K-context sensitivity (must be between 1 and CTX_MAX_K (%u))",
CTX_MAX_K);
if (CtxK == 1) {
CtxK = 0;
instrument_ctx = true;
instrument_caller = true; // Enable CALLER instead
}
if (CtxK) {
PrevCallerSize = CtxK;
instrument_ctx = true;
}
#else
if (Ngram)
#ifndef LLVM_VERSION_PATCH
FATAL(
"Sorry, NGRAM branch coverage is not supported with llvm version "
"%d.%d.%d!",
LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, 0);
#else
FATAL(
"Sorry, NGRAM branch coverage is not supported with llvm version "
"%d.%d.%d!",
LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, LLVM_VERSION_PATCH);
#endif
if (CtxK)
#ifndef LLVM_VERSION_PATCH
FATAL(
"Sorry, K-CTX branch coverage is not supported with llvm version "
"%d.%d.%d!",
LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, 0);
#else
FATAL(
"Sorry, K-CTX branch coverage is not supported with llvm version "
"%d.%d.%d!",
LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, LLVM_VERSION_PATCH);
#endif
PrevLocSize = 1;
#endif
#ifdef HAVE_VECTOR_INTRINSICS
int PrevLocVecSize = PowerOf2Ceil(PrevLocSize);
if (Ngram)
PrevLocTy = VectorType::get(IntLocTy, PrevLocVecSize
#if LLVM_VERSION_MAJOR >= 12
,
false
#endif
);
#endif
#ifdef HAVE_VECTOR_INTRINSICS
int PrevCallerVecSize = PowerOf2Ceil(PrevCallerSize);
if (CtxK)
PrevCallerTy = VectorType::get(IntLocTy, PrevCallerVecSize
#if LLVM_VERSION_MAJOR >= 12
,
false
#endif
);
#endif
/* Get globals for the SHM region and the previous location. Note that
__afl_prev_loc is thread-local. */
GlobalVariable *AFLMapPtr =
new GlobalVariable(M, PointerType::get(Int8Ty, 0), false,
GlobalValue::ExternalLinkage, 0, "__afl_area_ptr");
GlobalVariable *AFLPrevLoc;
GlobalVariable *AFLPrevCaller;
GlobalVariable *AFLContext = NULL;
if (Ctx || instrument_caller)
#if defined(__ANDROID__) || defined(__HAIKU__)
AFLContext = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_ctx");
#else
AFLContext = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_ctx", 0,
GlobalVariable::GeneralDynamicTLSModel, 0, false);
#endif
#ifdef HAVE_VECTOR_INTRINSICS
if (Ngram)
#if defined(__ANDROID__) || defined(__HAIKU__)
AFLPrevLoc = new GlobalVariable(
M, PrevLocTy, /* isConstant */ false, GlobalValue::ExternalLinkage,
/* Initializer */ nullptr, "__afl_prev_loc");
#else
AFLPrevLoc = new GlobalVariable(
M, PrevLocTy, /* isConstant */ false, GlobalValue::ExternalLinkage,
/* Initializer */ nullptr, "__afl_prev_loc",
/* InsertBefore */ nullptr, GlobalVariable::GeneralDynamicTLSModel,
/* AddressSpace */ 0, /* IsExternallyInitialized */ false);
#endif
else
#endif
#if defined(__ANDROID__) || defined(__HAIKU__)
AFLPrevLoc = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc");
#else
AFLPrevLoc = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc", 0,
GlobalVariable::GeneralDynamicTLSModel, 0, false);
#endif
#ifdef HAVE_VECTOR_INTRINSICS
if (CtxK)
#if defined(__ANDROID__) || defined(__HAIKU__)
AFLPrevCaller = new GlobalVariable(
M, PrevCallerTy, /* isConstant */ false, GlobalValue::ExternalLinkage,
/* Initializer */ nullptr, "__afl_prev_caller");
#else
AFLPrevCaller = new GlobalVariable(
M, PrevCallerTy, /* isConstant */ false, GlobalValue::ExternalLinkage,
/* Initializer */ nullptr, "__afl_prev_caller",
/* InsertBefore */ nullptr, GlobalVariable::GeneralDynamicTLSModel,
/* AddressSpace */ 0, /* IsExternallyInitialized */ false);
#endif
else
#endif
#if defined(__ANDROID__) || defined(__HAIKU__)
AFLPrevCaller =
new GlobalVariable(M, Int32Ty, false, GlobalValue::ExternalLinkage, 0,
"__afl_prev_caller");
#else
AFLPrevCaller = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_caller",
0, GlobalVariable::GeneralDynamicTLSModel, 0, false);
#endif
#ifdef HAVE_VECTOR_INTRINSICS
/* Create the vector shuffle mask for updating the previous block history.
Note that the first element of the vector will store cur_loc, so just set
it to undef to allow the optimizer to do its thing. */
SmallVector<Constant *, 32> PrevLocShuffle = {UndefValue::get(Int32Ty)};
for (unsigned I = 0; I < PrevLocSize - 1; ++I)
PrevLocShuffle.push_back(ConstantInt::get(Int32Ty, I));
for (int I = PrevLocSize; I < PrevLocVecSize; ++I)
PrevLocShuffle.push_back(ConstantInt::get(Int32Ty, PrevLocSize));
Constant *PrevLocShuffleMask = ConstantVector::get(PrevLocShuffle);
Constant * PrevCallerShuffleMask = NULL;
SmallVector<Constant *, 32> PrevCallerShuffle = {UndefValue::get(Int32Ty)};
if (CtxK) {
for (unsigned I = 0; I < PrevCallerSize - 1; ++I)
PrevCallerShuffle.push_back(ConstantInt::get(Int32Ty, I));
for (int I = PrevCallerSize; I < PrevCallerVecSize; ++I)
PrevCallerShuffle.push_back(ConstantInt::get(Int32Ty, PrevCallerSize));
PrevCallerShuffleMask = ConstantVector::get(PrevCallerShuffle);
}
#endif
// other constants we need
ConstantInt *One = ConstantInt::get(Int8Ty, 1);
Value * PrevCtx = NULL; // CTX sensitive coverage
LoadInst *PrevCaller = NULL; // K-CTX coverage
/* 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));
// Context sensitive coverage
if (instrument_ctx && &BB == &F.getEntryBlock()) {
#ifdef HAVE_VECTOR_INTRINSICS
if (CtxK) {
PrevCaller = IRB.CreateLoad(AFLPrevCaller);
PrevCaller->setMetadata(M.getMDKindID("nosanitize"),
MDNode::get(C, None));
PrevCtx =
IRB.CreateZExt(IRB.CreateXorReduce(PrevCaller), IRB.getInt32Ty());
} else
#endif
{
// load the context ID of the previous function and write to to a
// local variable on the stack
LoadInst *PrevCtxLoad = IRB.CreateLoad(AFLContext);
PrevCtxLoad->setMetadata(M.getMDKindID("nosanitize"),
MDNode::get(C, None));
PrevCtx = PrevCtxLoad;
}
// does the function have calls? and is any of the calls larger than one
// basic block?
for (auto &BB_2 : F) {
if (has_calls) break;
for (auto &IN : BB_2) {
CallInst *callInst = nullptr;
if ((callInst = dyn_cast<CallInst>(&IN))) {
Function *Callee = callInst->getCalledFunction();
if (!Callee || Callee->size() < function_minimum_size)
continue;
else {
has_calls = 1;
break;
}
}
}
}
// if yes we store a context ID for this function in the global var
if (has_calls) {
Value *NewCtx = ConstantInt::get(Int32Ty, RandBelow(map_size));
#ifdef HAVE_VECTOR_INTRINSICS
if (CtxK) {
Value *ShuffledPrevCaller = IRB.CreateShuffleVector(
PrevCaller, UndefValue::get(PrevCallerTy),
PrevCallerShuffleMask);
Value *UpdatedPrevCaller = IRB.CreateInsertElement(
ShuffledPrevCaller, NewCtx, (uint64_t)0);
StoreInst *Store =
IRB.CreateStore(UpdatedPrevCaller, AFLPrevCaller);
Store->setMetadata(M.getMDKindID("nosanitize"),
MDNode::get(C, None));
} else
#endif
{
if (Ctx) NewCtx = IRB.CreateXor(PrevCtx, NewCtx);
StoreInst *StoreCtx = IRB.CreateStore(NewCtx, AFLContext);
StoreCtx->setMetadata(M.getMDKindID("nosanitize"),
MDNode::get(C, None));
}
}
}
if (RandBelow(100) >= InstRatio) continue;
/* Make up cur_loc */
// cur_loc++;
cur_loc = RandBelow(map_size);
/* There is a problem with Ubuntu 18.04 and llvm 6.0 (see issue #63).
The inline function successors() is not inlined and also not found at runtime
:-( As I am unable to detect Ubuntu18.04 heree, the next best thing is to
disable this optional optimization for LLVM 6.0.0 and Linux */
#if !(LLVM_VERSION_MAJOR == 6 && LLVM_VERSION_MINOR == 0) || !defined __linux__
// only instrument if this basic block is the destination of a previous
// basic block that has multiple successors
// this gets rid of ~5-10% of instrumentations that are unnecessary
// result: a little more speed and less map pollution
int more_than_one = -1;
// fprintf(stderr, "BB %u: ", cur_loc);
for (pred_iterator PI = pred_begin(&BB), E = pred_end(&BB); PI != E;
++PI) {
BasicBlock *Pred = *PI;
int count = 0;
if (more_than_one == -1) more_than_one = 0;
// fprintf(stderr, " %p=>", Pred);
for (succ_iterator SI = succ_begin(Pred), E = succ_end(Pred); SI != E;
++SI) {
BasicBlock *Succ = *SI;
// if (count > 0)
// fprintf(stderr, "|");
if (Succ != NULL) count++;
// fprintf(stderr, "%p", Succ);
}
if (count > 1) more_than_one = 1;
}
// fprintf(stderr, " == %d\n", more_than_one);
if (F.size() > 1 && more_than_one != 1) {
// in CTX mode we have to restore the original context for the caller -
// she might be calling other functions which need the correct CTX
if (instrument_ctx && has_calls) {
Instruction *Inst = BB.getTerminator();
if (isa<ReturnInst>(Inst) || isa<ResumeInst>(Inst)) {
IRBuilder<> Post_IRB(Inst);
StoreInst *RestoreCtx;
#ifdef HAVE_VECTOR_INTRINSICS
if (CtxK)
RestoreCtx = IRB.CreateStore(PrevCaller, AFLPrevCaller);
else
#endif
RestoreCtx = Post_IRB.CreateStore(PrevCtx, AFLContext);
RestoreCtx->setMetadata(M.getMDKindID("nosanitize"),
MDNode::get(C, None));
}
}
continue;
}
#endif
ConstantInt *CurLoc;
#ifdef HAVE_VECTOR_INTRINSICS
if (Ngram)
CurLoc = ConstantInt::get(IntLocTy, cur_loc);
else
#endif
CurLoc = ConstantInt::get(Int32Ty, cur_loc);
/* Load prev_loc */
LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc);
PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
Value *PrevLocTrans;
#ifdef HAVE_VECTOR_INTRINSICS
/* "For efficiency, we propose to hash the tuple as a key into the
hit_count map as (prev_block_trans << 1) ^ curr_block_trans, where
prev_block_trans = (block_trans_1 ^ ... ^ block_trans_(n-1)" */
if (Ngram)
PrevLocTrans =
IRB.CreateZExt(IRB.CreateXorReduce(PrevLoc), IRB.getInt32Ty());
else
#endif
PrevLocTrans = PrevLoc;
if (instrument_ctx)
PrevLocTrans =
IRB.CreateZExt(IRB.CreateXor(PrevLocTrans, PrevCtx), Int32Ty);
else
PrevLocTrans = IRB.CreateZExt(PrevLocTrans, IRB.getInt32Ty());
/* Load SHM pointer */
LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr);
MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
Value *MapPtrIdx;
#ifdef HAVE_VECTOR_INTRINSICS
if (Ngram)
MapPtrIdx = IRB.CreateGEP(
MapPtr,
IRB.CreateZExt(
IRB.CreateXor(PrevLocTrans, IRB.CreateZExt(CurLoc, Int32Ty)),
Int32Ty));
else
#endif
MapPtrIdx = IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocTrans, CurLoc));
/* Update bitmap */
if (ThreadSafe) { /* Atomic */
/*
#if LLVM_VERSION_MAJOR < 9
if (neverZero_counters_str !=
NULL) { // with llvm 9 we make this the default as the bug
in llvm
// is then fixed
#else
if (NotZero) {
#endif
// register MapPtrIdx in a todo list
todo.push_back(MapPtrIdx);
} else {
*/
IRB.CreateAtomicRMW(llvm::AtomicRMWInst::BinOp::Add, MapPtrIdx, One,
#if LLVM_VERSION_MAJOR >= 13
llvm::MaybeAlign(1),
#endif
llvm::AtomicOrdering::Monotonic);
/*
}
*/
} else {
LoadInst *Counter = IRB.CreateLoad(MapPtrIdx);
Counter->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
Value *Incr = IRB.CreateAdd(Counter, One);
#if LLVM_VERSION_MAJOR < 9
if (neverZero_counters_str !=
NULL) { // with llvm 9 we make this the default as the bug in llvm
// is then fixed
#else
if (NotZero) {
#endif
/* hexcoder: Realize a counter that skips zero during overflow.
* Once this counter reaches its maximum value, it next increments to
* 1
*
* Instead of
* Counter + 1 -> Counter
* we inject now this
* Counter + 1 -> {Counter, OverflowFlag}
* Counter + OverflowFlag -> Counter
*/
ConstantInt *Zero = ConstantInt::get(Int8Ty, 0);
auto cf = IRB.CreateICmpEQ(Incr, Zero);
auto carry = IRB.CreateZExt(cf, Int8Ty);
Incr = IRB.CreateAdd(Incr, carry);
}
IRB.CreateStore(Incr, MapPtrIdx)
->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
} /* non atomic case */
/* Update prev_loc history vector (by placing cur_loc at the head of the
vector and shuffle the other elements back by one) */
StoreInst *Store;
#ifdef HAVE_VECTOR_INTRINSICS
if (Ngram) {
Value *ShuffledPrevLoc = IRB.CreateShuffleVector(
PrevLoc, UndefValue::get(PrevLocTy), PrevLocShuffleMask);
Value *UpdatedPrevLoc = IRB.CreateInsertElement(
ShuffledPrevLoc, IRB.CreateLShr(CurLoc, (uint64_t)1), (uint64_t)0);
Store = IRB.CreateStore(UpdatedPrevLoc, AFLPrevLoc);
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
} else
#endif
{
Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1),
AFLPrevLoc);
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
}
// in CTX mode we have to restore the original context for the caller -
// she might be calling other functions which need the correct CTX.
// Currently this is only needed for the Ubuntu clang-6.0 bug
if (instrument_ctx && has_calls) {
Instruction *Inst = BB.getTerminator();
if (isa<ReturnInst>(Inst) || isa<ResumeInst>(Inst)) {
IRBuilder<> Post_IRB(Inst);
StoreInst *RestoreCtx;
#ifdef HAVE_VECTOR_INTRINSICS
if (CtxK)
RestoreCtx = IRB.CreateStore(PrevCaller, AFLPrevCaller);
else
#endif
RestoreCtx = Post_IRB.CreateStore(PrevCtx, AFLContext);
RestoreCtx->setMetadata(M.getMDKindID("nosanitize"),
MDNode::get(C, None));
}
}
inst_blocks++;
}
}
/* Say something nice. */
/*if (!be_quiet) {
if (!inst_blocks)
WARNF("No instrumentation targets found.");
else {
char modeline[100];
snprintf(modeline, sizeof(modeline), "%s%s%s%s%s",
getenv("AFL_HARDEN") ? "hardened" : "non-hardened",
getenv("AFL_USE_ASAN") ? ", ASAN" : "",
getenv("AFL_USE_MSAN") ? ", MSAN" : "",
getenv("AFL_USE_CFISAN") ? ", CFISAN" : "",
getenv("AFL_USE_UBSAN") ? ", UBSAN" : "");
OKF("Instrumented %d locations (%s mode, ratio %u%%).", inst_blocks,
modeline, InstRatio);
}
}*/
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

@ -26,6 +26,7 @@ include!(concat!(env!("OUT_DIR"), "/clang_constants.rs"));
pub enum LLVMPasses {
//CmpLogIns,
CmpLogRtn,
AFLCoverage,
}
impl LLVMPasses {
@ -34,6 +35,8 @@ impl LLVMPasses {
match self {
LLVMPasses::CmpLogRtn => PathBuf::from(env!("OUT_DIR"))
.join(format!("cmplog-routines-pass.{}", dll_extension())),
LLVMPasses::AFLCoverage => PathBuf::from(env!("OUT_DIR"))
.join(format!("afl-coverage-pass.{}", dll_extension())),
}
}
}
@ -210,6 +213,11 @@ impl CompilerWrapper for ClangWrapper {
}
args.extend_from_slice(self.link_args.as_slice());
if cfg!(unix) {
args.push("-pthread".into());
args.push("-ldl".into());
}
} else {
args.extend_from_slice(self.cc_args.as_slice());
}

View File

@ -20,7 +20,7 @@ cc = { version = "1.0", features = ["parallel"] }
[dependencies]
libafl = { path = "../libafl", version = "0.6.1", features = ["std", "libafl_derive"] }
libafl_targets = { path = "../libafl_targets", version = "0.6.1", features = ["sancov_cmplog"] }
libafl_targets = { path = "../libafl_targets", version = "0.6.1", features = ["std", "sancov_cmplog"] }
nix = "0.23.0"
libc = "0.2"
hashbrown = "0.11"

View File

@ -11,12 +11,12 @@ keywords = ["fuzzing", "testing"]
edition = "2021"
[features]
default = []
default = ["std"]
std = []
libfuzzer = []
pointer_maps = []
sancov_pcguard_edges = []
sancov_pcguard_hitcounts = []
sancov_pcguard_edges_ptr = []
sancov_pcguard_hitcounts_ptr = []
sancov_value_profile = []
sancov_8bit = []
sancov_cmplog = []

View File

@ -28,14 +28,14 @@ fn main() {
&mut constants_file,
"// These constants are autogenerated by build.rs
/// The size of the edges map
pub const EDGES_MAP_SIZE: usize = {};
/// The size of the cmps map
pub const CMP_MAP_SIZE: usize = {};
/// The width of the `CmpLog` map
pub const CMPLOG_MAP_W: usize = {};
/// The height of the `CmpLog` map
pub const CMPLOG_MAP_H: usize = {};
/// The size of the edges map
pub const EDGES_MAP_SIZE: usize = {};
/// The size of the cmps map
pub const CMP_MAP_SIZE: usize = {};
/// The width of the `CmpLog` map
pub const CMPLOG_MAP_W: usize = {};
/// The height of the `CmpLog` map
pub const CMPLOG_MAP_H: usize = {};
",
edges_map_size, cmp_map_size, cmplog_map_w, cmplog_map_h
)
@ -90,6 +90,13 @@ pub const CMPLOG_MAP_H: usize = {};
.file(src_dir.join("common.c"))
.compile("common");
println!("cargo:rerun-if-changed=src/coverage.c");
cc::Build::new()
.file(src_dir.join("coverage.c"))
.define("EDGES_MAP_SIZE", Some(&*format!("{}", edges_map_size)))
.compile("coverage");
println!("cargo:rerun-if-changed=src/cmplog.h");
println!("cargo:rerun-if-changed=src/cmplog.c");

View File

@ -8,6 +8,37 @@
#define STATIC_ASSERT(pred) switch(0){case 0:case pred:;}
// From https://stackoverflow.com/a/18298965
#if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__
#define THREAD_LOCAL _Thread_local
#elif defined _WIN32 && ( \
defined _MSC_VER || \
defined __ICL || \
defined __DMC__ || \
defined __BORLANDC__ )
#define THREAD_LOCAL __declspec(thread)
/* note that ICC (linux) and Clang are covered by __GNUC__ */
#elif defined __GNUC__ || \
defined __SUNPRO_C || \
defined __xlC__
#define THREAD_LOCAL __thread
#endif
#if defined(__ANDROID__) || defined(__HAIKU__)
#undef THREAD_LOCAL
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
#undef THREAD_LOCAL
#endif
#endif
#ifdef THREAD_LOCAL
#define MAYBE_THREAD_LOCAL THREAD_LOCAL
#else
#define MAYBE_THREAD_LOCAL
#endif
#ifdef _WIN32
#define RETADDR (uintptr_t)_ReturnAddress()
#define EXPORT_FN __declspec(dllexport)

View File

@ -0,0 +1,17 @@
#include "common.h"
typedef uint32_t prev_loc_t;
/* Maximum ngram size */
#define NGRAM_SIZE_MAX 16U
/* Maximum K for top-K context sensitivity */
#define CTX_MAX_K 32U
extern uint8_t __afl_area_ptr_local[EDGES_MAP_SIZE];
uint8_t* __afl_area_ptr = __afl_area_ptr_local;
//#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_caller[CTX_MAX_K];
MAYBE_THREAD_LOCAL uint32_t __afl_prev_ctx;

View File

@ -1,19 +1,47 @@
//! Coverage maps as static mut array
use crate::EDGES_MAP_SIZE;
#[cfg(any(
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
use core::ptr;
use core::slice::from_raw_parts_mut;
/// The map for edges.
pub static mut EDGES_MAP: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE];
#[cfg(any(
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
pub static mut EDGES_MAP_PTR: *mut u8 = ptr::null_mut();
#[no_mangle]
pub static mut __afl_area_ptr_local: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE];
pub use __afl_area_ptr_local as EDGES_MAP;
/// The max count of edges tracked.
pub static mut MAX_EDGES_NUM: usize = 0;
extern "C" {
pub static mut __afl_area_ptr: *mut u8;
}
pub use __afl_area_ptr as EDGES_MAP_PTR;
#[no_mangle]
pub static mut __afl_map_size: usize = EDGES_MAP_SIZE;
pub use __afl_map_size as EDGES_MAP_PTR_SIZE;
#[must_use]
pub fn edges_map_from_ptr<'a>() -> &'a mut [u8] {
unsafe {
assert!(!EDGES_MAP_PTR.is_null());
from_raw_parts_mut(EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE)
}
}
#[must_use]
pub fn edges_max_num() -> usize {
unsafe {
if MAX_EDGES_NUM > 0 {
MAX_EDGES_NUM
} else {
#[cfg(feature = "pointer_maps")]
{
EDGES_MAP_PTR_SIZE
}
#[cfg(not(feature = "pointer_maps"))]
{
EDGES_MAP.len()
}
}
}
}

View File

@ -45,7 +45,7 @@ impl<'a> DrCovWriter<'a> {
) -> Self {
Self {
writer: BufWriter::new(
File::create(path).expect("unable to create file for coverage data"),
File::create(path).expect("Unable to create file for coverage data"),
),
module_mapping,
basic_blocks,

View File

@ -5,19 +5,9 @@ extern crate alloc;
include!(concat!(env!("OUT_DIR"), "/constants.rs"));
#[cfg(any(
feature = "sancov_pcguard_edges",
feature = "sancov_pcguard_hitcounts",
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
#[cfg(any(feature = "sancov_pcguard_edges", feature = "sancov_pcguard_hitcounts",))]
pub mod sancov_pcguard;
#[cfg(any(
feature = "sancov_pcguard_edges",
feature = "sancov_pcguard_hitcounts",
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
#[cfg(any(feature = "sancov_pcguard_edges", feature = "sancov_pcguard_hitcounts",))]
pub use sancov_pcguard::*;
#[cfg(any(feature = "sancov_cmplog", feature = "sancov_value_profile"))]
@ -44,4 +34,5 @@ pub use value_profile::*;
pub mod cmplog;
pub use cmplog::*;
#[cfg(feature = "std")]
pub mod drcov;

View File

@ -1,20 +1,8 @@
//! [`LLVM` `PcGuard`](https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards) runtime for `LibAFL`.
#[cfg(any(
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
use crate::coverage::EDGES_MAP_PTR;
use crate::coverage::{EDGES_MAP, MAX_EDGES_NUM};
#[cfg(all(
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
#[cfg(not(any(doc, feature = "clippy")))]
compile_error!(
"the libafl_targets `sancov_pcguard_edges_ptr` and `sancov_pcguard_hitcounts_ptr` features are mutually exclusive."
);
#[cfg(feature = "pointer_maps")]
use crate::coverage::{EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE};
#[cfg(all(feature = "sancov_pcguard_edges", feature = "sancov_pcguard_hitcounts"))]
#[cfg(not(any(doc, feature = "clippy")))]
@ -27,33 +15,33 @@ compile_error!(
/// # Safety
/// Dereferences `guard`, reads the position from there, then dereferences the [`EDGES_MAP`] at that position.
/// Should usually not be called directly.
#[cfg(any(
feature = "sancov_pcguard_edges",
feature = "sancov_pcguard_hitcounts",
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
#[no_mangle]
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
let pos = *guard as usize;
#[cfg(feature = "sancov_pcguard_edges")]
#[cfg(feature = "pointer_maps")]
{
*EDGES_MAP.get_unchecked_mut(pos) = 1;
#[cfg(feature = "sancov_pcguard_edges")]
{
(EDGES_MAP_PTR as *mut u8).add(pos).write(1);
}
#[cfg(feature = "sancov_pcguard_hitcounts")]
{
let addr = (EDGES_MAP_PTR as *mut u8).add(pos);
let val = addr.read().wrapping_add(1);
addr.write(val);
}
}
#[cfg(feature = "sancov_pcguard_hitcounts")]
#[cfg(not(feature = "pointer_maps"))]
{
let val = (*EDGES_MAP.get_unchecked(pos) as u8).wrapping_add(1);
*EDGES_MAP.get_unchecked_mut(pos) = val;
}
#[cfg(feature = "sancov_pcguard_edges_ptr")]
{
(EDGES_MAP_PTR as *mut u8).add(pos).write(1);
}
#[cfg(feature = "sancov_pcguard_hitcounts_ptr")]
{
let addr = (EDGES_MAP_PTR as *mut u8).add(pos);
let val = addr.read().wrapping_add(1);
addr.write(val);
#[cfg(feature = "sancov_pcguard_edges")]
{
*EDGES_MAP.get_unchecked_mut(pos) = 1;
}
#[cfg(feature = "sancov_pcguard_hitcounts")]
{
let val = (*EDGES_MAP.get_unchecked(pos) as u8).wrapping_add(1);
*EDGES_MAP.get_unchecked_mut(pos) = val;
}
}
}
@ -61,20 +49,12 @@ pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
///
/// # Safety
/// Dereferences at `start` and writes to it.
#[cfg(any(
feature = "sancov_pcguard_edges",
feature = "sancov_pcguard_hitcounts",
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
#[no_mangle]
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32, stop: *mut u32) {
#[cfg(any(
feature = "sancov_pcguard_edges_ptr",
feature = "sancov_pcguard_hitcounts_ptr"
))]
#[cfg(feature = "pointer_maps")]
if EDGES_MAP_PTR.is_null() {
EDGES_MAP_PTR = EDGES_MAP.as_mut_ptr();
EDGES_MAP_PTR_SIZE = EDGES_MAP.len();
}
if start == stop || *start != 0 {
@ -84,8 +64,15 @@ pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32
while start < stop {
*start = MAX_EDGES_NUM as u32;
start = start.offset(1);
MAX_EDGES_NUM = MAX_EDGES_NUM.wrapping_add(1);
assert!((MAX_EDGES_NUM <= EDGES_MAP.len()), "The number of edges reported by SanitizerCoverage exceed the size of the edges map ({}). Use the LIBAFL_EDGES_MAP_SIZE env to increase it at compile time.", EDGES_MAP.len());
#[cfg(feature = "pointer_maps")]
{
MAX_EDGES_NUM = MAX_EDGES_NUM.wrapping_add(1) % EDGES_MAP_PTR_SIZE;
}
#[cfg(not(feature = "pointer_maps"))]
{
MAX_EDGES_NUM = MAX_EDGES_NUM.wrapping_add(1);
assert!((MAX_EDGES_NUM <= EDGES_MAP.len()), "The number of edges reported by SanitizerCoverage exceed the size of the edges map ({}). Use the LIBAFL_EDGES_MAP_SIZE env to increase it at compile time.", EDGES_MAP.len());
}
}
}