From 4083f0ba73b899e420988fa9c014d89d4c63bcad Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Fri, 24 Jan 2025 18:21:51 +0100 Subject: [PATCH] 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. --- Cargo.toml | 8 +- bindings/pylibafl/Cargo.toml | 6 +- .../binary_only/qemu_coverage/Makefile.toml | 8 +- .../binary_only/qemu_coverage/src/fuzzer.rs | 5 +- libafl/src/lib.rs | 2 + libafl_bolts/src/lib.rs | 2 + libafl_bolts/src/tuples.rs | 9 +- libafl_cc/build.rs | 44 +- libafl_concolic/symcc_runtime/src/lib.rs | 2 + libafl_concolic/symcc_runtime/src/tracing.rs | 1 + libafl_intelpt/src/lib.rs | 4 +- libafl_libfuzzer/build.rs | 57 +-- libafl_qemu/build_linux.rs | 4 +- libafl_qemu/src/arch/hexagon.rs | 2 +- libafl_qemu/src/executor.rs | 1 - libafl_qemu/src/modules/drcov.rs | 441 ++++++++++-------- libafl_qemu/src/modules/mod.rs | 6 +- .../src/modules/usermode/injections.rs | 1 + libafl_qemu/src/modules/usermode/snapshot.rs | 4 +- libafl_qemu/src/modules/utils/filters.rs | 1 + libafl_targets/src/drcov.rs | 15 +- libafl_targets/src/lib.rs | 2 +- libafl_targets/src/sancov_pcguard.rs | 2 +- scripts/clippy.sh | 2 +- utils/drcov_utils/Cargo.toml | 1 + utils/drcov_utils/src/bin/drcov_dump_addrs.rs | 64 ++- utils/drcov_utils/src/bin/drcov_merge.rs | 1 + 27 files changed, 419 insertions(+), 276 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 588f8b55aa..40657c843b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/bindings/pylibafl/Cargo.toml b/bindings/pylibafl/Cargo.toml index dc63e25c79..bfaf396381 100644 --- a/bindings/pylibafl/Cargo.toml +++ b/bindings/pylibafl/Cargo.toml @@ -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" diff --git a/fuzzers/binary_only/qemu_coverage/Makefile.toml b/fuzzers/binary_only/qemu_coverage/Makefile.toml index 979c2f83fc..362cc94645 100644 --- a/fuzzers/binary_only/qemu_coverage/Makefile.toml +++ b/fuzzers/binary_only/qemu_coverage/Makefile.toml @@ -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)) diff --git a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs index fcc2ff6cb9..bcfffb4f12 100644 --- a/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_coverage/src/fuzzer.rs @@ -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() ); diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 346feb244b..aaedf63ec7 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -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::*, *, diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index cea08cf501..6345311ae4 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -678,6 +678,8 @@ impl From 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::*, *}; } diff --git a/libafl_bolts/src/tuples.rs b/libafl_bolts/src/tuples.rs index 4c9b5c78aa..d1e779155a 100644 --- a/libafl_bolts/src/tuples.rs +++ b/libafl_bolts/src/tuples.rs @@ -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}; diff --git a/libafl_cc/build.rs b/libafl_cc/build.rs index d58b8ca835..09bde33e45 100644 --- a/libafl_cc/build.rs +++ b/libafl_cc/build.rs @@ -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 { 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, diff --git a/libafl_concolic/symcc_runtime/src/lib.rs b/libafl_concolic/symcc_runtime/src/lib.rs index 00c02e94b4..f0e3fd35bd 100644 --- a/libafl_concolic/symcc_runtime/src/lib.rs +++ b/libafl_concolic/symcc_runtime/src/lib.rs @@ -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")); } diff --git a/libafl_concolic/symcc_runtime/src/tracing.rs b/libafl_concolic/symcc_runtime/src/tracing.rs index 5b0cdf3113..3985fc913e 100644 --- a/libafl_concolic/symcc_runtime/src/tracing.rs +++ b/libafl_concolic/symcc_runtime/src/tracing.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; diff --git a/libafl_intelpt/src/lib.rs b/libafl_intelpt/src/lib.rs index 43402c2e77..fa78966064 100644 --- a/libafl_intelpt/src/lib.rs +++ b/libafl_intelpt/src/lib.rs @@ -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::{ diff --git a/libafl_libfuzzer/build.rs b/libafl_libfuzzer/build.rs index 6c293b4f0e..2f668e8542 100644 --- a/libafl_libfuzzer/build.rs +++ b/libafl_libfuzzer/build.rs @@ -102,36 +102,39 @@ fn main() -> Result<(), Box> { { 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!( diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index a250554525..a8a421e280 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -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"); diff --git a/libafl_qemu/src/arch/hexagon.rs b/libafl_qemu/src/arch/hexagon.rs index 479ff2588e..69d35beea1 100644 --- a/libafl_qemu/src/arch/hexagon.rs +++ b/libafl_qemu/src/arch/hexagon.rs @@ -133,7 +133,7 @@ impl crate::ArchExtras for crate::CPU { &self, conv: CallingConvention, idx: i32, - val: T, + _val: T, ) -> Result<(), QemuRWError> where T: Into, diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 6f3bd4abf3..8cc884ec12 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -302,7 +302,6 @@ pub type QemuInProcessForkExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, SP, Z StatefulInProcessForkExecutor<'a, EM, Emulator, 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>, } diff --git a/libafl_qemu/src/modules/drcov.rs b/libafl_qemu/src/modules/drcov.rs index be69ce189f..dcf156d59b 100644 --- a/libafl_qemu/src/modules/drcov.rs +++ b/libafl_qemu/src/modules/drcov.rs @@ -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>> = Mutex::new(None); + +///Map of `pc` -> `block_id` static DRCOV_MAP: Mutex>> = Mutex::new(None); + +/// Map of `pc` -> `block_len` static DRCOV_LENGTHS: Mutex>> = Mutex::new(None); #[cfg_attr( @@ -46,7 +56,7 @@ pub struct DrCovModuleBuilder { filter: Option, module_mapping: Option>, filename: Option, - full_trace: Option, + full_trace: bool, } impl DrCovModuleBuilder @@ -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 { drcov_len: usize, } +pub fn gen_unique_block_ids( + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + state: Option<&mut S>, + pc: GuestAddr, +) -> Option +where + ET: EmulatorModuleTuple, + F: AddressFilter, + I: Unpin, + S: Unpin + HasMetadata, +{ + let drcov_module = emulator_modules.get::>().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::() + .is_none() + { + state.add_metadata(DrCovMetadata::new()); + } + + let meta = state.metadata_map_mut().get_mut::().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( + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: Option<&mut S>, + pc: GuestAddr, + block_length: GuestUsize, +) where + ET: EmulatorModuleTuple, + F: AddressFilter, + I: Unpin, + S: Unpin + HasMetadata, +{ + let drcov_module = emulator_modules.get::>().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( + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: Option<&mut S>, + id: u64, +) where + ET: EmulatorModuleTuple, + F: AddressFilter, + I: Unpin, + S: Unpin + HasMetadata, +{ + if emulator_modules.get::>().unwrap().full_trace { + DRCOV_IDS.lock().unwrap().as_mut().unwrap().push(id); + } +} + +impl EmulatorModule for DrCovModule +where + F: AddressFilter, + I: Unpin, + S: Unpin + HasMetadata, +{ + #[cfg(feature = "usermode")] + fn first_exec( + &mut self, + qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where + ET: EmulatorModuleTuple, + { + if self.full_trace { + emulator_modules.blocks( + Hook::Function(gen_unique_block_ids::), + Hook::Function(gen_block_lengths::), + Hook::Function(exec_trace_block::), + ); + } else { + emulator_modules.blocks( + Hook::Function(gen_unique_block_ids::), + Hook::Function(gen_block_lengths::), + 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 = RangeMap::new(); + let mut module_path_map: HashMap, 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( + &mut self, + _qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where + ET: EmulatorModuleTuple, + { + 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::), + Hook::Function(gen_block_lengths::), + Hook::Function(exec_trace_block::), + ); + } else { + emulator_modules.blocks( + Hook::Function(gen_unique_block_ids::), + Hook::Function(gen_block_lengths::), + Hook::Empty, + ); + }; + } + + fn post_exec( + &mut self, + _qemu: Qemu, + _emulator_modules: &mut EmulatorModules, + _state: &mut S, + _input: &I, + _observers: &mut OT, + _exit_kind: &mut ExitKind, + ) where + OT: ObserversTuple, + ET: EmulatorModuleTuple, + { + self.flush(); + } + + unsafe fn on_crash(&mut self) { + self.flush(); + } + + unsafe fn on_timeout(&mut self) { + self.flush(); + } +} + impl DrCovModule { #[must_use] pub fn builder() -> DrCovModuleBuilder { DrCovModuleBuilder { filter: Some(NopAddressFilter), module_mapping: None, - full_trace: None, + full_trace: false, filename: None, } } } + impl DrCovModule { #[must_use] - #[expect(clippy::let_underscore_untyped)] pub fn new( filter: F, filename: PathBuf, @@ -132,10 +365,12 @@ impl DrCovModule { 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 DrCovModule { } } - 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 DrCovModule { 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 EmulatorModule for DrCovModule -where - F: AddressFilter, - I: Unpin, - S: Unpin + HasMetadata, -{ - fn post_qemu_init(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) - where - ET: EmulatorModuleTuple, - { - } - - #[cfg(feature = "usermode")] - fn first_exec( - &mut self, - qemu: Qemu, - emulator_modules: &mut EmulatorModules, - _state: &mut S, - ) where - ET: EmulatorModuleTuple, - { - emulator_modules.blocks( - Hook::Function(gen_unique_block_ids::), - Hook::Function(gen_block_lengths::), - Hook::Function(exec_trace_block::), - ); - - if self.module_mapping.is_none() { - log::info!("Auto-filling module mapping for DrCov module from QEMU mapping."); - - let mut module_mapping: RangeMap = 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( - &mut self, - _qemu: Qemu, - _emulator_modules: &mut EmulatorModules, - _state: &mut S, - ) where - ET: EmulatorModuleTuple, - { - assert!( - self.module_mapping.is_some(), - "DrCov should have a module mapping already set." - ); - } - - fn post_exec( - &mut self, - _qemu: Qemu, - _emulator_modules: &mut EmulatorModules, - _state: &mut S, - _input: &I, - _observers: &mut OT, - _exit_kind: &mut ExitKind, - ) where - OT: ObserversTuple, - ET: EmulatorModuleTuple, - { - self.write(); - } - - unsafe fn on_crash(&mut self) { - self.write(); - } - - unsafe fn on_timeout(&mut self) { - self.write(); - } -} - impl HasAddressFilter for DrCovModule 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( - _qemu: Qemu, - emulator_modules: &mut EmulatorModules, - state: Option<&mut S>, - pc: GuestAddr, -) -> Option -where - ET: EmulatorModuleTuple, - F: AddressFilter, - I: Unpin, - S: Unpin + HasMetadata, -{ - let drcov_module = emulator_modules.get::>().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::() - .is_none() - { - state.add_metadata(DrCovMetadata::new()); - } - let meta = state.metadata_map_mut().get_mut::().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( - _qemu: Qemu, - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - pc: GuestAddr, - block_length: GuestUsize, -) where - ET: EmulatorModuleTuple, - F: AddressFilter, - I: Unpin, - S: Unpin + HasMetadata, -{ - let drcov_module = emulator_modules.get::>().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( - _qemu: Qemu, - emulator_modules: &mut EmulatorModules, - _state: Option<&mut S>, - id: u64, -) where - ET: EmulatorModuleTuple, - F: AddressFilter, - I: Unpin, - S: Unpin + HasMetadata, -{ - if emulator_modules.get::>().unwrap().full_trace { - DRCOV_IDS.lock().unwrap().as_mut().unwrap().push(id); - } -} diff --git a/libafl_qemu/src/modules/mod.rs b/libafl_qemu/src/modules/mod.rs index 054c962a6a..71e403a727 100644 --- a/libafl_qemu/src/modules/mod.rs +++ b/libafl_qemu/src/modules/mod.rs @@ -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")] diff --git a/libafl_qemu/src/modules/usermode/injections.rs b/libafl_qemu/src/modules/usermode/injections.rs index daeee9d81f..d469f8d33a 100644 --- a/libafl_qemu/src/modules/usermode/injections.rs +++ b/libafl_qemu/src/modules/usermode/injections.rs @@ -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. /// +#[expect(non_upper_case_globals)] const SYS_execve: u8 = 221; /// Parses `injections.yaml` diff --git a/libafl_qemu/src/modules/usermode/snapshot.rs b/libafl_qemu/src/modules/usermode/snapshot.rs index 1ce4f8a4a9..a0e020145a 100644 --- a/libafl_qemu/src/modules/usermode/snapshot.rs +++ b/libafl_qemu/src/modules/usermode/snapshot.rs @@ -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, diff --git a/libafl_qemu/src/modules/utils/filters.rs b/libafl_qemu/src/modules/utils/filters.rs index 49baa34d8e..0845a29e8d 100644 --- a/libafl_qemu/src/modules/utils/filters.rs +++ b/libafl_qemu/src/modules/utils/filters.rs @@ -311,6 +311,7 @@ impl PageFilter for NopPageFilter { } #[cfg(feature = "usermode")] +#[allow(dead_code)] pub(crate) static mut NOP_ADDRESS_FILTER: UnsafeCell = UnsafeCell::new(NopAddressFilter); #[cfg(feature = "systemmode")] diff --git a/libafl_targets/src/drcov.rs b/libafl_targets/src/drcov.rs index 9f8b448718..28056ad14d 100644 --- a/libafl_targets/src/drcov.rs +++ b/libafl_targets/src/drcov.rs @@ -251,6 +251,19 @@ fn parse_hex_to_u64(str: &str) -> Result { 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 + ?Sized>(file: &P) -> Result { @@ -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")); }; diff --git a/libafl_targets/src/lib.rs b/libafl_targets/src/lib.rs index 43fc18b731..07a027dfe0 100644 --- a/libafl_targets/src/lib.rs +++ b/libafl_targets/src/lib.rs @@ -46,7 +46,7 @@ #[macro_use] extern crate std; -#[allow(unused_imports)] // for no-std +#[allow(unused_imports)] #[macro_use] extern crate alloc; diff --git a/libafl_targets/src/sancov_pcguard.rs b/libafl_targets/src/sancov_pcguard.rs index a65e73cb55..bec95b5f97 100644 --- a/libafl_targets/src/sancov_pcguard.rs +++ b/libafl_targets/src/sancov_pcguard.rs @@ -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"))] diff --git a/scripts/clippy.sh b/scripts/clippy.sh index 44bac06fc8..297c61c102 100755 --- a/scripts/clippy.sh +++ b/scripts/clippy.sh @@ -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 diff --git a/utils/drcov_utils/Cargo.toml b/utils/drcov_utils/Cargo.toml index b74f894a70..cc72570e3c 100644 --- a/utils/drcov_utils/Cargo.toml +++ b/utils/drcov_utils/Cargo.toml @@ -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"] } diff --git a/utils/drcov_utils/src/bin/drcov_dump_addrs.rs b/utils/drcov_utils/src/bin/drcov_dump_addrs.rs index 262cdaf6b1..a404eb40fb 100644 --- a/utils/drcov_utils/src/bin/drcov_dump_addrs.rs +++ b/utils/drcov_utils/src/bin/drcov_dump_addrs.rs @@ -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, + #[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, + + #[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 = 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(()) } diff --git a/utils/drcov_utils/src/bin/drcov_merge.rs b/utils/drcov_utils/src/bin/drcov_merge.rs index 13d3d4a4d3..f0ae0445f9 100644 --- a/utils/drcov_utils/src/bin/drcov_merge.rs +++ b/utils/drcov_utils/src/bin/drcov_merge.rs @@ -24,6 +24,7 @@ pub struct Opt { } fn main() { + env_logger::init(); let opts = Opt::parse(); assert!(