Concolic example fuzzer fixes (#251)

* fix compilation of runtime of concolic example fuzzer

* fix compilation of example fuzzer

* fix incorrect traced target configuration

this would lead to the runtime never tracing any expressions.
failed to specifiy the input file name for the runtime to know what to symbolize

* add ability to specify whether a node should do concolic or traditional

* slightly more realistic concolic solving by using solver timeout

* enable expression pruning
This commit is contained in:
julihoh 2021-08-06 17:47:50 +02:00 committed by GitHub
parent 2282b09ef5
commit e971f240da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 35 deletions

View File

@ -18,6 +18,7 @@ debug = true
[dependencies] [dependencies]
libafl = { path = "../../../libafl/", features = ["concolic_mutation"] } libafl = { path = "../../../libafl/", features = ["concolic_mutation"] }
libafl_targets = { path = "../../../libafl_targets/", features = ["sancov_pcguard_edges", "sancov_cmplog", "libfuzzer"] } libafl_targets = { path = "../../../libafl_targets/", features = ["sancov_pcguard_edges", "sancov_cmplog", "libfuzzer"] }
structopt = "0.3.21"
[build-dependencies] [build-dependencies]
cc = { version = "1.0", features = ["parallel"] } cc = { version = "1.0", features = ["parallel"] }

View File

@ -28,7 +28,7 @@ use libafl::{
}, },
observers::{ observers::{
concolic::{ concolic::{
serialization_format::shared_memory::{DEFAULT_ENV_NAME, DEFAULT_SIZE}, serialization_format::{DEFAULT_ENV_NAME, DEFAULT_SIZE},
ConcolicObserver, ConcolicObserver,
}, },
StdMapObserver, TimeObserver, StdMapObserver, TimeObserver,
@ -47,11 +47,22 @@ use libafl_targets::{
MAX_EDGES_NUM, MAX_EDGES_NUM,
}; };
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
struct Opt {
/// This node should do concolic tracing + solving instead of traditional fuzzing
#[structopt(short, long)]
concolic: bool,
}
pub fn main() { pub fn main() {
// Registry the metadata types used in this fuzzer // Registry the metadata types used in this fuzzer
// Needed only on no_std // Needed only on no_std
//RegistryBuilder::register::<Tokens>(); //RegistryBuilder::register::<Tokens>();
let opt = Opt::from_args();
println!( println!(
"Workdir: {:?}", "Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string() env::current_dir().unwrap().to_string_lossy().to_string()
@ -60,12 +71,18 @@ pub fn main() {
&[PathBuf::from("./corpus")], &[PathBuf::from("./corpus")],
PathBuf::from("./crashes"), PathBuf::from("./crashes"),
1337, 1337,
opt.concolic,
) )
.expect("An error occurred while fuzzing"); .expect("An error occurred while fuzzing");
} }
/// The actual fuzzer /// The actual fuzzer
fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { fn fuzz(
corpus_dirs: &[PathBuf],
objective_dir: PathBuf,
broker_port: u16,
concolic: bool,
) -> Result<(), Error> {
// 'While the stats are state, they are usually used in the broker - which is likely never restarted // 'While the stats are state, they are usually used in the broker - which is likely never restarted
let stats = MultiStats::new(|s| println!("{}", s)); let stats = MultiStats::new(|s| println!("{}", s));
@ -183,35 +200,40 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re
let mutator = StdScheduledMutator::new(havoc_mutations()); let mutator = StdScheduledMutator::new(havoc_mutations());
let mutational = StdMutationalStage::new(mutator); let mutational = StdMutationalStage::new(mutator);
// The shared memory for the concolic runtime to write its trace to if concolic {
let mut concolic_shmem = StdShMemProvider::new() // The shared memory for the concolic runtime to write its trace to
.unwrap() let mut concolic_shmem = StdShMemProvider::new()
.new_map(DEFAULT_SIZE) .unwrap()
.unwrap(); .new_map(DEFAULT_SIZE)
concolic_shmem.write_to_env(DEFAULT_ENV_NAME).unwrap(); .unwrap();
concolic_shmem.write_to_env(DEFAULT_ENV_NAME).unwrap();
// The concolic observer observers the concolic shared memory map. // The concolic observer observers the concolic shared memory map.
let concolic_observer = ConcolicObserver::new("concolic".to_string(), concolic_shmem.map_mut()); let concolic_observer =
ConcolicObserver::new("concolic".to_string(), concolic_shmem.map_mut());
let concolic_observer_name = concolic_observer.name().to_string(); let concolic_observer_name = concolic_observer.name().to_string();
// The order of the stages matter! // The order of the stages matter!
let mut stages = tuple_list!( let mut stages = tuple_list!(
// Create a concolic trace // Create a concolic trace
ConcolicTracingStage::new( ConcolicTracingStage::new(
TracingStage::new( TracingStage::new(
MyCommandConfigurator::default().into_executor(tuple_list!(concolic_observer)) MyCommandConfigurator::default().into_executor(tuple_list!(concolic_observer))
),
concolic_observer_name,
), ),
concolic_observer_name, // Use the concolic trace for z3-based solving
), SimpleConcolicMutationalStage::default(),
// Use the concolic trace for z3-based solving );
SimpleConcolicMutationalStage::default(),
tracing,
i2s,
mutational
);
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?; fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
} else {
// The order of the stages matter!
let mut stages = tuple_list!(tracing, i2s, mutational);
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
}
// Never reached // Never reached
Ok(()) Ok(())
@ -240,6 +262,7 @@ where
.stdin(Stdio::null()) .stdin(Stdio::null())
.stdout(Stdio::null()) .stdout(Stdio::null())
.stderr(Stdio::null()) .stderr(Stdio::null())
.env("SYMCC_INPUT_FILE", "cur_input")
.spawn() .spawn()
.expect("failed to start process")) .expect("failed to start process"))
} }

View File

@ -1,17 +1,18 @@
use symcc_runtime::{
export_runtime,
filter::NoFloat,
tracing::{self, StdShMemMessageFileWriter},
Runtime,
};
//! This is a basic SymCC runtime. //! This is a basic SymCC runtime.
//! It traces the execution to the shared memory region that should be passed through the environment by the fuzzer process. //! It traces the execution to the shared memory region that should be passed through the environment by the fuzzer process.
//! Additionally, it concretizes all floating point operations for simplicity. //! Additionally, it concretizes all floating point operations for simplicity.
//! Refer to the `symcc_runtime` crate documentation for building your own runtime. //! Refer to the `symcc_runtime` crate documentation for building your own runtime.
use symcc_runtime::{
export_runtime,
filter::{NoFloat, CallStackCoverage},
tracing::{self, StdShMemMessageFileWriter},
Runtime,
};
export_runtime!( export_runtime!(
NoFloat => NoFloat; NoFloat => NoFloat;
CallStackCoverage::default() => CallStackCoverage; // QSym-style expression pruning
tracing::TracingRuntime::new( tracing::TracingRuntime::new(
StdShMemMessageFileWriter::from_stdshmem_default_env() StdShMemMessageFileWriter::from_stdshmem_default_env()
.expect("unable to construct tracing runtime writer. (missing env?)") .expect("unable to construct tracing runtime writer. (missing env?)")

View File

@ -134,7 +134,9 @@ fn generate_mutations(iter: impl Iterator<Item = (SymExprRef, SymExpr)>) -> Vec<
let mut res = Vec::new(); let mut res = Vec::new();
let ctx = Context::new(&Config::new()); let mut cfg = Config::new();
cfg.set_timeout_msec(10_000);
let ctx = Context::new(&cfg);
let solver = Solver::new(&ctx); let solver = Solver::new(&ctx);
let mut translation = HashMap::<SymExprRef, Dynamic>::new(); let mut translation = HashMap::<SymExprRef, Dynamic>::new();
@ -305,7 +307,6 @@ fn generate_mutations(iter: impl Iterator<Item = (SymExprRef, SymExpr)>) -> Vec<
} }
z3::SatResult::Unknown => { z3::SatResult::Unknown => {
// we've got a problem. ignore // we've got a problem. ignore
solver.pop(1);
} }
z3::SatResult::Sat => { z3::SatResult::Sat => {
let model = solver.get_model().unwrap(); let model = solver.get_model().unwrap();