From 50bdc8af7b16c56523622c0674fd4970c7c1cd44 Mon Sep 17 00:00:00 2001 From: Marco Cavenati Date: Mon, 28 Jul 2025 13:54:32 +0200 Subject: [PATCH 1/5] qemu intel PT: improve VM tracing - Better filter out the VMX root traces during decoding thanks to the new `resync` API in libipt (bumped to 0.4.0). - Extract the Decoder from the linux intel_pt file, since the file/struct was getting too big and the decoder will eventually be compatible with windows. - PT tracing is now enabled manually by fuzzers to have more precise control, instead of beeing always on in vm operations. - Add KVM dirty tracing option to qemu config and raw string options --- Cargo.toml | 2 +- crates/libafl_intelpt/src/decoder.rs | 203 ++++++++++++++ crates/libafl_intelpt/src/lib.rs | 3 + crates/libafl_intelpt/src/linux.rs | 259 +++--------------- .../src/modules/systemmode/intel_pt.rs | 47 +--- crates/libafl_qemu/src/qemu/config.rs | 42 ++- 6 files changed, 302 insertions(+), 254 deletions(-) create mode 100644 crates/libafl_intelpt/src/decoder.rs diff --git a/Cargo.toml b/Cargo.toml index c4998b321c1..21f77115425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ fastbloom = { version = "0.14.0", default-features = false } hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible just = "=1.40.0" libc = "0.2.159" # For (*nix) libc -libipt = "0.3.0" +libipt = { version = "0.4.0", features = ["libipt_master"] } log = "0.4.22" meminterval = "0.4.2" mimalloc = { version = "0.1.43", default-features = false } diff --git a/crates/libafl_intelpt/src/decoder.rs b/crates/libafl_intelpt/src/decoder.rs new file mode 100644 index 00000000000..f6c10847a6f --- /dev/null +++ b/crates/libafl_intelpt/src/decoder.rs @@ -0,0 +1,203 @@ +use core::fmt::Debug; + +use libafl_bolts::{Error, hash_64_fast}; +use libipt::{ + block::BlockDecoder, enc_dec_builder::EncoderDecoderBuilder, error::PtErrorCode, + event::EventType, image::Image, status::Status, +}; +use num_traits::SaturatingAdd; + +use crate::error_from_pt_error; + +#[derive(Debug)] +pub(crate) struct Decoder<'a, T> { + decoder: BlockDecoder<'a>, + status: Status, + previous_block_end_ip: u64, + vmx_non_root: Option, + exclude_hv: bool, + trace_skip: u64, + map_ptr: *mut T, + map_len: usize, +} + +impl<'a, T> Decoder<'a, T> +where + T: SaturatingAdd + From + Debug, +{ + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + decoder_builder: EncoderDecoderBuilder>, + exclude_hv: bool, + image: &'a mut Image, + trace_ptr: *mut u8, + trace_len: usize, + trace_skip: u64, + map_ptr: *mut T, + map_len: usize, + ) -> Result { + let builder = unsafe { decoder_builder.buffer_from_raw(trace_ptr, trace_len) }; + + let mut decoder = builder.build().map_err(error_from_pt_error)?; + decoder + .set_image(Some(image)) + .map_err(error_from_pt_error)?; + let status = Status::empty(); + + Ok(Self { + decoder, + status, + previous_block_end_ip: 0, + vmx_non_root: None, + exclude_hv, + trace_skip, + map_ptr, + map_len, + }) + } + + pub(crate) fn decode_traces_into_map(mut self) -> Result { + 'sync: loop { + match self.decoder.sync_forward() { + Ok(status) => { + self.status = status; + match self.decode_and_resync_loop() { + Ok(()) if self.status.eos() => break 'sync, + Ok(()) => (), + Err(e) => log::warn!("{e:?}"), + } + } + Err(e) => { + if e.code() != PtErrorCode::Eos { + log::warn!("PT error in sync forward {e:?}"); + } + break 'sync; + } + } + } + + self.decoder.sync_backward().map_err(error_from_pt_error)?; + self.decoder.sync_offset().map_err(error_from_pt_error) + } + + fn decode_and_resync_loop(&mut self) -> Result<(), Error> { + loop { + match self.decode_blocks_loop() { + Ok(()) if self.status.eos() => return Ok(()), + Ok(()) | Err(_) => (), + } + + match self.resync_loop() { + Ok(()) if self.status.eos() => return Ok(()), + Ok(()) => (), + Err(e) => return Err(e), + } + } + } + + fn resync_loop(&mut self) -> Result<(), Error> + where + T: SaturatingAdd + From + Debug, + { + loop { + match self.decoder.resync() { + Ok(s) => { + self.status = s; + if self.status.eos() { + return Ok(()); + } + + // If exclude_hv is set and we are in root VMX operation, continue resyncing + if self.exclude_hv || matches!(self.vmx_non_root, Some(true)) { + return Ok(()); + } + } + Err(e) => match e.code() { + PtErrorCode::Eos => return Ok(()), + PtErrorCode::EventIgnored => self.handle_event()?, + _ => return Err(Error::illegal_state(format!("PT error in resync {e:?}"))), + }, + } + } + } + + fn decode_blocks_loop(&mut self) -> Result<(), Error> + where + T: SaturatingAdd + From + Debug, + { + #[cfg(debug_assertions)] + let mut trace_entry_iters: (u64, u64) = (0, 0); + + loop { + #[cfg(debug_assertions)] + { + let offset = self.decoder.offset().map_err(error_from_pt_error)?; + if trace_entry_iters.0 == offset { + trace_entry_iters.1 += 1; + if trace_entry_iters.1 > 1000 { + return Err(Error::illegal_state(format!( + "PT Decoder got stuck at trace offset {offset:x}.\ + Make sure the decoder Image has the right content and offsets.", + ))); + } + } else { + trace_entry_iters = (offset, 0); + } + } + + while self.status.event_pending() { + self.handle_event()?; + } + + // If exclude_hv is set and we are in root VMX operation, bail out + if self.exclude_hv && matches!(self.vmx_non_root, Some(false)) { + return Ok(()); + } + + match self.decoder.decode_next() { + Ok((b, s)) => { + self.status = s; + let offset = self.decoder.offset().map_err(error_from_pt_error)?; + if b.ninsn() > 0 && self.trace_skip < offset { + let id = hash_64_fast(self.previous_block_end_ip) ^ hash_64_fast(b.ip()); + // SAFETY: the index is < map_len since the modulo operation is applied + unsafe { + let map_loc = self.map_ptr.add(id as usize % self.map_len); + *map_loc = (*map_loc).saturating_add(&1u8.into()); + } + self.previous_block_end_ip = b.end_ip(); + } + + if self.status.eos() { + return Ok(()); + } + } + Err(e) => { + if e.code() != PtErrorCode::Eos { + let offset = self.decoder.offset().map_err(error_from_pt_error)?; + log::info!( + "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", + self.previous_block_end_ip + ); + } + return Err(error_from_pt_error(e)); + } + } + } + } + + fn handle_event(&mut self) -> Result<(), Error> { + match self.decoder.event() { + Ok((event, s)) => { + self.status = s; + match event.event_type() { + EventType::Paging(p) => self.vmx_non_root = Some(p.non_root()), + EventType::AsyncPaging(p) => self.vmx_non_root = Some(p.non_root()), + _ => (), + } + Ok(()) + } + Err(e) => Err(Error::illegal_state(format!("PT error in event {e:?}"))), + } + } +} diff --git a/crates/libafl_intelpt/src/lib.rs b/crates/libafl_intelpt/src/lib.rs index 46d2e323874..29937dc3aa7 100644 --- a/crates/libafl_intelpt/src/lib.rs +++ b/crates/libafl_intelpt/src/lib.rs @@ -20,6 +20,9 @@ use std::fs; use raw_cpuid::CpuId; +#[cfg(target_os = "linux")] +// This should be windows compatible. It's behind linux check just to avoid unused errors etc. +mod decoder; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] diff --git a/crates/libafl_intelpt/src/linux.rs b/crates/libafl_intelpt/src/linux.rs index ffbe67d5fd4..e00b1791166 100644 --- a/crates/libafl_intelpt/src/linux.rs +++ b/crates/libafl_intelpt/src/linux.rs @@ -8,7 +8,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use core::{ffi::CStr, fmt::Debug, ops::RangeInclusive, ptr}; +use core::{ffi::CStr, fmt::Debug, ptr}; use std::{ fs, io::{Read, Seek, SeekFrom}, @@ -23,19 +23,17 @@ use std::{ use arbitrary_int::u4; use bitbybit::bitfield; use caps::{CapSet, Capability}; -use libafl_bolts::{Error, hash_64_fast}; +use libafl_bolts::Error; pub use libipt::{ asid::Asid, + enc_dec_builder::{AddrFilter, AddrFilterType, AddrFilters}, image::{Image, SectionCache, SectionInfo}, status::Status, }; use libipt::{ block::BlockDecoder, - enc_dec_builder::{ - AddrFilterRange, AddrFilterType, AddrFilters, AddrFiltersBuilder, Cpu, - EncoderDecoderBuilder, - }, - error::{PtError, PtErrorCode}, + enc_dec_builder::{Cpu, EncoderDecoderBuilder}, + error::PtError, }; use num_enum::TryFromPrimitive; use num_traits::{Euclid, SaturatingAdd}; @@ -47,6 +45,7 @@ use perf_event_open_sys::{ use raw_cpuid::CpuId; use super::{PAGE_SIZE, availability}; +use crate::decoder::Decoder; const PT_EVENT_PATH: &str = "/sys/bus/event_source/devices/intel_pt"; @@ -101,9 +100,9 @@ pub struct IntelPT { aux_head: *mut u64, aux_tail: *mut u64, previous_decode_head: u64, - ip_filters: Vec>, // The lifetime of BlockDecoder<'a> is irrelevant during the building phase. decoder_builder: EncoderDecoderBuilder>, + exclude_hv: bool, #[cfg(feature = "export_raw")] last_decode_trace: Vec, } @@ -120,12 +119,13 @@ impl IntelPT { /// Set filters based on Instruction Pointer (IP) /// /// Only instructions in `filters` ranges will be traced. - pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> Result<(), Error> { + fn set_ip_filters(&mut self, filters: &AddrFilters) -> Result<(), Error> { let str_filter = filters .iter() + .filter(|f| f.filter_type == AddrFilterType::FILTER) .map(|filter| { - let size = filter.end() - filter.start(); - format!("filter {:#016x}/{:#016x} ", filter.start(), size) + let size = filter.to - filter.from; + format!("filter {:#016x}/{:#016x} ", filter.from, size) }) .reduce(|acc, s| acc + &s) .unwrap_or_default(); @@ -138,11 +138,15 @@ impl IntelPT { Ok(()) => String::new(), Err(reasons) => format!(" Possible reasons: {reasons}"), }; - let not_enough_filters = if filters.len() > nr_addr_filters().unwrap_or(0) as usize + let n_set_filters = filters + .iter() + .filter(|f| f.filter_type == AddrFilterType::FILTER) + .count(); + let not_enough_filters = if n_set_filters > nr_addr_filters().unwrap_or(0) as usize { format!( " Not enough filters, trying to set {} filters while {} available.", - filters.len(), + n_set_filters, nr_addr_filters().unwrap_or(0) ) } else { @@ -152,42 +156,13 @@ impl IntelPT { "Failed to set IP filters.{not_enough_filters}{availability}" ))) } - 0 => { - self.ip_filters = filters.to_vec(); - Ok(()) - } + 0 => Ok(()), ret => Err(Error::unsupported(format!( "Failed to set IP filter, ioctl returned unexpected value {ret}" ))), } } - /// Get the current IP filters configuration - #[must_use] - pub fn ip_filters(&self) -> Vec> { - self.ip_filters.clone() - } - - fn ip_filters_to_addr_filter(&self) -> AddrFilters { - let mut builder = AddrFiltersBuilder::new(); - let mut iter = self.ip_filters.iter().map(|f| { - AddrFilterRange::new(*f.start() as u64, *f.end() as u64, AddrFilterType::FILTER) - }); - if let Some(f) = iter.next() { - builder.addr0(f); - if let Some(f) = iter.next() { - builder.addr1(f); - if let Some(f) = iter.next() { - builder.addr2(f); - if let Some(f) = iter.next() { - builder.addr3(f); - } - } - } - } - builder.build() - } - /// Start tracing /// /// Be aware that the tracing is not started on [`IntelPT`] construction. @@ -222,40 +197,6 @@ impl IntelPT { } } - // /// Fill the coverage map by decoding the PT traces and reading target memory through `read_mem` - // /// - // /// This function consumes the traces. - // /// - // /// # Example - // /// - // /// An example `read_mem` callback function for the (inprocess) `intel_pt_babyfuzzer` could be: - // /// ``` - // /// let read_mem = |buf: &mut [u8], addr: u64| { - // /// let src = addr as *const u8; - // /// let dst = buf.as_mut_ptr(); - // /// let size = buf.len(); - // /// unsafe { - // /// core::ptr::copy_nonoverlapping(src, dst, size); - // /// } - // /// }; - // /// ``` - // #[allow(clippy::cast_possible_wrap)] - // pub fn decode_with_callback(&mut self, read_memory: F, map: &mut [T]) -> Result<(), Error> - // where - // F: Fn(&mut [u8], u64), - // T: SaturatingAdd + From + Debug, - // { - // self.decode_traces_into_map_common( - // None, - // Some(|buff: &mut [u8], addr: u64, _: Asid| { - // debug_assert!(i32::try_from(buff.len()).is_ok()); - // read_memory(buff, addr); - // buff.len() as i32 - // }), - // map, - // ) - // } - /// Fill the coverage map by decoding the PT traces /// /// This function consumes the traces. @@ -267,25 +208,6 @@ impl IntelPT { ) -> Result<(), Error> where T: SaturatingAdd + From + Debug, - { - self.decode_traces_into_map_common( - Some(image), - None:: i32>, - map_ptr, - map_len, - ) - } - - fn decode_traces_into_map_common( - &mut self, - image: Option<&mut Image>, - read_memory: Option, - map_ptr: *mut T, - map_len: usize, - ) -> Result<(), Error> - where - F: Fn(&mut [u8], u64, Asid) -> i32, - T: SaturatingAdd + From + Debug, { let head = unsafe { self.aux_head.read_volatile() }; let tail = unsafe { self.aux_tail.read_volatile() }; @@ -333,43 +255,21 @@ impl IntelPT { self.last_decode_trace.set_len(len); } } - let builder = unsafe { self.decoder_builder.clone().buffer_from_raw(data_ptr, len) } - .filter(self.ip_filters_to_addr_filter()); - let mut decoder = builder.build().map_err(error_from_pt_error)?; - decoder.set_image(image).map_err(error_from_pt_error)?; - if let Some(rm) = read_memory { - decoder.image().set_callback(Some(rm)); - } - - let mut previous_block_end_ip = 0; - let mut status; - 'sync: loop { - match decoder.sync_forward() { - Ok(s) => { - status = s; - Self::decode_blocks( - &mut decoder, - &mut status, - &mut previous_block_end_ip, - skip, - map_ptr, - map_len, - )?; - } - Err(e) => { - if e.code() != PtErrorCode::Eos { - log::info!("PT error in sync forward {e:?}"); - } - break 'sync; - } - } - } + let decoder = Decoder::new( + self.decoder_builder.clone(), + self.exclude_hv, + image, + data_ptr, + len, + skip, + map_ptr, + map_len, + )?; + let offset = decoder.decode_traces_into_map()?; // Advance the trace pointer up to the latest sync point, otherwise next execution's trace // might not contain a PSB packet. - decoder.sync_backward().map_err(error_from_pt_error)?; - let offset = decoder.sync_offset().map_err(error_from_pt_error)?; unsafe { self.aux_tail.write_volatile(tail + offset) }; self.previous_decode_head = head; Ok(()) @@ -406,82 +306,6 @@ impl IntelPT { } } - #[inline] - fn decode_blocks( - decoder: &mut BlockDecoder, - status: &mut Status, - previous_block_end_ip: &mut u64, - skip: u64, - map_ptr: *mut T, - map_len: usize, - ) -> Result<(), Error> - where - T: SaturatingAdd + From + Debug, - { - #[cfg(debug_assertions)] - let mut trace_entry_iters: (u64, u64) = (0, 0); - - 'block: loop { - let offset = decoder.offset().map_err(error_from_pt_error)?; - #[cfg(debug_assertions)] - { - if trace_entry_iters.0 == offset { - trace_entry_iters.1 += 1; - if trace_entry_iters.1 > 1000 { - log::warn!( - "Decoder got stuck at trace offset {offset:x}. Make sure the decoder Image has the right content and offsets." - ); - break 'block; - } - } else { - trace_entry_iters = (offset, 0); - } - } - - while status.event_pending() { - match decoder.event() { - Ok((_, s)) => { - *status = s; - } - Err(e) => { - log::info!("PT error in event {e:?}"); - break 'block; - } - } - } - - match decoder.decode_next() { - Ok((b, s)) => { - *status = s; - - if b.ninsn() > 0 && skip < offset { - let id = hash_64_fast(*previous_block_end_ip) ^ hash_64_fast(b.ip()); - // SAFETY: the index is < map_len since the modulo operation is applied - unsafe { - let map_loc = map_ptr.add(id as usize % map_len); - *map_loc = (*map_loc).saturating_add(&1u8.into()); - } - *previous_block_end_ip = b.end_ip(); - } - - if status.eos() { - break 'block; - } - } - Err(e) => { - if e.code() != PtErrorCode::Eos { - let offset = decoder.offset().map_err(error_from_pt_error)?; - log::info!( - "PT error in block next {e:?} trace offset {offset:x} last decoded block end {previous_block_end_ip:x}" - ); - } - break 'block; - } - } - } - Ok(()) - } - /// Get the raw trace used in the last decoding #[cfg(feature = "export_raw")] #[must_use] @@ -528,7 +352,7 @@ pub struct IntelPTBuilder { inherit: bool, perf_buffer_size: usize, perf_aux_buffer_size: usize, - ip_filters: Vec>, + ip_filters: AddrFilters, } impl Default for IntelPTBuilder { @@ -545,9 +369,9 @@ impl Default for IntelPTBuilder { /// .inherit(false) /// .perf_buffer_size(128 * PAGE_SIZE + PAGE_SIZE) /// .unwrap() - /// .perf_aux_buffer_size(4 * 1024 * 1024) + /// .perf_aux_buffer_size(16 * 1024 * 1024) /// .unwrap() - /// .ip_filters(&[]); + /// .ip_filters(&Default::default()); /// assert_eq!(builder, IntelPTBuilder::default()); /// ``` fn default() -> Self { @@ -558,8 +382,8 @@ impl Default for IntelPTBuilder { exclude_hv: true, inherit: false, perf_buffer_size: 128 * PAGE_SIZE + PAGE_SIZE, - perf_aux_buffer_size: 4 * 1024 * 1024, - ip_filters: Vec::new(), + perf_aux_buffer_size: 16 * 1024 * 1024, + ip_filters: AddrFilters::default(), } } } @@ -624,7 +448,8 @@ impl IntelPTBuilder { let mut decoder_builder = EncoderDecoderBuilder::new() .set_end_on_call(true) - .set_end_on_jump(true); + .set_end_on_jump(true) + .filter(self.ip_filters); if let Some(cpu) = current_cpu() { decoder_builder = decoder_builder.cpu(cpu); } @@ -638,14 +463,12 @@ impl IntelPTBuilder { aux_head, aux_tail, previous_decode_head: 0, - ip_filters: Vec::with_capacity(*NR_ADDR_FILTERS.as_ref().unwrap_or(&0) as usize), decoder_builder, + exclude_hv: self.exclude_hv, #[cfg(feature = "export_raw")] last_decode_trace: Vec::new(), }; - if !self.ip_filters.is_empty() { - intel_pt.set_ip_filters(&self.ip_filters)?; - } + intel_pt.set_ip_filters(&self.ip_filters)?; Ok(intel_pt) } @@ -742,10 +565,8 @@ impl IntelPTBuilder { #[must_use] /// Set filters based on Instruction Pointer (IP) - /// - /// Only instructions in `filters` ranges will be traced. - pub fn ip_filters(mut self, filters: &[RangeInclusive]) -> Self { - self.ip_filters = filters.to_vec(); + pub const fn ip_filters(mut self, filters: &AddrFilters) -> Self { + self.ip_filters = *filters; self } } diff --git a/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs b/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs index c9b25c416ee..d40869c394c 100644 --- a/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -1,18 +1,15 @@ -use std::{ - fmt::Debug, - ops::{Range, RangeInclusive}, -}; +use std::fmt::Debug; use libafl::{HasMetadata, observers::ObserversTuple}; -pub use libafl_intelpt::SectionInfo; +pub use libafl_intelpt::{AddrFilter, AddrFilterType, AddrFilters, SectionInfo}; use libafl_intelpt::{Image, IntelPT, IntelPTBuilder}; -use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr}; +use libafl_qemu_sys::CPUArchStatePtr; use num_traits::SaturatingAdd; use typed_builder::TypedBuilder; use crate::{ EmulatorModules, NewThreadHook, Qemu, QemuParams, - modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind}, + modules::{EmulatorModule, EmulatorModuleTuple, ExitKind}, }; #[derive(Debug, TypedBuilder)] @@ -37,6 +34,13 @@ impl IntelPTModule { } } +impl IntelPTModule { + pub fn enable_tracing(&mut self) { + let pt = self.pt.as_mut().expect("Intel PT module not initialized."); + pt.enable_tracing().unwrap(); + } +} + impl EmulatorModule for IntelPTModule where I: Unpin, @@ -65,8 +69,9 @@ where ) where ET: EmulatorModuleTuple, { - let pt = self.pt.as_mut().expect("Intel PT module not initialized."); - pt.enable_tracing().unwrap(); + if self.pt.is_none() { + panic!("Intel PT module not initialized."); + } } fn post_exec( @@ -97,30 +102,6 @@ where } } -impl AddressFilter for IntelPTModule -where - T: Debug + 'static, -{ - fn register(&mut self, address_range: &Range) { - let pt = self.pt.as_mut().unwrap(); - let mut filters = pt.ip_filters(); - let range_inclusive = - RangeInclusive::new(address_range.start as usize, address_range.end as usize - 1); - filters.push(range_inclusive); - pt.set_ip_filters(&filters).unwrap() - } - - fn allowed(&self, address: &GuestAddr) -> bool { - let pt = self.pt.as_ref().unwrap(); - for f in pt.ip_filters() { - if f.contains(&(*address as usize)) { - return true; - } - } - false - } -} - pub fn intel_pt_new_thread( emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, diff --git a/crates/libafl_qemu/src/qemu/config.rs b/crates/libafl_qemu/src/qemu/config.rs index 1e27f37dc42..27f6c6f190b 100644 --- a/crates/libafl_qemu/src/qemu/config.rs +++ b/crates/libafl_qemu/src/qemu/config.rs @@ -13,10 +13,28 @@ use typed_builder::TypedBuilder; #[derive(Debug, strum_macros::Display, Clone)] #[strum(prefix = "-accel ", serialize_all = "lowercase")] pub enum Accelerator { - Kvm, + #[strum(to_string = "kvm{0}")] + Kvm(KvmProperties), Tcg, } +#[cfg(feature = "systemmode")] +#[derive(Debug, Clone, Default, TypedBuilder)] +pub struct KvmProperties { + #[builder(default, setter(strip_option))] + dirty_ring_size: Option, +} + +#[cfg(feature = "systemmode")] +impl Display for KvmProperties { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(s) = self.dirty_ring_size { + write!(f, ",dirty-ring-size={s}")?; + } + Ok(()) + } +} + #[derive(Debug, strum_macros::Display, Clone)] #[strum(prefix = "if=", serialize_all = "lowercase")] pub enum DriveInterface { @@ -47,6 +65,21 @@ pub enum DriveCache { Unsafe, } +#[derive(Debug, Clone)] +pub struct DriveRawOptions(pub String); + +impl Display for DriveRawOptions { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl> From for DriveRawOptions { + fn from(r: R) -> Self { + Self(r.as_ref().to_string()) + } +} + #[derive(Debug, Clone, Default, TypedBuilder)] pub struct Drive { #[builder(default, setter(strip_option, into))] @@ -57,6 +90,8 @@ pub struct Drive { interface: Option, #[builder(default, setter(strip_option))] cache: Option, + #[builder(default, setter(strip_option, into))] + raw_options: Option, } impl Display for Drive { @@ -85,6 +120,9 @@ impl Display for Drive { if let Some(cache) = &self.cache { write!(f, "{}{cache}", separator())?; } + if let Some(raw_options) = &self.raw_options { + write!(f, "{}{raw_options}", separator())?; + } Ok(()) } @@ -506,6 +544,8 @@ pub struct QemuConfig { #[cfg(feature = "systemmode")] #[builder(default, setter(strip_option, into))] initrd: Option, + #[builder(default, setter(strip_option, into))] + raw_options: Option, #[cfg(feature = "usermode")] #[builder(setter(into))] program: Program, From ffb881bd87801445cd095dcfe4c0db78b3695a29 Mon Sep 17 00:00:00 2001 From: Marco Cavenati Date: Mon, 28 Jul 2025 15:02:09 +0200 Subject: [PATCH 2/5] fix intel_pt_command_executor example fuzzer --- .../intel_pt_command_executor/Cargo.lock | 25 ++++++++++++++++-- .../intel_pt_command_executor/Justfile | 3 +++ .../intel_pt_command_executor/src/main.rs | 26 ++++++++++++------- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock b/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock index e54852fbd66..1ec86172508 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock +++ b/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock @@ -299,6 +299,26 @@ version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dtor" version = "0.0.6" @@ -633,11 +653,12 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libipt" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975943706827c4d4f6b22771e71512af9b4cf7144680003600db11ada92f8383" +checksum = "4c3143c4dae9794d23fa2bbc6315847fdf3ef718caa09a7ba09238bce19fe9d4" dependencies = [ "bitflags", + "derive_more", "libipt-sys", "num_enum", ] diff --git a/fuzzers/binary_only/intel_pt_command_executor/Justfile b/fuzzers/binary_only/intel_pt_command_executor/Justfile index bd5f645410d..27b0291ef47 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Justfile +++ b/fuzzers/binary_only/intel_pt_command_executor/Justfile @@ -2,6 +2,9 @@ import "../../../just/libafl.just" FUZZER_NAME := "intel_pt_command_executor" +[unix] +default: run + [unix] target_dir: mkdir -p {{ TARGET_DIR }} diff --git a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs index c752db0bdba..8c9e5201b2c 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs +++ b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs @@ -1,5 +1,5 @@ use std::{ - env, ffi::CString, num::NonZero, os::unix::ffi::OsStrExt, path::PathBuf, slice, time::Duration, + env, ffi::CString, num::NonZero, os::unix::ffi::OsStrExt, path::PathBuf, time::Duration, }; use libafl::{ @@ -20,7 +20,7 @@ use libafl::{ state::StdState, }; use libafl_bolts::{core_affinity, rands::StdRand, tuples::tuple_list}; -use libafl_intelpt::{IntelPT, PAGE_SIZE}; +use libafl_intelpt::{AddrFilter, AddrFilterType, AddrFilters, IntelPT, PAGE_SIZE}; // Coverage map const MAP_SIZE: usize = 4096; @@ -90,25 +90,31 @@ pub fn main() { // The target is a ET_DYN elf, it will be relocated by the loader with this offset. // see https://github.com/torvalds/linux/blob/c1e939a21eb111a6d6067b38e8e04b8809b64c4e/arch/x86/include/asm/elf.h#L234C1-L239C38 - const DEFAULT_MAP_WINDOW: usize = (1 << 47) - PAGE_SIZE; - const ELF_ET_DYN_BASE: usize = (DEFAULT_MAP_WINDOW / 3 * 2) & !(PAGE_SIZE - 1); + const DEFAULT_MAP_WINDOW: u64 = (1 << 47) - PAGE_SIZE as u64; + const ELF_ET_DYN_BASE: u64 = (DEFAULT_MAP_WINDOW / 3 * 2) & !(PAGE_SIZE as u64 - 1); // Set the instruction pointer (IP) filter and memory image of our target. // These information can be retrieved from `readelf -l` (for example) - let code_memory_addresses = ELF_ET_DYN_BASE + 0x15000..=ELF_ET_DYN_BASE + 0x14000 + 0x41000; - + let (code_memory_start, code_memory_end) = + (ELF_ET_DYN_BASE + 0x6000, ELF_ET_DYN_BASE + 0x6000 + 0x3dfd9); + let filters = AddrFilters::new(&[AddrFilter::new( + code_memory_start, + code_memory_end, + AddrFilterType::FILTER, + )]) + .unwrap(); let intel_pt = IntelPT::builder() .cpu(cpu.0) .inherit(true) - .ip_filters(slice::from_ref(&code_memory_addresses)) + .ip_filters(&filters) .build() .unwrap(); let sections = [SectionInfo { filename: target_path.to_string_lossy().to_string(), - offset: 0x14000, - size: (*code_memory_addresses.end() - *code_memory_addresses.start() + 1) as u64, - virtual_address: *code_memory_addresses.start() as u64, + offset: 0x6000, + size: code_memory_end - code_memory_start, + virtual_address: code_memory_start, }]; let hook = unsafe { IntelPTHook::builder().map_ptr(MAP_PTR).map_len(MAP_SIZE) } From e1b25ec2fd1528f7494ef94e992e589ffccd61a5 Mon Sep 17 00:00:00 2001 From: Marco Cavenati Date: Tue, 29 Jul 2025 12:06:56 +0200 Subject: [PATCH 3/5] intel_pt_command_ex: get binary addrs from file Instead of hardcoding the addresses (that are compiler dependent), read them from the binary. --- .../intel_pt_command_executor/Cargo.lock | 48 +++++++++++++++- .../intel_pt_command_executor/Cargo.toml | 1 + .../intel_pt_command_executor/src/main.rs | 57 +++++++++++++------ 3 files changed, 87 insertions(+), 19 deletions(-) diff --git a/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock b/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock index 1ec86172508..1318b45d502 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock +++ b/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock @@ -110,7 +110,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.36.7", "rustc-demangle", "windows-targets 0.52.6", ] @@ -283,6 +283,15 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "ctor" version = "0.4.2" @@ -401,6 +410,16 @@ dependencies = [ "siphasher", ] +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fs2" version = "0.4.3" @@ -481,6 +500,7 @@ dependencies = [ "libafl_bolts", "libafl_intelpt", "log", + "object 0.37.1", ] [[package]] @@ -816,6 +836,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fd943161069e1768b4b3d050890ba48730e590f57e56d4aa04e7e090e61b4a" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1001,6 +1032,15 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "ruzstd" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640bec8aad418d7d03c72ea2de10d5c646a598f9883c7babc160d91e3c1b26c" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.20" @@ -1171,6 +1211,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "141fb9f71ee586d956d7d6e4d5a9ef8e946061188520140f7591b668841d502e" +[[package]] +name = "twox-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" + [[package]] name = "typed-builder" version = "0.21.0" diff --git a/fuzzers/binary_only/intel_pt_command_executor/Cargo.toml b/fuzzers/binary_only/intel_pt_command_executor/Cargo.toml index 69dd70719a8..a17865e9151 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Cargo.toml +++ b/fuzzers/binary_only/intel_pt_command_executor/Cargo.toml @@ -12,3 +12,4 @@ libafl = { path = "../../../crates/libafl", default-features = false, features = libafl_bolts = { path = "../../../crates/libafl_bolts" } libafl_intelpt = { path = "../../../crates/libafl_intelpt" } log = { version = "0.4.22", features = ["release_max_level_info"] } +object = "0.37.1" diff --git a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs index 8c9e5201b2c..9f59c13137e 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/src/main.rs +++ b/fuzzers/binary_only/intel_pt_command_executor/src/main.rs @@ -1,5 +1,5 @@ use std::{ - env, ffi::CString, num::NonZero, os::unix::ffi::OsStrExt, path::PathBuf, time::Duration, + env, ffi::CString, fs, num::NonZero, os::unix::ffi::OsStrExt, path::PathBuf, time::Duration, }; use libafl::{ @@ -19,8 +19,9 @@ use libafl::{ stages::mutational::StdMutationalStage, state::StdState, }; -use libafl_bolts::{core_affinity, rands::StdRand, tuples::tuple_list}; +use libafl_bolts::{core_affinity, rands::StdRand, tuples::tuple_list, Error}; use libafl_intelpt::{AddrFilter, AddrFilterType, AddrFilters, IntelPT, PAGE_SIZE}; +use object::{elf::PF_X, Object, ObjectSegment, SegmentFlags}; // Coverage map const MAP_SIZE: usize = 4096; @@ -29,7 +30,7 @@ static mut MAP: [u8; MAP_SIZE] = [0; MAP_SIZE]; #[allow(static_mut_refs)] // only a problem in nightly static mut MAP_PTR: *mut u8 = unsafe { MAP.as_mut_ptr() }; -pub fn main() { +pub fn main() -> Result<(), Box> { // Let's set the default logging level to `warn` if env::var("RUST_LOG").is_err() { env::set_var("RUST_LOG", "warn") @@ -44,9 +45,31 @@ pub fn main() { .parent() .unwrap() .join("target_program"); + let target_binary = fs::read(&target_path)?; + let target_parsed = object::File::parse(&*target_binary)?; + + // Get the executable segment from the target program. + // + // Note: this simple example target has just one range of executable memory, in real world binaries + // there are likely multiple ranges. + let executable_segments = target_parsed.segments().filter(|s| match s.flags() { + SegmentFlags::Elf { p_flags } => (p_flags & PF_X) > 0, + _ => panic!("target binary is not an ELF file."), + }); + let executable_segment = + executable_segments + .into_iter() + .next() + .ok_or(Error::illegal_argument( + "No executable segment found in target program", + ))?; + log::debug!( + "Executable segment: {executable_segment:x?} at binary file offset {:x?}", + executable_segment.file_range() + ); // We'll run the target on cpu (aka core) 0 - let cpu = core_affinity::get_core_ids().unwrap()[0]; + let cpu = core_affinity::get_core_ids()?[0]; log::debug!("Using core {} for fuzzing", cpu.0); // Create an observation channel using the map @@ -72,8 +95,7 @@ pub fn main() { &mut feedback, // Same for objective feedbacks &mut objective, - ) - .unwrap(); + )?; // The Monitor trait define how the fuzzer stats are displayed to the user let mon = SimpleMonitor::new(|s| println!("{s}")); @@ -94,27 +116,24 @@ pub fn main() { const ELF_ET_DYN_BASE: u64 = (DEFAULT_MAP_WINDOW / 3 * 2) & !(PAGE_SIZE as u64 - 1); // Set the instruction pointer (IP) filter and memory image of our target. - // These information can be retrieved from `readelf -l` (for example) - let (code_memory_start, code_memory_end) = - (ELF_ET_DYN_BASE + 0x6000, ELF_ET_DYN_BASE + 0x6000 + 0x3dfd9); + let actual_virtual_address = executable_segment.address() + ELF_ET_DYN_BASE; let filters = AddrFilters::new(&[AddrFilter::new( - code_memory_start, - code_memory_end, + actual_virtual_address, + actual_virtual_address + executable_segment.size(), AddrFilterType::FILTER, - )]) - .unwrap(); + )])?; + let intel_pt = IntelPT::builder() .cpu(cpu.0) .inherit(true) .ip_filters(&filters) - .build() - .unwrap(); + .build()?; let sections = [SectionInfo { filename: target_path.to_string_lossy().to_string(), - offset: 0x6000, - size: code_memory_end - code_memory_start, - virtual_address: code_memory_start, + offset: executable_segment.file_range().0, + size: executable_segment.size(), + virtual_address: actual_virtual_address, }]; let hook = unsafe { IntelPTHook::builder().map_ptr(MAP_PTR).map_len(MAP_SIZE) } @@ -158,4 +177,6 @@ pub fn main() { fuzzer .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) .expect("Error in the fuzzing loop"); + + Ok(()) } From 22a05f248b287bceb98bf537e7099e3bfa9d6973 Mon Sep 17 00:00:00 2001 From: Marco Cavenati Date: Thu, 7 Aug 2025 17:06:19 +0200 Subject: [PATCH 4/5] Ignore blocks in VMX root if exclude_hv is set --- crates/libafl_intelpt/src/decoder.rs | 91 ++++++++++++------- crates/libafl_intelpt/src/linux.rs | 4 +- .../src/modules/systemmode/intel_pt.rs | 2 +- .../intel_pt_command_executor/Cargo.lock | 5 +- 4 files changed, 62 insertions(+), 40 deletions(-) diff --git a/crates/libafl_intelpt/src/decoder.rs b/crates/libafl_intelpt/src/decoder.rs index f6c10847a6f..fdf21fdec1f 100644 --- a/crates/libafl_intelpt/src/decoder.rs +++ b/crates/libafl_intelpt/src/decoder.rs @@ -103,14 +103,7 @@ where match self.decoder.resync() { Ok(s) => { self.status = s; - if self.status.eos() { - return Ok(()); - } - - // If exclude_hv is set and we are in root VMX operation, continue resyncing - if self.exclude_hv || matches!(self.vmx_non_root, Some(true)) { - return Ok(()); - } + return Ok(()); } Err(e) => match e.code() { PtErrorCode::Eos => return Ok(()), @@ -149,39 +142,62 @@ where self.handle_event()?; } - // If exclude_hv is set and we are in root VMX operation, bail out - if self.exclude_hv && matches!(self.vmx_non_root, Some(false)) { - return Ok(()); + let offset = self.decoder.offset().map_err(error_from_pt_error)?; + if self.should_ignore_vmx_root() || offset <= self.trace_skip { + self.ignore_block()?; + } else { + self.decode_block()?; } - match self.decoder.decode_next() { - Ok((b, s)) => { - self.status = s; - let offset = self.decoder.offset().map_err(error_from_pt_error)?; - if b.ninsn() > 0 && self.trace_skip < offset { - let id = hash_64_fast(self.previous_block_end_ip) ^ hash_64_fast(b.ip()); - // SAFETY: the index is < map_len since the modulo operation is applied - unsafe { - let map_loc = self.map_ptr.add(id as usize % self.map_len); - *map_loc = (*map_loc).saturating_add(&1u8.into()); - } - self.previous_block_end_ip = b.end_ip(); - } + if self.status.eos() { + return Ok(()); + } + } + } - if self.status.eos() { - return Ok(()); + fn decode_block(&mut self) -> Result<(), Error> { + match self.decoder.decode_next() { + Ok((b, s)) => { + self.status = s; + if b.ninsn() > 0 { + let id = hash_64_fast(self.previous_block_end_ip) ^ hash_64_fast(b.ip()); + // SAFETY: the index is < map_len since the modulo operation is applied + unsafe { + let map_loc = self.map_ptr.add(id as usize % self.map_len); + *map_loc = (*map_loc).saturating_add(&1u8.into()); } + self.previous_block_end_ip = b.end_ip(); } - Err(e) => { - if e.code() != PtErrorCode::Eos { - let offset = self.decoder.offset().map_err(error_from_pt_error)?; - log::info!( - "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", - self.previous_block_end_ip - ); - } - return Err(error_from_pt_error(e)); + Ok(()) + } + Err(e) => { + if e.code() != PtErrorCode::Eos { + let offset = self.decoder.offset().map_err(error_from_pt_error)?; + log::info!( + "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", + self.previous_block_end_ip + ); + } + Err(error_from_pt_error(e)) + } + } + } + + fn ignore_block(&mut self) -> Result<(), Error> { + match self.decoder.decode_next() { + Ok((_, s)) => { + self.status = s; + Ok(()) + } + Err(e) => { + if e.code() != PtErrorCode::Eos { + let offset = self.decoder.offset().map_err(error_from_pt_error)?; + log::trace!( + "PT error in ignore block {e:?} trace offset {offset:x} last decoded block end {:x}", + self.previous_block_end_ip + ); } + Err(error_from_pt_error(e)) } } } @@ -200,4 +216,9 @@ where Err(e) => Err(Error::illegal_state(format!("PT error in event {e:?}"))), } } + + /// Returns true if `exclude_hv` is set and we are in root VMX operation + fn should_ignore_vmx_root(&self) -> bool { + self.exclude_hv && matches!(self.vmx_non_root, Some(false)) + } } diff --git a/crates/libafl_intelpt/src/linux.rs b/crates/libafl_intelpt/src/linux.rs index e00b1791166..26bb9d55fa0 100644 --- a/crates/libafl_intelpt/src/linux.rs +++ b/crates/libafl_intelpt/src/linux.rs @@ -365,7 +365,7 @@ impl Default for IntelPTBuilder { /// .pid(None) /// .all_cpus() /// .exclude_kernel(true) - /// .exclude_hv(true) + /// .exclude_hv(false) /// .inherit(false) /// .perf_buffer_size(128 * PAGE_SIZE + PAGE_SIZE) /// .unwrap() @@ -379,7 +379,7 @@ impl Default for IntelPTBuilder { pid: None, cpu: -1, exclude_kernel: true, - exclude_hv: true, + exclude_hv: false, inherit: false, perf_buffer_size: 128 * PAGE_SIZE + PAGE_SIZE, perf_aux_buffer_size: 16 * 1024 * 1024, diff --git a/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs b/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs index d40869c394c..e98191c14a4 100644 --- a/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs +++ b/crates/libafl_qemu/src/modules/systemmode/intel_pt.rs @@ -30,7 +30,7 @@ pub struct IntelPTModule { impl IntelPTModule { pub fn default_pt_builder() -> IntelPTBuilder { - IntelPT::builder().exclude_kernel(false) + IntelPT::builder().exclude_kernel(false).exclude_hv(true) } } diff --git a/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock b/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock index 1318b45d502..d27dc004d97 100644 --- a/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock +++ b/fuzzers/binary_only/intel_pt_command_executor/Cargo.lock @@ -402,11 +402,12 @@ dependencies = [ [[package]] name = "fastbloom" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ec576c163744bef8707859f6aeb322bcf56b8da61215d99f77d6e33160ff01" +checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" dependencies = [ "getrandom", + "libm", "siphasher", ] From 9e8bdbd1faa9869a0c2cd4f02aa2784b2f126fe6 Mon Sep 17 00:00:00 2001 From: Marco Cavenati Date: Thu, 14 Aug 2025 11:03:10 +0200 Subject: [PATCH 5/5] limit resync iteration if there is no progress --- crates/libafl_intelpt/src/decoder.rs | 82 ++++++++++++++++------------ crates/libafl_intelpt/src/linux.rs | 2 + 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/crates/libafl_intelpt/src/decoder.rs b/crates/libafl_intelpt/src/decoder.rs index fdf21fdec1f..85af24bf490 100644 --- a/crates/libafl_intelpt/src/decoder.rs +++ b/crates/libafl_intelpt/src/decoder.rs @@ -2,8 +2,12 @@ use core::fmt::Debug; use libafl_bolts::{Error, hash_64_fast}; use libipt::{ - block::BlockDecoder, enc_dec_builder::EncoderDecoderBuilder, error::PtErrorCode, - event::EventType, image::Image, status::Status, + block::BlockDecoder, + enc_dec_builder::EncoderDecoderBuilder, + error::{PtError, PtErrorCode}, + event::EventType, + image::Image, + status::Status, }; use num_traits::SaturatingAdd; @@ -81,21 +85,46 @@ where } fn decode_and_resync_loop(&mut self) -> Result<(), Error> { + const MAX_RESYNC_TRIALS: usize = 32; + let mut last_error_offset = 0; + let mut last_error_count = 0; + loop { match self.decode_blocks_loop() { - Ok(()) if self.status.eos() => return Ok(()), - Ok(()) | Err(_) => (), + Ok(()) => { + debug_assert!( + self.status.eos(), + "PT decoder decode_blocks_loop should return Ok only at the end of stream" + ); + return Ok(()); + } + Err(e) if e.code() == PtErrorCode::Eos => return Ok(()), + Err(_) => (), + } + + let offset = self.decoder.offset().map_err(error_from_pt_error)?; + if offset == last_error_offset { + last_error_count += 1; + if last_error_count > MAX_RESYNC_TRIALS { + return Err(Error::illegal_state(format!( + "PT Decoder got stuck at trace offset {offset:x}.\ + Make sure the decoder Image has the right content and offsets.\ + Trying to continue decoding.", + ))); + } + } else { + last_error_offset = offset; } match self.resync_loop() { Ok(()) if self.status.eos() => return Ok(()), Ok(()) => (), - Err(e) => return Err(e), + Err(e) => return Err(error_from_pt_error(e)), } } } - fn resync_loop(&mut self) -> Result<(), Error> + fn resync_loop(&mut self) -> Result<(), PtError> where T: SaturatingAdd + From + Debug, { @@ -108,41 +137,22 @@ where Err(e) => match e.code() { PtErrorCode::Eos => return Ok(()), PtErrorCode::EventIgnored => self.handle_event()?, - _ => return Err(Error::illegal_state(format!("PT error in resync {e:?}"))), + _ => return Err(e), }, } } } - fn decode_blocks_loop(&mut self) -> Result<(), Error> + fn decode_blocks_loop(&mut self) -> Result<(), PtError> where T: SaturatingAdd + From + Debug, { - #[cfg(debug_assertions)] - let mut trace_entry_iters: (u64, u64) = (0, 0); - loop { - #[cfg(debug_assertions)] - { - let offset = self.decoder.offset().map_err(error_from_pt_error)?; - if trace_entry_iters.0 == offset { - trace_entry_iters.1 += 1; - if trace_entry_iters.1 > 1000 { - return Err(Error::illegal_state(format!( - "PT Decoder got stuck at trace offset {offset:x}.\ - Make sure the decoder Image has the right content and offsets.", - ))); - } - } else { - trace_entry_iters = (offset, 0); - } - } - while self.status.event_pending() { self.handle_event()?; } - let offset = self.decoder.offset().map_err(error_from_pt_error)?; + let offset = self.decoder.offset()?; if self.should_ignore_vmx_root() || offset <= self.trace_skip { self.ignore_block()?; } else { @@ -155,7 +165,7 @@ where } } - fn decode_block(&mut self) -> Result<(), Error> { + fn decode_block(&mut self) -> Result<(), PtError> { match self.decoder.decode_next() { Ok((b, s)) => { self.status = s; @@ -172,18 +182,18 @@ where } Err(e) => { if e.code() != PtErrorCode::Eos { - let offset = self.decoder.offset().map_err(error_from_pt_error)?; + let offset = self.decoder.offset()?; log::info!( "PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}", self.previous_block_end_ip ); } - Err(error_from_pt_error(e)) + Err(e) } } } - fn ignore_block(&mut self) -> Result<(), Error> { + fn ignore_block(&mut self) -> Result<(), PtError> { match self.decoder.decode_next() { Ok((_, s)) => { self.status = s; @@ -191,18 +201,18 @@ where } Err(e) => { if e.code() != PtErrorCode::Eos { - let offset = self.decoder.offset().map_err(error_from_pt_error)?; + let offset = self.decoder.offset()?; log::trace!( "PT error in ignore block {e:?} trace offset {offset:x} last decoded block end {:x}", self.previous_block_end_ip ); } - Err(error_from_pt_error(e)) + Err(e) } } } - fn handle_event(&mut self) -> Result<(), Error> { + fn handle_event(&mut self) -> Result<(), PtError> { match self.decoder.event() { Ok((event, s)) => { self.status = s; @@ -213,7 +223,7 @@ where } Ok(()) } - Err(e) => Err(Error::illegal_state(format!("PT error in event {e:?}"))), + Err(e) => Err(e), } } diff --git a/crates/libafl_intelpt/src/linux.rs b/crates/libafl_intelpt/src/linux.rs index 26bb9d55fa0..228a5d42058 100644 --- a/crates/libafl_intelpt/src/linux.rs +++ b/crates/libafl_intelpt/src/linux.rs @@ -243,7 +243,9 @@ impl IntelPT { (ptr, None) } else { // Head pointer wrapped, the trace is split + log::trace!("Handling split trace"); let mut owned_data = self.join_split_trace(head_wrap, tail_wrap); + debug_assert_eq!(owned_data.len(), len); (owned_data.as_mut_ptr(), Some(owned_data)) } };