* forkserver: Add an API to setup the shared memory region for edge coverage This is inspired from and meant to be similar to afl-cc's instrumentation. Remove ! return type from __afl_start_forkserver as it returns in several cases. * Add example fuzzer using LibAFL's forkserver The fuzzer is instrumented with libafl_cc as well. Co-authored-by: ergrelet <ergrelet@users.noreply.github.com>
This commit is contained in:
parent
676a149497
commit
3e38862837
32
fuzzers/forkserver_libafl_cc/Cargo.toml
Normal file
32
fuzzers/forkserver_libafl_cc/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "forkserver_libafl_cc"
|
||||
version = "0.8.2"
|
||||
authors = ["ergrelet <ergrelet@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
# Forces a crash
|
||||
crash = []
|
||||
|
||||
[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" }
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/", features = ["default"] }
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
nix = "0.25"
|
||||
libafl_targets = { path = "../../libafl_targets/" }
|
||||
libafl_cc = { path = "../../libafl_cc/" }
|
||||
|
||||
[lib]
|
||||
name = "libforkserver_libafl_cc"
|
||||
crate-type = ["staticlib"]
|
115
fuzzers/forkserver_libafl_cc/Makefile.toml
Normal file
115
fuzzers/forkserver_libafl_cc/Makefile.toml
Normal file
@ -0,0 +1,115 @@
|
||||
# Variables
|
||||
[env]
|
||||
FUZZER_NAME='fuzzer_libafl_cc'
|
||||
CARGO_TARGET_DIR = { value = "${PROJECT_DIR}/target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } }
|
||||
LIBAFL_CC = '${CARGO_TARGET_DIR}/release/libafl_cc'
|
||||
LIBAFL_CXX = '${CARGO_TARGET_DIR}/release/libafl_cxx'
|
||||
FUZZER = '${CARGO_TARGET_DIR}/release/${FUZZER_NAME}'
|
||||
PROJECT_DIR = { script = ["pwd"] }
|
||||
|
||||
[tasks.unsupported]
|
||||
script_runner="@shell"
|
||||
script='''
|
||||
echo "Cargo-make not integrated yet on this"
|
||||
'''
|
||||
|
||||
# Compilers
|
||||
[tasks.cxx]
|
||||
linux_alias = "cxx_unix"
|
||||
mac_alias = "cxx_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.cxx_unix]
|
||||
command = "cargo"
|
||||
args = ["build" , "--release"]
|
||||
|
||||
[tasks.cc]
|
||||
linux_alias = "cc_unix"
|
||||
mac_alias = "cc_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.cc_unix]
|
||||
command = "cargo"
|
||||
args = ["build" , "--release"]
|
||||
|
||||
[tasks.crash_cxx]
|
||||
linux_alias = "crash_cxx_unix"
|
||||
mac_alias = "crash_cxx_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.crash_cxx_unix]
|
||||
command = "cargo"
|
||||
args = ["build" , "--release", "--features=crash"]
|
||||
|
||||
[tasks.crash_cc]
|
||||
linux_alias = "crash_cc_unix"
|
||||
mac_alias = "crash_cc_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.crash_cc_unix]
|
||||
command = "cargo"
|
||||
args = ["build" , "--release", "--features=crash"]
|
||||
|
||||
# Harness
|
||||
[tasks.fuzzer]
|
||||
linux_alias = "fuzzer_unix"
|
||||
mac_alias = "fuzzer_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.fuzzer_unix]
|
||||
command = "${CARGO_TARGET_DIR}/release/libafl_cc"
|
||||
args = ["${PROJECT_DIR}/src/program.c", "-o", "${FUZZER_NAME}", "-lm"]
|
||||
dependencies = [ "cxx", "cc" ]
|
||||
|
||||
# Crashing Harness
|
||||
[tasks.fuzzer_crash]
|
||||
linux_alias = "fuzzer_crash_unix"
|
||||
mac_alias = "fuzzer_crash_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.fuzzer_crash_unix]
|
||||
command = "${CARGO_TARGET_DIR}/release/libafl_cc"
|
||||
args = ["${PROJECT_DIR}/src/program.c", "-o", "${FUZZER_NAME}_crash", "-lm"]
|
||||
dependencies = [ "crash_cxx", "crash_cc" ]
|
||||
|
||||
# Run the fuzzer
|
||||
[tasks.run]
|
||||
linux_alias = "run_unix"
|
||||
mac_alias = "run_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.run_unix]
|
||||
script_runner = "@shell"
|
||||
script='''
|
||||
taskset -c 1 ${CARGO_TARGET_DIR}/release/${CARGO_MAKE_PROJECT_NAME} ./${FUZZER_NAME} ./corpus/ -t 1000
|
||||
'''
|
||||
dependencies = [ "fuzzer" ]
|
||||
|
||||
|
||||
# Run the fuzzer with a crash
|
||||
[tasks.crash]
|
||||
linux_alias = "crash_unix"
|
||||
mac_alias = "crash_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.crash_unix]
|
||||
script_runner = "@shell"
|
||||
script='''
|
||||
taskset -c 1 ${CARGO_TARGET_DIR}/release/${CARGO_MAKE_PROJECT_NAME} ./${FUZZER_NAME}_crash ./corpus/ -t 1000
|
||||
'''
|
||||
dependencies = [ "fuzzer_crash" ]
|
||||
|
||||
# Clean up
|
||||
[tasks.clean]
|
||||
linux_alias = "clean_unix"
|
||||
mac_alias = "clean_unix"
|
||||
windows_alias = "unsupported"
|
||||
|
||||
[tasks.clean_unix]
|
||||
# Disable default `clean` definition
|
||||
clear = true
|
||||
script_runner="@shell"
|
||||
script='''
|
||||
rm -f ./${FUZZER_NAME}
|
||||
cargo clean
|
||||
'''
|
13
fuzzers/forkserver_libafl_cc/README.md
Normal file
13
fuzzers/forkserver_libafl_cc/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Simple Forkserver Fuzzer
|
||||
|
||||
This is a simple example fuzzer to fuzz an executable instrumented by libafl_cc.
|
||||
|
||||
## Usage
|
||||
|
||||
You can build this example by running `cargo make fuzzer`.
|
||||
This compiles, libafl_cc, the fuzzer and the example harness program in
|
||||
`src/program.c` with libafl_cc.
|
||||
|
||||
## Run
|
||||
|
||||
You can run this example by running `cargo make run`.
|
1
fuzzers/forkserver_libafl_cc/corpus/testfile
Normal file
1
fuzzers/forkserver_libafl_cc/corpus/testfile
Normal file
@ -0,0 +1 @@
|
||||
aaa
|
45
fuzzers/forkserver_libafl_cc/src/bin/libafl_cc.rs
Normal file
45
fuzzers/forkserver_libafl_cc/src/bin/libafl_cc.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::env;
|
||||
|
||||
use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses};
|
||||
|
||||
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++ wrapper was called. Expected {dir:?} to end with c or cxx"),
|
||||
};
|
||||
|
||||
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")
|
||||
// Enable libafl's coverage instrumentation
|
||||
.add_pass(LLVMPasses::AFLCoverage)
|
||||
.add_arg("-mllvm")
|
||||
.add_arg("-ctx") // Context sensitive coverage
|
||||
// Imitate afl-cc's compile definitions
|
||||
.add_arg("-D__AFL_FUZZ_INIT()=int __afl_sharedmem_fuzzing = 1;extern unsigned int *__afl_fuzz_len;extern unsigned char *__afl_fuzz_ptr;unsigned char __afl_fuzz_alt[1048576];unsigned char *__afl_fuzz_alt_ptr = __afl_fuzz_alt;void libafl_start_forkserver(void)")
|
||||
.add_arg("-D__AFL_FUZZ_TESTCASE_BUF=(__afl_fuzz_ptr ? __afl_fuzz_ptr : __afl_fuzz_alt_ptr)")
|
||||
.add_arg("-D__AFL_FUZZ_TESTCASE_LEN=(__afl_fuzz_ptr ? *__afl_fuzz_len : (*__afl_fuzz_len = read(0, __afl_fuzz_alt_ptr, 1048576)) == 0xffffffff ? 0 : *__afl_fuzz_len)")
|
||||
.add_arg("-D__AFL_INIT()=libafl_start_forkserver()")
|
||||
// Link with libafl's forkserver implementation
|
||||
.link_staticlib(&dir, "libforkserver_libafl_cc")
|
||||
.run()
|
||||
.expect("Failed to run the wrapped compiler")
|
||||
{
|
||||
std::process::exit(code);
|
||||
}
|
||||
} else {
|
||||
panic!("LibAFL CC: No Arguments given");
|
||||
}
|
||||
}
|
5
fuzzers/forkserver_libafl_cc/src/bin/libafl_cxx.rs
Normal file
5
fuzzers/forkserver_libafl_cc/src/bin/libafl_cxx.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod libafl_cc;
|
||||
|
||||
fn main() {
|
||||
libafl_cc::main();
|
||||
}
|
9
fuzzers/forkserver_libafl_cc/src/lib.rs
Normal file
9
fuzzers/forkserver_libafl_cc/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use libafl_targets::{map_shared_memory, start_forkserver};
|
||||
|
||||
#[no_mangle]
|
||||
pub fn libafl_start_forkserver() {
|
||||
// Map shared memory region for the edge coverage map
|
||||
map_shared_memory();
|
||||
// Start the forkserver
|
||||
start_forkserver();
|
||||
}
|
215
fuzzers/forkserver_libafl_cc/src/main.rs
Normal file
215
fuzzers/forkserver_libafl_cc/src/main.rs
Normal file
@ -0,0 +1,215 @@
|
||||
use core::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{self, Parser};
|
||||
use libafl::{
|
||||
bolts::{
|
||||
current_nanos,
|
||||
rands::StdRand,
|
||||
shmem::{ShMem, ShMemProvider, UnixShMemProvider},
|
||||
tuples::{tuple_list, MatchName, Merge},
|
||||
AsMutSlice,
|
||||
},
|
||||
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
|
||||
events::SimpleEventManager,
|
||||
executors::{
|
||||
forkserver::{ForkserverExecutor, TimeoutForkserverExecutor},
|
||||
HasObservers,
|
||||
},
|
||||
feedback_and_fast, feedback_or,
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::BytesInput,
|
||||
monitors::SimpleMonitor,
|
||||
mutators::{scheduled::havoc_mutations, tokens_mutations, StdScheduledMutator, Tokens},
|
||||
observers::{HitcountsMapObserver, MapObserver, StdMapObserver, TimeObserver},
|
||||
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
||||
stages::mutational::StdMutationalStage,
|
||||
state::{HasCorpus, HasMetadata, StdState},
|
||||
};
|
||||
use nix::sys::signal::Signal;
|
||||
|
||||
/// The commandline args this fuzzer accepts
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "forkserver_libafl_cc",
|
||||
about = "This is a simple example fuzzer to fuzz a executable instrumented by libafl_cc.",
|
||||
author = "ergrelet <ergrelet@users.noreply.github.com>"
|
||||
)]
|
||||
struct Opt {
|
||||
#[arg(
|
||||
help = "The instrumented binary we want to fuzz",
|
||||
name = "EXEC",
|
||||
required = true
|
||||
)]
|
||||
executable: String,
|
||||
|
||||
#[arg(
|
||||
help = "The directory to read initial inputs from ('seeds')",
|
||||
name = "INPUT_DIR",
|
||||
required = true
|
||||
)]
|
||||
in_dir: PathBuf,
|
||||
|
||||
#[arg(
|
||||
help = "Timeout for each individual execution, in milliseconds",
|
||||
short = 't',
|
||||
long = "timeout",
|
||||
default_value = "1200"
|
||||
)]
|
||||
timeout: u64,
|
||||
|
||||
#[arg(
|
||||
help = "If not set, the child's stdout and stderror will be redirected to /dev/null",
|
||||
short = 'd',
|
||||
long = "debug-child",
|
||||
default_value = "false"
|
||||
)]
|
||||
debug_child: bool,
|
||||
|
||||
#[arg(
|
||||
help = "Arguments passed to the target",
|
||||
name = "arguments",
|
||||
num_args(1..),
|
||||
allow_hyphen_values = true,
|
||||
)]
|
||||
arguments: Vec<String>,
|
||||
|
||||
#[arg(
|
||||
help = "Signal used to stop child",
|
||||
short = 's',
|
||||
long = "signal",
|
||||
value_parser = str::parse::<Signal>,
|
||||
default_value = "SIGKILL"
|
||||
)]
|
||||
signal: Signal,
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
pub fn main() {
|
||||
const MAP_SIZE: usize = 65536;
|
||||
|
||||
let opt = Opt::parse();
|
||||
|
||||
let corpus_dirs: Vec<PathBuf> = [opt.in_dir].to_vec();
|
||||
|
||||
// The unix shmem provider supported by LibAFL for shared memory
|
||||
let mut shmem_provider = UnixShMemProvider::new().unwrap();
|
||||
|
||||
// The coverage map shared between observer and executor
|
||||
let mut shmem = shmem_provider.new_shmem(MAP_SIZE).unwrap();
|
||||
// let the forkserver know the shmid
|
||||
shmem.write_to_env("__AFL_SHM_ID").unwrap();
|
||||
let shmem_buf = shmem.as_mut_slice();
|
||||
|
||||
// Create an observation channel using the signals map
|
||||
let edges_observer =
|
||||
unsafe { HitcountsMapObserver::new(StdMapObserver::new("shared_mem", shmem_buf)) };
|
||||
|
||||
// Create an observation channel to keep track of the execution time
|
||||
let time_observer = TimeObserver::new("time");
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
// This one is composed by two Feedbacks in OR
|
||||
let mut feedback = feedback_or!(
|
||||
// New maximization map feedback linked to the edges observer and the feedback state
|
||||
MaxMapFeedback::new_tracking(&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
|
||||
// We want to do the same crash deduplication that AFL does
|
||||
let mut objective = feedback_and_fast!(
|
||||
// Must be a crash
|
||||
CrashFeedback::new(),
|
||||
// Take it onlt if trigger new coverage over crashes
|
||||
MaxMapFeedback::new(&edges_observer)
|
||||
);
|
||||
|
||||
// create a State from scratch
|
||||
let mut state = StdState::new(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::<BytesInput>::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(PathBuf::from("./crashes")).unwrap(),
|
||||
// States of the feedbacks.
|
||||
// The feedbacks can report the data that should persist in the State.
|
||||
&mut feedback,
|
||||
// Same for objective feedbacks
|
||||
&mut objective,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The Monitor trait define how the fuzzer stats are reported to the user
|
||||
let monitor = SimpleMonitor::new(|s| println!("{s}"));
|
||||
|
||||
// The event manager handle the various events generated during the fuzzing loop
|
||||
// such as the notification of the addition of a new item to the corpus
|
||||
let mut mgr = SimpleEventManager::new(monitor);
|
||||
|
||||
// A minimization+queue policy to get testcasess from the corpus
|
||||
let scheduler = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new());
|
||||
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
// If we should debug the child
|
||||
let debug_child = opt.debug_child;
|
||||
|
||||
// Create the executor for the forkserver
|
||||
let args = opt.arguments;
|
||||
|
||||
let mut tokens = Tokens::new();
|
||||
let mut forkserver = ForkserverExecutor::builder()
|
||||
.program(opt.executable)
|
||||
.debug_child(debug_child)
|
||||
.shmem_provider(&mut shmem_provider)
|
||||
.autotokens(&mut tokens)
|
||||
.parse_afl_cmdline(args)
|
||||
.coverage_map_size(MAP_SIZE)
|
||||
.build(tuple_list!(time_observer, edges_observer))
|
||||
.unwrap();
|
||||
|
||||
if let Some(dynamic_map_size) = forkserver.coverage_map_size() {
|
||||
forkserver
|
||||
.observers_mut()
|
||||
.match_name_mut::<HitcountsMapObserver<StdMapObserver<'_, u8, false>>>("shared_mem")
|
||||
.unwrap()
|
||||
.downsize_map(dynamic_map_size);
|
||||
}
|
||||
|
||||
let mut executor = TimeoutForkserverExecutor::with_signal(
|
||||
forkserver,
|
||||
Duration::from_millis(opt.timeout),
|
||||
opt.signal,
|
||||
)
|
||||
.expect("Failed to create the executor.");
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
state
|
||||
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to load initial corpus at {:?}: {:?}",
|
||||
&corpus_dirs, err
|
||||
)
|
||||
});
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
state.add_metadata(tokens);
|
||||
|
||||
// Setup a mutational stage with a basic bytes mutator
|
||||
let mutator =
|
||||
StdScheduledMutator::with_max_stack_pow(havoc_mutations().merge(tokens_mutations()), 6);
|
||||
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||
|
||||
fuzzer
|
||||
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
|
||||
.expect("Error in the fuzzing loop");
|
||||
}
|
37
fuzzers/forkserver_libafl_cc/src/program.c
Normal file
37
fuzzers/forkserver_libafl_cc/src/program.c
Normal file
@ -0,0 +1,37 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// The following line is needed for shared memory testcase fuzzing
|
||||
__AFL_FUZZ_INIT();
|
||||
|
||||
void vuln(char *buf) {
|
||||
if (strcmp(buf, "vuln") == 0) { abort(); }
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Start the forkserver at this point (i.e., forks will happen here)
|
||||
__AFL_INIT();
|
||||
|
||||
// The following five lines are for normal fuzzing.
|
||||
/*
|
||||
FILE *file = stdin;
|
||||
if (argc > 1) { file = fopen(argv[1], "rb"); }
|
||||
char buf[16];
|
||||
char *p = fgets(buf, 16, file);
|
||||
buf[15] = 0;
|
||||
*/
|
||||
|
||||
// The following line is also needed for shared memory testcase fuzzing
|
||||
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; // must be after __AFL_INIT
|
||||
|
||||
// printf("input: %s\n", buf);
|
||||
if (buf[0] == 'b') {
|
||||
if (buf[1] == 'a') {
|
||||
if (buf[2] == 'd') { abort(); }
|
||||
}
|
||||
}
|
||||
vuln((char *)buf);
|
||||
|
||||
return 0;
|
||||
}
|
@ -7,17 +7,24 @@
|
||||
#include <unistd.h>
|
||||
#ifndef USEMMAP
|
||||
#include <sys/shm.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define write_error(s) fprintf(stderr, "Error at %s:%d: %s\n", __FILE__, __LINE__, s)
|
||||
#define write_error(s) \
|
||||
fprintf(stderr, "Error at %s:%d: %s\n", __FILE__, __LINE__, s)
|
||||
|
||||
// AFL++ constants
|
||||
#define FORKSRV_FD 198
|
||||
#define MAX_FILE (1024 * 1024)
|
||||
#define SHMEM_FUZZ_HDR_SIZE 4
|
||||
#define SHM_ENV_VAR "__AFL_SHM_ID"
|
||||
#define SHM_FUZZ_ENV_VAR "__AFL_SHM_FUZZ_ID"
|
||||
#define DEFAULT_PERMISSION 0600
|
||||
|
||||
/* Reporting errors */
|
||||
#define FS_OPT_ERROR 0xf800008f
|
||||
@ -45,18 +52,21 @@
|
||||
#define FS_OPT_SET_MAPSIZE(x) \
|
||||
(x <= 1 || x > FS_OPT_MAX_MAPSIZE ? 0 : ((x - 1) << 1))
|
||||
|
||||
// Set by this macro https://github.com/AFLplusplus/AFLplusplus/blob/stable/src/afl-cc.c#L993
|
||||
// Set by this macro
|
||||
// https://github.com/AFLplusplus/AFLplusplus/blob/stable/src/afl-cc.c#L993
|
||||
|
||||
int __afl_sharedmem_fuzzing __attribute__((weak));
|
||||
|
||||
extern size_t __afl_map_size;
|
||||
extern uint8_t* __token_start;
|
||||
extern uint8_t* __token_stop;
|
||||
extern uint8_t *__afl_area_ptr;
|
||||
extern size_t __afl_map_size;
|
||||
extern uint8_t *__token_start;
|
||||
extern uint8_t *__token_stop;
|
||||
|
||||
uint8_t* __afl_fuzz_ptr;
|
||||
uint8_t *__afl_fuzz_ptr;
|
||||
static uint32_t __afl_fuzz_len_local;
|
||||
uint32_t* __afl_fuzz_len = &__afl_fuzz_len_local;
|
||||
uint32_t *__afl_fuzz_len = &__afl_fuzz_len_local;
|
||||
|
||||
int already_initialized_shm;
|
||||
int already_initialized_forkserver;
|
||||
|
||||
static int child_pid;
|
||||
@ -65,48 +75,98 @@ static void (*old_sigterm_handler)(int) = 0;
|
||||
static uint8_t is_persistent;
|
||||
|
||||
void __afl_set_persistent_mode(uint8_t mode) {
|
||||
|
||||
is_persistent = mode;
|
||||
|
||||
}
|
||||
|
||||
/* Error reporting to forkserver controller */
|
||||
|
||||
static void send_forkserver_error(int error) {
|
||||
|
||||
uint32_t status;
|
||||
if (!error || error > 0xffff) return;
|
||||
status = (FS_OPT_ERROR | FS_OPT_SET_ERROR(error));
|
||||
if (write(FORKSRV_FD + 1, (char *)&status, 4) != 4) { return; }
|
||||
|
||||
}
|
||||
|
||||
/* Ensure we kill the child on termination */
|
||||
|
||||
static void at_exit(int signal) {
|
||||
|
||||
(void)signal;
|
||||
|
||||
if (child_pid > 0) {
|
||||
|
||||
kill(child_pid, SIGKILL);
|
||||
child_pid = -1;
|
||||
|
||||
}
|
||||
|
||||
_exit(0);
|
||||
|
||||
}
|
||||
|
||||
/* SHM fuzzing setup. */
|
||||
|
||||
static void map_input_shared_memory() {
|
||||
void __afl_map_shm(void) {
|
||||
if (already_initialized_shm) return;
|
||||
already_initialized_shm = 1;
|
||||
|
||||
char *id_str = getenv(SHM_ENV_VAR);
|
||||
|
||||
if (id_str) {
|
||||
#ifdef USEMMAP
|
||||
const char *shm_file_path = id_str;
|
||||
int shm_fd = -1;
|
||||
unsigned char *shm_base = NULL;
|
||||
|
||||
/* create the shared memory segment as if it was a file */
|
||||
shm_fd = shm_open(shm_file_path, O_RDWR, DEFAULT_PERMISSION);
|
||||
if (shm_fd == -1) {
|
||||
fprintf(stderr, "shm_open() failed\n");
|
||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
shm_base =
|
||||
mmap(0, __afl_map_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
|
||||
|
||||
close(shm_fd);
|
||||
shm_fd = -1;
|
||||
|
||||
if (shm_base == MAP_FAILED) {
|
||||
fprintf(stderr, "mmap() failed\n");
|
||||
perror("mmap for map");
|
||||
send_forkserver_error(FS_ERROR_MMAP);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
__afl_area_ptr = shm_base;
|
||||
#else
|
||||
uint32_t shm_id = atoi(id_str);
|
||||
__afl_area_ptr = (uint8_t *)shmat(shm_id, NULL, 0);
|
||||
|
||||
/* Whooooops. */
|
||||
|
||||
if (!__afl_area_ptr || __afl_area_ptr == (void *)-1) {
|
||||
send_forkserver_error(FS_ERROR_SHMAT);
|
||||
perror("shmat for map");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Write something into the bitmap so that even with low AFL_INST_RATIO,
|
||||
our parent doesn't give up on us. */
|
||||
|
||||
__afl_area_ptr[0] = 1;
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"Error: variable for edge coverage shared memory is not set\n");
|
||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void map_input_shared_memory() {
|
||||
char *id_str = getenv(SHM_FUZZ_ENV_VAR);
|
||||
|
||||
if (id_str) {
|
||||
|
||||
uint8_t* map = NULL;
|
||||
uint8_t *map = NULL;
|
||||
|
||||
#ifdef USEMMAP
|
||||
const char *shm_file_path = id_str;
|
||||
@ -115,48 +175,41 @@ static void map_input_shared_memory() {
|
||||
/* create the shared memory segment as if it was a file */
|
||||
shm_fd = shm_open(shm_file_path, O_RDWR, DEFAULT_PERMISSION);
|
||||
if (shm_fd == -1) {
|
||||
|
||||
fprintf(stderr, "shm_open() failed for fuzz\n");
|
||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
||||
exit(1);
|
||||
|
||||
}
|
||||
|
||||
map = (uint8_t* )mmap(0, MAX_FILE + sizeof(uint32_t), PROT_READ, MAP_SHARED, shm_fd, 0);
|
||||
map = (uint8_t *)mmap(0, MAX_FILE + sizeof(uint32_t), PROT_READ, MAP_SHARED,
|
||||
shm_fd, 0);
|
||||
|
||||
#else
|
||||
uint32_t shm_id = atoi(id_str);
|
||||
map = (uint8_t* )shmat(shm_id, NULL, 0);
|
||||
map = (uint8_t *)shmat(shm_id, NULL, 0);
|
||||
|
||||
#endif
|
||||
|
||||
/* Whooooops. */
|
||||
|
||||
if (!map || map == (void *)-1) {
|
||||
|
||||
perror("Could not access fuzzing shared memory");
|
||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
||||
exit(1);
|
||||
|
||||
}
|
||||
|
||||
__afl_fuzz_len = (uint32_t* )map;
|
||||
__afl_fuzz_len = (uint32_t *)map;
|
||||
__afl_fuzz_ptr = map + sizeof(uint32_t);
|
||||
|
||||
} else {
|
||||
|
||||
fprintf(stderr, "Error: variable for fuzzing shared memory is not set\n");
|
||||
send_forkserver_error(FS_ERROR_SHM_OPEN);
|
||||
exit(1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Fork server logic. */
|
||||
|
||||
void __afl_start_forkserver(void) {
|
||||
|
||||
if (already_initialized_forkserver) return;
|
||||
already_initialized_forkserver = 1;
|
||||
|
||||
@ -175,24 +228,14 @@ void __afl_start_forkserver(void) {
|
||||
void (*old_sigchld_handler)(int) = signal(SIGCHLD, SIG_DFL);
|
||||
|
||||
if (__afl_map_size <= FS_OPT_MAX_MAPSIZE) {
|
||||
|
||||
status_for_fsrv |= (FS_OPT_SET_MAPSIZE(__afl_map_size) | FS_OPT_MAPSIZE);
|
||||
|
||||
}
|
||||
|
||||
int autodict_on = __token_start != NULL && __token_stop != NULL;
|
||||
if (autodict_on) {
|
||||
|
||||
status_for_fsrv |= FS_OPT_AUTODICT;
|
||||
|
||||
}
|
||||
if (autodict_on) { status_for_fsrv |= FS_OPT_AUTODICT; }
|
||||
|
||||
if (__afl_sharedmem_fuzzing != 0) { status_for_fsrv |= FS_OPT_SHDMEM_FUZZ; }
|
||||
if (status_for_fsrv) {
|
||||
|
||||
status_for_fsrv |= FS_OPT_ENABLED;
|
||||
|
||||
}
|
||||
if (status_for_fsrv) { status_for_fsrv |= FS_OPT_ENABLED; }
|
||||
|
||||
memcpy(tmp, &status_for_fsrv, 4);
|
||||
|
||||
@ -202,73 +245,57 @@ void __afl_start_forkserver(void) {
|
||||
if (write(FORKSRV_FD + 1, tmp, 4) != 4) { return; }
|
||||
|
||||
if (__afl_sharedmem_fuzzing || autodict_on) {
|
||||
|
||||
if (read(FORKSRV_FD, &was_killed, 4) != 4) _exit(1);
|
||||
|
||||
if ((was_killed & (FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ)) == (FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ)) {
|
||||
|
||||
if ((was_killed & (FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ)) ==
|
||||
(FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ)) {
|
||||
map_input_shared_memory();
|
||||
|
||||
}
|
||||
|
||||
if ((was_killed & (FS_OPT_ENABLED | FS_OPT_AUTODICT)) == (FS_OPT_ENABLED | FS_OPT_AUTODICT) && autodict_on) {
|
||||
|
||||
if ((was_killed & (FS_OPT_ENABLED | FS_OPT_AUTODICT)) ==
|
||||
(FS_OPT_ENABLED | FS_OPT_AUTODICT) &&
|
||||
autodict_on) {
|
||||
// great lets pass the dictionary through the forkserver FD
|
||||
uint32_t len = (__token_stop - __token_start), offset = 0;
|
||||
|
||||
if (write(FORKSRV_FD + 1, &len, 4) != 4) {
|
||||
|
||||
write_error("could not send dictionary len");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
while (len != 0) {
|
||||
|
||||
int32_t ret;
|
||||
ret = write(FORKSRV_FD + 1, __token_start + offset, len);
|
||||
|
||||
if (ret < 1) {
|
||||
|
||||
write_error("could not send dictionary");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
len -= ret;
|
||||
offset += ret;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// uh this forkserver does not understand extended option passing
|
||||
// or does not want the dictionary
|
||||
if (!__afl_fuzz_ptr) already_read_first = 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
while (1) {
|
||||
|
||||
int status;
|
||||
|
||||
/* Wait for parent by reading from the pipe. Abort if read fails. */
|
||||
|
||||
if (already_read_first) {
|
||||
|
||||
already_read_first = 0;
|
||||
|
||||
} else {
|
||||
|
||||
if (read(FORKSRV_FD, &was_killed, 4) != 4) {
|
||||
|
||||
// write_error("read from afl-fuzz");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* If we stopped the child in persistent mode, but there was a race
|
||||
@ -276,33 +303,25 @@ void __afl_start_forkserver(void) {
|
||||
process. */
|
||||
|
||||
if (child_stopped && was_killed) {
|
||||
|
||||
child_stopped = 0;
|
||||
if (waitpid(child_pid, &status, 0) < 0) {
|
||||
|
||||
write_error("child_stopped && was_killed");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!child_stopped) {
|
||||
|
||||
/* Once woken up, create a clone of our process. */
|
||||
|
||||
child_pid = fork();
|
||||
if (child_pid < 0) {
|
||||
|
||||
write_error("fork");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
/* In child process: close fds, resume execution. */
|
||||
|
||||
if (!child_pid) {
|
||||
|
||||
//(void)nice(-20);
|
||||
|
||||
signal(SIGCHLD, old_sigchld_handler);
|
||||
@ -311,33 +330,26 @@ void __afl_start_forkserver(void) {
|
||||
close(FORKSRV_FD);
|
||||
close(FORKSRV_FD + 1);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* Special handling for persistent mode: if the child is alive but
|
||||
currently stopped, simply restart it with SIGCONT. */
|
||||
|
||||
kill(child_pid, SIGCONT);
|
||||
child_stopped = 0;
|
||||
|
||||
}
|
||||
|
||||
/* In parent process: write PID to pipe, then wait for child. */
|
||||
|
||||
if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) {
|
||||
|
||||
write_error("write to afl-fuzz");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0) {
|
||||
|
||||
write_error("waitpid");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
/* In persistent mode, the child stops itself with SIGSTOP to indicate
|
||||
@ -349,12 +361,8 @@ void __afl_start_forkserver(void) {
|
||||
/* Relay wait status to pipe, then loop back. */
|
||||
|
||||
if (write(FORKSRV_FD + 1, &status, 4) != 4) {
|
||||
|
||||
write_error("writing to afl-fuzz");
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,19 @@
|
||||
//! Forkserver logic into targets
|
||||
|
||||
extern "C" {
|
||||
/// Map a shared memory region for the edge coverage map.
|
||||
fn __afl_map_shm();
|
||||
/// Start the forkserver.
|
||||
fn __afl_start_forkserver() -> !;
|
||||
fn __afl_start_forkserver();
|
||||
}
|
||||
|
||||
/// Map a shared memory region for the edge coverage map.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The function's logic is written in C and this code is a wrapper.
|
||||
pub fn map_shared_memory() {
|
||||
unsafe { __afl_map_shm() }
|
||||
}
|
||||
|
||||
/// Start the forkserver from this point. Any shared memory must be created before.
|
||||
@ -10,6 +21,6 @@ extern "C" {
|
||||
/// # Note
|
||||
///
|
||||
/// The forkserver logic is written in C and this code is a wrapper.
|
||||
pub fn start_forkserver() -> ! {
|
||||
pub fn start_forkserver() {
|
||||
unsafe { __afl_start_forkserver() }
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user