Fix drcov path parsing (#2884)
* fix drcov path parsing * refactoring of drcov tool * add the possibility to sort addresses in drcov tools * more aggressive clippy. it now catches more warnings as errors than before * reduce the number of unfixable warnings displayed.
This commit is contained in:
parent
c5b7c7c235
commit
4083f0ba73
@ -126,12 +126,15 @@ z3 = "0.12.1"
|
||||
|
||||
|
||||
[workspace.lints.rust]
|
||||
# Deny
|
||||
warnings = { level = "deny", priority = -1 }
|
||||
|
||||
# Forbid
|
||||
unexpected_cfgs = "forbid"
|
||||
|
||||
# Allow
|
||||
incomplete_features = "allow"
|
||||
ambiguous_glob_reexports = "allow"
|
||||
# ambiguous_glob_reexports = "allow"
|
||||
|
||||
|
||||
[workspace.lints.clippy]
|
||||
@ -142,9 +145,10 @@ cargo_common_metadata = "deny"
|
||||
|
||||
# Warn
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
negative_feature_names = "warn"
|
||||
|
||||
# Allow
|
||||
negative_feature_names = "allow" # TODO: turn into 'warn' when working
|
||||
multiple_crate_versions = "allow" # TODO: turn into `warn` when working
|
||||
unreadable_literal = "allow"
|
||||
type_repetition_in_bounds = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
|
@ -30,5 +30,7 @@ pyo3-build-config = { workspace = true }
|
||||
name = "pylibafl"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
# TODO: find a way to fix this when a solution is found
|
||||
# https://github.com/rust-lang/cargo/issues/9330
|
||||
# [profile.dev]
|
||||
# panic = "abort"
|
||||
|
@ -244,7 +244,7 @@ windows_alias = "unsupported"
|
||||
command = "${TARGET_DIR}/${PROFILE_DIR}/qemu_coverage-${CARGO_MAKE_PROFILE}"
|
||||
args = [
|
||||
"--coverage-path",
|
||||
"${TARGET_DIR}/drcov.log",
|
||||
"${TARGET_DIR}/cov.drcov",
|
||||
"--input-dir",
|
||||
"./corpus",
|
||||
"--",
|
||||
@ -294,11 +294,11 @@ script = '''
|
||||
cargo make ${FEATURE} || exit 1
|
||||
|
||||
cargo run --manifest-path ../../../utils/drcov_utils/Cargo.toml --bin drcov_merge -- \
|
||||
-i ${TARGET_DIR}/drcov-000.log ${TARGET_DIR}/drcov-001.log ${TARGET_DIR}/drcov-002.log ${TARGET_DIR}/drcov-003.log \
|
||||
--output ${TARGET_DIR}/drcov-merged.log || exit 1
|
||||
-i ${TARGET_DIR}/cov-000.drcov ${TARGET_DIR}/cov-001.drcov ${TARGET_DIR}/cov-002.drcov ${TARGET_DIR}/cov-003.drcov \
|
||||
--output ${TARGET_DIR}/cov-merged.drcov || exit 1
|
||||
|
||||
TMP=$(cargo run --manifest-path ../../../utils/drcov_utils/Cargo.toml --bin drcov_dump_addrs -- \
|
||||
-i ${TARGET_DIR}/drcov-merged.log | wc -l || exit 1)
|
||||
-i ${TARGET_DIR}/cov-merged.drcov -a | wc -l || exit 1)
|
||||
|
||||
NB_BLOCKS=$((TMP - 1))
|
||||
|
||||
|
@ -135,10 +135,7 @@ pub fn fuzz() {
|
||||
cov_path.set_file_name(format!("{coverage_name}-{core:03}.{coverage_extension}"));
|
||||
|
||||
let emulator_modules = tuple_list!(
|
||||
DrCovModule::builder()
|
||||
.filename(cov_path.clone())
|
||||
.full_trace(false)
|
||||
.build(),
|
||||
DrCovModule::builder().filename(cov_path.clone()).build(),
|
||||
SnapshotModule::new()
|
||||
);
|
||||
|
||||
|
@ -86,6 +86,8 @@ pub use libafl_bolts::{nonzero, Error};
|
||||
/// The purpose of this module is to alleviate imports of many components by adding a glob import.
|
||||
#[cfg(feature = "prelude")]
|
||||
pub mod prelude {
|
||||
#![expect(ambiguous_glob_reexports)]
|
||||
|
||||
pub use super::{
|
||||
corpus::*, events::*, executors::*, feedbacks::*, fuzzer::*, generators::*, inputs::*,
|
||||
monitors::*, mutators::*, observers::*, schedulers::*, stages::*, state::*, *,
|
||||
|
@ -678,6 +678,8 @@ impl From<pyo3::PyErr> for Error {
|
||||
/// The purpose of this module is to alleviate imports of many components by adding a glob import.
|
||||
#[cfg(feature = "prelude")]
|
||||
pub mod prelude {
|
||||
#![allow(ambiguous_glob_reexports)]
|
||||
|
||||
pub use super::{bolts_prelude::*, *};
|
||||
}
|
||||
|
||||
|
@ -3,15 +3,12 @@
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::{borrow::Cow, vec::Vec};
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::{
|
||||
any::{type_name, TypeId},
|
||||
cell::Cell,
|
||||
any::type_name,
|
||||
fmt::{Debug, Formatter},
|
||||
marker::PhantomData,
|
||||
mem::transmute,
|
||||
ops::{Index, IndexMut},
|
||||
ops::{Deref, DerefMut, Index, IndexMut},
|
||||
};
|
||||
use core::{any::TypeId, cell::Cell, marker::PhantomData, mem::transmute};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -1,11 +1,17 @@
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
str,
|
||||
};
|
||||
#[cfg(any(
|
||||
target_vendor = "apple",
|
||||
feature = "ddg-instr",
|
||||
feature = "function-logging",
|
||||
feature = "cmplog-routines",
|
||||
feature = "autotokens",
|
||||
feature = "coverage-accounting",
|
||||
feature = "cmplog-instructions",
|
||||
feature = "ctx",
|
||||
feature = "dump-cfg",
|
||||
feature = "profiling",
|
||||
))]
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs::File, io::Write, path::Path, process::Command, str};
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
use glob::glob;
|
||||
@ -20,6 +26,17 @@ const LLVM_VERSION_MAX: u32 = 33;
|
||||
const LLVM_VERSION_MIN: u32 = 6;
|
||||
|
||||
/// Get the extension for a shared object
|
||||
#[cfg(any(
|
||||
feature = "ddg-instr",
|
||||
feature = "function-logging",
|
||||
feature = "cmplog-routines",
|
||||
feature = "autotokens",
|
||||
feature = "coverage-accounting",
|
||||
feature = "cmplog-instructions",
|
||||
feature = "ctx",
|
||||
feature = "dump-cfg",
|
||||
feature = "profiling",
|
||||
))]
|
||||
fn dll_extension<'a>() -> &'a str {
|
||||
if let Ok(vendor) = env::var("CARGO_CFG_TARGET_VENDOR") {
|
||||
if vendor == "apple" {
|
||||
@ -143,6 +160,17 @@ fn find_llvm_version() -> Option<i32> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "ddg-instr",
|
||||
feature = "function-logging",
|
||||
feature = "cmplog-routines",
|
||||
feature = "autotokens",
|
||||
feature = "coverage-accounting",
|
||||
feature = "cmplog-instructions",
|
||||
feature = "ctx",
|
||||
feature = "dump-cfg",
|
||||
feature = "profiling",
|
||||
))]
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn build_pass(
|
||||
bindir_path: &Path,
|
||||
|
@ -40,6 +40,8 @@ pub mod cpp_runtime {
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused_attributes)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Tracing of expressions in a serialized form.
|
||||
#![allow(no_mangle_generic_items)]
|
||||
|
||||
pub use libafl::observers::concolic::serialization_format::StdShMemMessageFileWriter;
|
||||
use libafl::observers::concolic::SymExpr;
|
||||
|
@ -39,9 +39,9 @@ use arbitrary_int::u4;
|
||||
use bitbybit::bitfield;
|
||||
#[cfg(target_os = "linux")]
|
||||
use caps::{CapSet, Capability};
|
||||
use libafl_bolts::Error;
|
||||
#[cfg(target_os = "linux")]
|
||||
use libafl_bolts::ownedref::OwnedRefMut;
|
||||
use libafl_bolts::{hash_64_fast, Error};
|
||||
use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut};
|
||||
use libipt::PtError;
|
||||
#[cfg(target_os = "linux")]
|
||||
use libipt::{
|
||||
|
@ -102,36 +102,39 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
{
|
||||
todo!("copy all the source files"); // we don't support libafl_libfuzzer for others rn
|
||||
}
|
||||
let mut template: toml::Value =
|
||||
toml::from_str(&fs::read_to_string("runtime/Cargo.toml.template")?)?;
|
||||
let toml::Value::Table(root) = &mut template else {
|
||||
unreachable!("Invalid Cargo.toml");
|
||||
};
|
||||
root.insert(
|
||||
"workspace".to_string(),
|
||||
toml::Value::Table(toml::Table::new()),
|
||||
);
|
||||
let Some(toml::Value::Table(deps)) = root.get_mut("dependencies") else {
|
||||
unreachable!("Invalid Cargo.toml");
|
||||
};
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
for (_name, spec) in deps {
|
||||
if let toml::Value::Table(spec) = spec {
|
||||
// replace all path deps with version deps
|
||||
if spec.remove("path").is_some() {
|
||||
spec.insert(
|
||||
"version".to_string(),
|
||||
toml::Value::String(version.to_string()),
|
||||
);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mut template: toml::Value =
|
||||
toml::from_str(&fs::read_to_string("runtime/Cargo.toml.template")?)?;
|
||||
let toml::Value::Table(root) = &mut template else {
|
||||
unreachable!("Invalid Cargo.toml");
|
||||
};
|
||||
root.insert(
|
||||
"workspace".to_string(),
|
||||
toml::Value::Table(toml::Table::new()),
|
||||
);
|
||||
let Some(toml::Value::Table(deps)) = root.get_mut("dependencies") else {
|
||||
unreachable!("Invalid Cargo.toml");
|
||||
};
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
for (_name, spec) in deps {
|
||||
if let toml::Value::Table(spec) = spec {
|
||||
// replace all path deps with version deps
|
||||
if spec.remove("path").is_some() {
|
||||
spec.insert(
|
||||
"version".to_string(),
|
||||
toml::Value::String(version.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let serialized = toml::to_string(&template)?;
|
||||
fs::write(custom_lib_dir.join("Cargo.toml"), serialized)?;
|
||||
|
||||
// build in this filled out template
|
||||
command.current_dir(custom_lib_dir);
|
||||
}
|
||||
|
||||
let serialized = toml::to_string(&template)?;
|
||||
fs::write(custom_lib_dir.join("Cargo.toml"), serialized)?;
|
||||
|
||||
// build in this filled out template
|
||||
command.current_dir(custom_lib_dir);
|
||||
}
|
||||
|
||||
assert!(
|
||||
|
@ -115,7 +115,9 @@ pub fn build() {
|
||||
let cross_cc = if cfg!(feature = "usermode") && (qemu_asan || qemu_asan_guest) {
|
||||
// TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)
|
||||
let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| {
|
||||
println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({cpu_target}) is not the host arch ({}))", env::consts::ARCH);
|
||||
if cpu_target != env::consts::ARCH {
|
||||
println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong since the selected cpu target ({cpu_target}) is different from the host arch ({}))", env::consts::ARCH);
|
||||
}
|
||||
"cc".to_owned()
|
||||
});
|
||||
println!("cargo:rerun-if-env-changed=CROSS_CC");
|
||||
|
@ -133,7 +133,7 @@ impl crate::ArchExtras for crate::CPU {
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
val: T,
|
||||
_val: T,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
|
@ -302,7 +302,6 @@ pub type QemuInProcessForkExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, SP, Z
|
||||
StatefulInProcessForkExecutor<'a, EM, Emulator<C, CM, ED, ET, I, S, SM>, H, I, OT, S, SP, Z>;
|
||||
|
||||
#[cfg(feature = "fork")]
|
||||
#[expect(clippy::type_complexity)]
|
||||
pub struct QemuForkExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, SP, Z> {
|
||||
inner: QemuInProcessForkExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, SP, Z>,
|
||||
}
|
||||
|
@ -1,3 +1,8 @@
|
||||
#[cfg(feature = "usermode")]
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
ops::Range,
|
||||
};
|
||||
use std::{path::PathBuf, sync::Mutex};
|
||||
|
||||
use hashbrown::{hash_map::Entry, HashMap};
|
||||
@ -19,8 +24,13 @@ use crate::{
|
||||
Qemu,
|
||||
};
|
||||
|
||||
/// Trace of `block_id`s met at runtime
|
||||
static DRCOV_IDS: Mutex<Option<Vec<u64>>> = Mutex::new(None);
|
||||
|
||||
///Map of `pc` -> `block_id`
|
||||
static DRCOV_MAP: Mutex<Option<HashMap<GuestAddr, u64>>> = Mutex::new(None);
|
||||
|
||||
/// Map of `pc` -> `block_len`
|
||||
static DRCOV_LENGTHS: Mutex<Option<HashMap<GuestAddr, GuestUsize>>> = Mutex::new(None);
|
||||
|
||||
#[cfg_attr(
|
||||
@ -46,7 +56,7 @@ pub struct DrCovModuleBuilder<F> {
|
||||
filter: Option<F>,
|
||||
module_mapping: Option<RangeMap<u64, (u16, String)>>,
|
||||
filename: Option<PathBuf>,
|
||||
full_trace: Option<bool>,
|
||||
full_trace: bool,
|
||||
}
|
||||
|
||||
impl<F> DrCovModuleBuilder<F>
|
||||
@ -58,7 +68,7 @@ where
|
||||
self.filter.unwrap(),
|
||||
self.filename.unwrap(),
|
||||
self.module_mapping,
|
||||
self.full_trace.unwrap(),
|
||||
self.full_trace,
|
||||
)
|
||||
}
|
||||
|
||||
@ -97,7 +107,7 @@ where
|
||||
filter: self.filter,
|
||||
module_mapping: self.module_mapping,
|
||||
filename: self.filename,
|
||||
full_trace: Some(full_trace),
|
||||
full_trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,20 +121,243 @@ pub struct DrCovModule<F> {
|
||||
drcov_len: usize,
|
||||
}
|
||||
|
||||
pub fn gen_unique_block_ids<ET, F, I, S>(
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
state: Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
) -> Option<u64>
|
||||
where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
|
||||
if !drcov_module.must_instrument(pc) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let state = state.expect("The gen_unique_block_ids hook works only for in-process fuzzing. Is the Executor initialized?");
|
||||
if state
|
||||
.metadata_map_mut()
|
||||
.get_mut::<DrCovMetadata>()
|
||||
.is_none()
|
||||
{
|
||||
state.add_metadata(DrCovMetadata::new());
|
||||
}
|
||||
|
||||
let meta = state.metadata_map_mut().get_mut::<DrCovMetadata>().unwrap();
|
||||
|
||||
match DRCOV_MAP.lock().unwrap().as_mut().unwrap().entry(pc) {
|
||||
Entry::Occupied(entry) => {
|
||||
let id = *entry.get();
|
||||
if drcov_module.full_trace {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
let id = meta.current_id;
|
||||
entry.insert(id);
|
||||
meta.current_id = id + 1;
|
||||
if drcov_module.full_trace {
|
||||
// GuestAddress is u32 for 32 bit guests
|
||||
#[expect(clippy::unnecessary_cast)]
|
||||
Some(id as u64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
|
||||
pub fn gen_block_lengths<ET, F, I, S>(
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
block_length: GuestUsize,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
|
||||
if !drcov_module.must_instrument(pc) {
|
||||
return;
|
||||
}
|
||||
DRCOV_LENGTHS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(pc, block_length);
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
|
||||
pub fn exec_trace_block<ET, F, I, S>(
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: Option<&mut S>,
|
||||
id: u64,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
if emulator_modules.get::<DrCovModule<F>>().unwrap().full_trace {
|
||||
DRCOV_IDS.lock().unwrap().as_mut().unwrap().push(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, I, S> EmulatorModule<I, S> for DrCovModule<F>
|
||||
where
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
#[cfg(feature = "usermode")]
|
||||
fn first_exec<ET>(
|
||||
&mut self,
|
||||
qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: &mut S,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
if self.full_trace {
|
||||
emulator_modules.blocks(
|
||||
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
|
||||
Hook::Function(gen_block_lengths::<ET, F, I, S>),
|
||||
Hook::Function(exec_trace_block::<ET, F, I, S>),
|
||||
);
|
||||
} else {
|
||||
emulator_modules.blocks(
|
||||
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
|
||||
Hook::Function(gen_block_lengths::<ET, F, I, S>),
|
||||
Hook::Empty,
|
||||
);
|
||||
};
|
||||
|
||||
if self.module_mapping.is_none() {
|
||||
log::info!("Auto-filling module mapping for DrCov module from QEMU mapping.");
|
||||
|
||||
let mut module_range_map: RangeMap<u64, (u16, String)> = RangeMap::new();
|
||||
let mut module_path_map: HashMap<String, (Range<u64>, u16)> = HashMap::default();
|
||||
|
||||
// We are building a mapping of
|
||||
// Path -> pc_start..pc_end, where pc_start is the smallest pc for the path and pc_end the biggest pc.
|
||||
let mut i = 0;
|
||||
for ((map_start, map_end), map_path) in qemu.mappings().filter_map(|m| {
|
||||
m.path().filter(|p| !p.is_empty()).map(|p| {
|
||||
(
|
||||
(
|
||||
u64::try_from(m.start()).unwrap(),
|
||||
u64::try_from(m.end()).unwrap(),
|
||||
),
|
||||
p.to_string(),
|
||||
)
|
||||
})
|
||||
}) {
|
||||
// Check if path is already present
|
||||
match module_path_map.entry(map_path) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
// If present, try to widen the range if necessary
|
||||
let (range, _) = entry.get_mut();
|
||||
range.start = min(range.start, map_start);
|
||||
range.end = max(range.end, map_end);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert((map_start..map_end, i));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
// module_mapping.insert(r, (i as u16, p));
|
||||
}
|
||||
|
||||
// Now, we can reorder the data by building a RangeMap and consume the old map.
|
||||
for (path, (range, id)) in module_path_map {
|
||||
module_range_map.insert(range, (id, path));
|
||||
}
|
||||
|
||||
self.module_mapping = Some(module_range_map);
|
||||
} else {
|
||||
log::info!("Using user-provided module mapping for DrCov module.");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
fn first_exec<ET>(
|
||||
&mut self,
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: &mut S,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
assert!(
|
||||
self.module_mapping.is_some(),
|
||||
"DrCov should have a module mapping already set."
|
||||
);
|
||||
|
||||
if self.full_trace {
|
||||
emulator_modules.blocks(
|
||||
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
|
||||
Hook::Function(gen_block_lengths::<ET, F, I, S>),
|
||||
Hook::Function(exec_trace_block::<ET, F, I, S>),
|
||||
);
|
||||
} else {
|
||||
emulator_modules.blocks(
|
||||
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
|
||||
Hook::Function(gen_block_lengths::<ET, F, I, S>),
|
||||
Hook::Empty,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn post_exec<OT, ET>(
|
||||
&mut self,
|
||||
_qemu: Qemu,
|
||||
_emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: &mut S,
|
||||
_input: &I,
|
||||
_observers: &mut OT,
|
||||
_exit_kind: &mut ExitKind,
|
||||
) where
|
||||
OT: ObserversTuple<I, S>,
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
self.flush();
|
||||
}
|
||||
|
||||
unsafe fn on_crash(&mut self) {
|
||||
self.flush();
|
||||
}
|
||||
|
||||
unsafe fn on_timeout(&mut self) {
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
|
||||
impl DrCovModule<NopAddressFilter> {
|
||||
#[must_use]
|
||||
pub fn builder() -> DrCovModuleBuilder<NopAddressFilter> {
|
||||
DrCovModuleBuilder {
|
||||
filter: Some(NopAddressFilter),
|
||||
module_mapping: None,
|
||||
full_trace: None,
|
||||
full_trace: false,
|
||||
filename: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> DrCovModule<F> {
|
||||
#[must_use]
|
||||
#[expect(clippy::let_underscore_untyped)]
|
||||
pub fn new(
|
||||
filter: F,
|
||||
filename: PathBuf,
|
||||
@ -132,10 +365,12 @@ impl<F> DrCovModule<F> {
|
||||
full_trace: bool,
|
||||
) -> Self {
|
||||
if full_trace {
|
||||
let _ = DRCOV_IDS.lock().unwrap().insert(vec![]);
|
||||
*DRCOV_IDS.lock().unwrap() = Some(vec![]);
|
||||
}
|
||||
let _ = DRCOV_MAP.lock().unwrap().insert(HashMap::new());
|
||||
let _ = DRCOV_LENGTHS.lock().unwrap().insert(HashMap::new());
|
||||
|
||||
*DRCOV_MAP.lock().unwrap() = Some(HashMap::new());
|
||||
*DRCOV_LENGTHS.lock().unwrap() = Some(HashMap::new());
|
||||
|
||||
Self {
|
||||
filter,
|
||||
module_mapping,
|
||||
@ -145,7 +380,7 @@ impl<F> DrCovModule<F> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self) {
|
||||
pub fn flush(&mut self) {
|
||||
let lengths_opt = DRCOV_LENGTHS.lock().unwrap();
|
||||
let lengths = lengths_opt.as_ref().unwrap();
|
||||
if self.full_trace {
|
||||
@ -225,8 +460,9 @@ impl<F> DrCovModule<F> {
|
||||
match lengths.get(pc) {
|
||||
Some(block_length) => {
|
||||
drcov_vec.push(DrCovBasicBlock::new(
|
||||
*pc as u64,
|
||||
*pc as u64 + *block_length as u64,
|
||||
u64::try_from(*pc).unwrap(),
|
||||
u64::try_from(*pc).unwrap() as u64
|
||||
+ u64::try_from(*block_length).unwrap(),
|
||||
));
|
||||
}
|
||||
None => {
|
||||
@ -259,96 +495,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, I, S> EmulatorModule<I, S> for DrCovModule<F>
|
||||
where
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
fn post_qemu_init<ET>(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules<ET, I, S>)
|
||||
where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
fn first_exec<ET>(
|
||||
&mut self,
|
||||
qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: &mut S,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
emulator_modules.blocks(
|
||||
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
|
||||
Hook::Function(gen_block_lengths::<ET, F, I, S>),
|
||||
Hook::Function(exec_trace_block::<ET, F, I, S>),
|
||||
);
|
||||
|
||||
if self.module_mapping.is_none() {
|
||||
log::info!("Auto-filling module mapping for DrCov module from QEMU mapping.");
|
||||
|
||||
let mut module_mapping: RangeMap<u64, (u16, String)> = RangeMap::new();
|
||||
|
||||
#[expect(clippy::unnecessary_cast)] // for GuestAddr -> u64
|
||||
for (i, (r, p)) in qemu
|
||||
.mappings()
|
||||
.filter_map(|m| {
|
||||
m.path()
|
||||
.map(|p| ((m.start() as u64)..(m.end() as u64), p.to_string()))
|
||||
.filter(|(_, p)| !p.is_empty())
|
||||
})
|
||||
.enumerate()
|
||||
{
|
||||
module_mapping.insert(r, (i as u16, p));
|
||||
}
|
||||
|
||||
self.module_mapping = Some(module_mapping);
|
||||
} else {
|
||||
log::info!("Using user-provided module mapping for DrCov module.");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
fn first_exec<ET>(
|
||||
&mut self,
|
||||
_qemu: Qemu,
|
||||
_emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: &mut S,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
assert!(
|
||||
self.module_mapping.is_some(),
|
||||
"DrCov should have a module mapping already set."
|
||||
);
|
||||
}
|
||||
|
||||
fn post_exec<OT, ET>(
|
||||
&mut self,
|
||||
_qemu: Qemu,
|
||||
_emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: &mut S,
|
||||
_input: &I,
|
||||
_observers: &mut OT,
|
||||
_exit_kind: &mut ExitKind,
|
||||
) where
|
||||
OT: ObserversTuple<I, S>,
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
{
|
||||
self.write();
|
||||
}
|
||||
|
||||
unsafe fn on_crash(&mut self) {
|
||||
self.write();
|
||||
}
|
||||
|
||||
unsafe fn on_timeout(&mut self) {
|
||||
self.write();
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> HasAddressFilter for DrCovModule<F>
|
||||
where
|
||||
F: AddressFilter,
|
||||
@ -375,96 +521,3 @@ where
|
||||
unsafe { (&raw mut NOP_PAGE_FILTER).as_mut().unwrap().get_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_unique_block_ids<ET, F, I, S>(
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
state: Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
) -> Option<u64>
|
||||
where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
|
||||
if !drcov_module.must_instrument(pc) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let state = state.expect("The gen_unique_block_ids hook works only for in-process fuzzing. Is the Executor initialized?");
|
||||
if state
|
||||
.metadata_map_mut()
|
||||
.get_mut::<DrCovMetadata>()
|
||||
.is_none()
|
||||
{
|
||||
state.add_metadata(DrCovMetadata::new());
|
||||
}
|
||||
let meta = state.metadata_map_mut().get_mut::<DrCovMetadata>().unwrap();
|
||||
|
||||
match DRCOV_MAP.lock().unwrap().as_mut().unwrap().entry(pc) {
|
||||
Entry::Occupied(e) => {
|
||||
let id = *e.get();
|
||||
if drcov_module.full_trace {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
let id = meta.current_id;
|
||||
e.insert(id);
|
||||
meta.current_id = id + 1;
|
||||
if drcov_module.full_trace {
|
||||
// GuestAddress is u32 for 32 bit guests
|
||||
#[expect(clippy::unnecessary_cast)]
|
||||
Some(id as u64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
|
||||
pub fn gen_block_lengths<ET, F, I, S>(
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
block_length: GuestUsize,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
|
||||
if !drcov_module.must_instrument(pc) {
|
||||
return;
|
||||
}
|
||||
DRCOV_LENGTHS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(pc, block_length);
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
|
||||
pub fn exec_trace_block<ET, F, I, S>(
|
||||
_qemu: Qemu,
|
||||
emulator_modules: &mut EmulatorModules<ET, I, S>,
|
||||
_state: Option<&mut S>,
|
||||
id: u64,
|
||||
) where
|
||||
ET: EmulatorModuleTuple<I, S>,
|
||||
F: AddressFilter,
|
||||
I: Unpin,
|
||||
S: Unpin + HasMetadata,
|
||||
{
|
||||
if emulator_modules.get::<DrCovModule<F>>().unwrap().full_trace {
|
||||
DRCOV_IDS.lock().unwrap().as_mut().unwrap().push(id);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
use core::{fmt::Debug, ops::Range};
|
||||
use core::fmt::Debug;
|
||||
|
||||
use libafl::{executors::ExitKind, observers::ObserversTuple};
|
||||
use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType};
|
||||
use libafl_qemu_sys::GuestAddr;
|
||||
#[cfg(feature = "systemmode")]
|
||||
use libafl_qemu_sys::GuestPhysAddr;
|
||||
|
||||
use crate::{
|
||||
emu::EmulatorModules,
|
||||
@ -15,6 +12,7 @@ use crate::{
|
||||
#[cfg(feature = "usermode")]
|
||||
pub mod usermode;
|
||||
#[cfg(feature = "usermode")]
|
||||
#[cfg_attr(feature = "hexagon", allow(unused_imports))]
|
||||
pub use usermode::*;
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
|
@ -34,6 +34,7 @@ use crate::{
|
||||
#[cfg(cpu_target = "hexagon")]
|
||||
/// Hexagon syscalls are not currently supported by the `syscalls` crate, so we just paste this here for now.
|
||||
/// <https://github.com/qemu/qemu/blob/11be70677c70fdccd452a3233653949b79e97908/linux-user/hexagon/syscall_nr.h#L230>
|
||||
#[expect(non_upper_case_globals)]
|
||||
const SYS_execve: u8 = 221;
|
||||
|
||||
/// Parses `injections.yaml`
|
||||
|
@ -1,5 +1,5 @@
|
||||
#![allow(clippy::needless_pass_by_value)] // default compiler complains about Option<&mut T> otherwise, and this is used extensively.
|
||||
use std::{cell::UnsafeCell, mem::MaybeUninit, sync::Mutex};
|
||||
use std::{cell::UnsafeCell, mem::MaybeUninit, ops::Range, sync::Mutex};
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use libafl_qemu_sys::{GuestAddr, MmapPerms};
|
||||
@ -25,7 +25,7 @@ use crate::{
|
||||
modules::{
|
||||
asan::AsanModule,
|
||||
utils::filters::{HasAddressFilter, NopAddressFilter, NOP_ADDRESS_FILTER},
|
||||
EmulatorModule, EmulatorModuleTuple, Range,
|
||||
EmulatorModule, EmulatorModuleTuple,
|
||||
},
|
||||
qemu::{Hook, SyscallHookResult},
|
||||
Qemu, SYS_brk, SYS_mprotect, SYS_mremap, SYS_munmap, SYS_pread64, SYS_read, SYS_readlinkat,
|
||||
|
@ -311,6 +311,7 @@ impl PageFilter for NopPageFilter {
|
||||
}
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) static mut NOP_ADDRESS_FILTER: UnsafeCell<NopAddressFilter> =
|
||||
UnsafeCell::new(NopAddressFilter);
|
||||
#[cfg(feature = "systemmode")]
|
||||
|
@ -251,6 +251,19 @@ fn parse_hex_to_u64(str: &str) -> Result<u64, ParseIntError> {
|
||||
u64::from_str_radix(&str[2..], 16)
|
||||
}
|
||||
|
||||
fn parse_path(s: &str) -> PathBuf {
|
||||
let s = s.trim();
|
||||
|
||||
// If first and last character is a quote, let's remove them
|
||||
let s = if s.starts_with('\"') && s.ends_with('\"'){
|
||||
&s[1..s.len() - 1]
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
||||
PathBuf::from(s)
|
||||
}
|
||||
|
||||
impl DrCovReader {
|
||||
/// Parse a `drcov` file to memory.
|
||||
pub fn read<P: AsRef<Path> + ?Sized>(file: &P) -> Result<Self, Error> {
|
||||
@ -336,7 +349,7 @@ impl DrCovReader {
|
||||
return Err(err("timestamp"));
|
||||
};
|
||||
|
||||
let Some(path) = split.next().map(|s| PathBuf::from(s.trim())) else {
|
||||
let Some(path) = split.next().map(parse_path) else {
|
||||
return Err(err("path"));
|
||||
};
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[allow(unused_imports)] // for no-std
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
|
@ -205,7 +205,7 @@ extern "C" {
|
||||
#[no_mangle]
|
||||
#[allow(unused_assignments)] // cfg dependent
|
||||
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
|
||||
#[allow(unused_mut)] // cfg dependent
|
||||
#[allow(unused_variables, unused_mut)] // cfg dependent
|
||||
let mut pos = *guard as usize;
|
||||
|
||||
#[cfg(any(feature = "sancov_ngram4", feature = "sancov_ngram8"))]
|
||||
|
@ -59,7 +59,7 @@ for project in "${PROJECTS[@]}"; do
|
||||
features="--features=clippy"
|
||||
fi
|
||||
if [ -d "$project" ]; then
|
||||
run_clippy "$project" $features
|
||||
run_clippy "$project" "$features"
|
||||
else
|
||||
echo "Warning: Directory $project does not exist. Skipping."
|
||||
fi
|
||||
|
@ -9,6 +9,7 @@ categories = ["development-tools"]
|
||||
keywords = ["fuzzing", "libafl", "drcov"]
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.11.6"
|
||||
libafl_targets = { workspace = true, default-features = true }
|
||||
clap = { workspace = true, features = ["derive", "wrap_help"] }
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
fs::{create_dir_all, File},
|
||||
io::Write,
|
||||
io::{Error, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
@ -17,15 +17,25 @@ use libafl_targets::drcov::DrCovReader;
|
||||
pub struct Opt {
|
||||
#[arg(short, long, help = "DrCov traces to read", required = true)]
|
||||
pub inputs: Vec<PathBuf>,
|
||||
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Output folder to write address files to. If none is set, this will output all addresses to stdout."
|
||||
)]
|
||||
pub out_dir: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long, help = "Print the metadata of the drcov file.")]
|
||||
pub metadata: bool,
|
||||
|
||||
#[arg(short, long, help = "Dump the addresses.")]
|
||||
pub addrs: bool,
|
||||
|
||||
#[arg(short, long, help = "Sort the addresses from smallest to biggest.")]
|
||||
pub sort: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Error> {
|
||||
let opts = Opt::parse();
|
||||
|
||||
if let Some(out_dir) = &opts.out_dir {
|
||||
@ -45,7 +55,13 @@ fn main() {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(out_dir) = &opts.out_dir {
|
||||
let mut blocks = drcov.basic_block_addresses_u64();
|
||||
|
||||
if opts.sort {
|
||||
blocks.sort_unstable();
|
||||
}
|
||||
|
||||
let mut writer: Box<dyn Write> = if let Some(out_dir) = &opts.out_dir {
|
||||
// Write files to a directory
|
||||
let out_file = out_dir.join(
|
||||
input
|
||||
@ -53,27 +69,47 @@ fn main() {
|
||||
.expect("File without filename shouldn't exist"),
|
||||
);
|
||||
|
||||
let Ok(mut file) = File::create_new(&out_file).map_err(|err| {
|
||||
let Ok(file) = File::create_new(&out_file).map_err(|err| {
|
||||
eprintln!("Could not create file {out_file:?} - continuing: {err:?}");
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
println!("Dumping addresses from drcov file {input:?} to {out_file:?}");
|
||||
println!("Dumping traces from drcov file {input:?} to {out_file:?}",);
|
||||
|
||||
for line in drcov.basic_block_addresses_u64() {
|
||||
file.write_all(format!("{line:#x}\n").as_bytes())
|
||||
.expect("Could not write to file");
|
||||
}
|
||||
Box::new(file)
|
||||
} else {
|
||||
// dump to stdout
|
||||
println!("# Blocks covered in {input:?}:");
|
||||
Box::new(std::io::stdout())
|
||||
};
|
||||
|
||||
for line in drcov.basic_block_addresses_u64() {
|
||||
println!("{line:#x}");
|
||||
// dump to stdout
|
||||
let modules = &drcov.module_entries;
|
||||
|
||||
if opts.metadata {
|
||||
writeln!(writer, "# {} Modules:", modules.len())?;
|
||||
for module in &drcov.module_entries {
|
||||
writeln!(
|
||||
writer,
|
||||
"\t{} - [{:#020x}-{:#020x}] {}",
|
||||
module.id,
|
||||
module.base,
|
||||
module.end,
|
||||
module.path.display()
|
||||
)?;
|
||||
}
|
||||
writeln!(writer, "# {} Blocks covered in {input:?}.", blocks.len())?;
|
||||
|
||||
println!();
|
||||
if opts.addrs {
|
||||
writeln!(writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
if opts.addrs {
|
||||
for line in blocks {
|
||||
writeln!(writer, "{line:#x}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ pub struct Opt {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let opts = Opt::parse();
|
||||
|
||||
assert!(
|
||||
|
Loading…
x
Reference in New Issue
Block a user