Bump Libipt 0.3, add HW breakpoint support (#2984)
* Decode with callback * WIP restore intelpt module * Fix build_target if target_dir doesn't exist * WIP itelpt qemu/kvm example: bootloader * qemu config refactoring * Fix intel_pt_command_executor target dir * * QEMU error refactoring* * back to one QEMU init function * other small things * update test * Bump libipt * waitpid_filtered to ignore SIGWINCH * Fix warnings unused manifest key: *.version * Add export_raw feature to libafl_intelpt * derive Debug for IntelPTHook * Clippy * Light refactor of EmulatorModules * qemu is now a parameter to EmulatorModule callbacks and most function hooks. * EmulatorModules is initialized before QEMU is initialized. * Update target program ELF offsets * fmt * * asan fixed size accesses working with generics * continue to propagate qemu argument as hook first parameter * use pre_syscall* and post_syscall* everywhere * fix some clippy stuff * fmt * Add comment to KVM pt_mode check * refactor * Add intel_pt_export_raw feature in libafl * fix fuzzers * * refactor asan and asanguest modules to avoid custom init of QEMU and use the module interface instead. * adapt qemu_launcher example to fully work with emulator, since qemu must now be initialized by emulator. * fmt * clippy * fix qemu_coverage * fmt * forgot qemu args in launcher * map_error instead of unwrap * use correct args * Update to new libafl_qemu * adapt api * borrow checker friendly join_split_trace and copy trace before deocde to prevent decoding failures * testing stuff * Set ip_filters (also) with builder * Move trace to file * Store a pt_builder in module enable the setting of filters and other pt settings * baby_bootloader target * Best bootloader ever * new builder? * use closure for qemu config from emulator builder. * better format * clippy + fmt * Fix build target Create target directory if doesn't exist * Remove filter on speculatively exec blocks since also committed blocks can have this flag * Add current ip_filters getter * Fix possibile infinite loop in trace decode * HW breakpoint + snapshot * add snapshot and exit at first objective * prefer raw pointers to slice_from_raw_parts_mut since the latter is highly unsafe and allows more potentially dangerous reordering * Add cpu option to QEMU config * Add cpu option and minor improvements * fix cargo run causing recompile * no default devices * windows clippy fix * Exclude intel_pt feature from CI as all systemmode feats * Add qemu_intel_pt_bootloader to CI * Fix NopPageFilter * Fix qemu_config * Restore HW breakpoints * Lints * return Result for hw bp set/remove * mark join_split_trace as unsafe * Put the qcow2 in a tmpfs ramdisk 10x exec/sec * Post merge fixes * Try out libipt 0.3 alpha * Try out libipt 0.3 alpha also in hook * Clippy * New libipt * Post merge fixes * Bump libipt * Drive cache None * Post merge fixes * Use SectionInfo from libipt * No slice::from_raw_parts_mut, just use raw pointer * Cache the decoder builder * Update qemu-bridge * Add qemu -append param * Move linux specific code to a mod, less #[cfg]s * Add qemu initrd config * Add qemu monitor tcp * Add not enough ip filters message * Fix wrong must_use * Prevent possible infinite loop in block decoding in debug mode * Clippy * fix CI? * Revert, keep libipt 0.3 and hw bp --------- Co-authored-by: Romain Malmain <romain.malmain@pm.me> Co-authored-by: Marco Cavenati <marco@lenovo300e>
This commit is contained in:
parent
530a3cc6aa
commit
47f7978b91
@ -97,7 +97,7 @@ document-features = "0.2.10"
|
||||
fastbloom = { version = "0.8.0", default-features = false }
|
||||
hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible
|
||||
libc = "0.2.159" # For (*nix) libc
|
||||
libipt = "0.2.0"
|
||||
libipt = "0.3.0"
|
||||
log = "0.4.22"
|
||||
meminterval = "0.4.1"
|
||||
mimalloc = { version = "0.1.43", default-features = false }
|
||||
|
@ -8,7 +8,7 @@ use libafl::{
|
||||
corpus::{InMemoryCorpus, OnDiskCorpus},
|
||||
events::SimpleEventManager,
|
||||
executors::{
|
||||
hooks::intel_pt::{IntelPTHook, Section},
|
||||
hooks::intel_pt::{IntelPTHook, SectionInfo},
|
||||
inprocess::GenericInProcessExecutor,
|
||||
ExitKind,
|
||||
},
|
||||
@ -100,10 +100,10 @@ pub fn main() {
|
||||
let sections = process_maps
|
||||
.iter()
|
||||
.filter_map(|pm| {
|
||||
if pm.is_exec() && pm.filename().is_some() {
|
||||
Some(Section {
|
||||
file_path: pm.filename().unwrap().to_string_lossy().to_string(),
|
||||
file_offset: pm.offset as u64,
|
||||
if pm.is_exec() && pm.filename().is_some() && pm.inode != 0 {
|
||||
Some(SectionInfo {
|
||||
filename: pm.filename().unwrap().to_string_lossy().to_string(),
|
||||
offset: pm.offset as u64,
|
||||
size: pm.size() as u64,
|
||||
virtual_address: pm.start() as u64,
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ use libafl::{
|
||||
events::SimpleEventManager,
|
||||
executors::{
|
||||
command::{CommandConfigurator, PTraceCommandConfigurator},
|
||||
hooks::intel_pt::{IntelPTHook, Section},
|
||||
hooks::intel_pt::{IntelPTHook, SectionInfo},
|
||||
},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
@ -105,9 +105,9 @@ pub fn main() {
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let sections = [Section {
|
||||
file_path: target_path.to_string_lossy().to_string(),
|
||||
file_offset: 0x14000,
|
||||
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,
|
||||
}];
|
||||
|
@ -111,13 +111,7 @@ regex = ["std", "dep:regex"]
|
||||
casr = ["libcasr", "std", "regex"]
|
||||
|
||||
## Intel Processor Trace
|
||||
intel_pt = [
|
||||
"std",
|
||||
"dep:libafl_intelpt",
|
||||
"dep:libipt",
|
||||
"dep:nix",
|
||||
"dep:num_enum",
|
||||
]
|
||||
intel_pt = ["std", "dep:libafl_intelpt", "dep:nix", "dep:num_enum"]
|
||||
## Save all the Intel PT raw traces to files, use only for debug
|
||||
intel_pt_export_raw = ["intel_pt", "libafl_intelpt/export_raw"]
|
||||
|
||||
@ -307,7 +301,6 @@ document-features = { workspace = true, optional = true }
|
||||
# Optional
|
||||
clap = { workspace = true, optional = true }
|
||||
num_enum = { workspace = true, optional = true }
|
||||
libipt = { workspace = true, optional = true }
|
||||
fastbloom = { workspace = true, optional = true }
|
||||
|
||||
[lints]
|
||||
|
@ -1,37 +1,24 @@
|
||||
use core::fmt::Debug;
|
||||
use std::{
|
||||
ptr::slice_from_raw_parts_mut,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
use libafl_intelpt::{error_from_pt_error, IntelPT};
|
||||
use libipt::{Asid, Image, SectionCache};
|
||||
pub use libafl_intelpt::SectionInfo;
|
||||
use libafl_intelpt::{Image, IntelPT};
|
||||
use num_traits::SaturatingAdd;
|
||||
use serde::Serialize;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use crate::{executors::hooks::ExecutorHook, Error};
|
||||
|
||||
/// Info of a binary's section that can be used during `Intel PT` traces decoding
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Section {
|
||||
/// Path of the binary
|
||||
pub file_path: String,
|
||||
/// Offset of the section in the file
|
||||
pub file_offset: u64,
|
||||
/// Size of the section
|
||||
pub size: u64,
|
||||
/// Start virtual address of the section once loaded in memory
|
||||
pub virtual_address: u64,
|
||||
}
|
||||
use crate::executors::hooks::ExecutorHook;
|
||||
|
||||
/// Hook to enable Intel Processor Trace (PT) tracing
|
||||
#[derive(Debug, TypedBuilder)]
|
||||
pub struct IntelPTHook<T> {
|
||||
#[builder(default = IntelPT::builder().build().unwrap())]
|
||||
intel_pt: IntelPT,
|
||||
#[builder(setter(transform = |sections: &[Section]| sections_to_image(sections).unwrap()))]
|
||||
image: (Image<'static>, SectionCache<'static>),
|
||||
#[builder(setter(transform = |sections: &[SectionInfo]| {
|
||||
let mut i = Image::new(None).unwrap();
|
||||
i.add_files_cached(sections, None).unwrap();
|
||||
i
|
||||
}))]
|
||||
image: Image,
|
||||
map_ptr: *mut T,
|
||||
map_len: usize,
|
||||
}
|
||||
@ -51,9 +38,8 @@ where
|
||||
let pt = &mut self.intel_pt;
|
||||
pt.disable_tracing().unwrap();
|
||||
|
||||
let slice = unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) };
|
||||
let _ = pt
|
||||
.decode_traces_into_map(&mut self.image.0, slice)
|
||||
.decode_traces_into_map(&mut self.image, self.map_ptr, self.map_len)
|
||||
.inspect_err(|e| log::warn!("Intel PT trace decoding failed: {e}"));
|
||||
#[cfg(feature = "intel_pt_export_raw")]
|
||||
{
|
||||
@ -63,35 +49,3 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It would be nice to have this as a `TryFrom<IntoIter<Section>>`, but Rust's orphan rule doesn't
|
||||
// like this (and `TryFromIter` is not a thing atm)
|
||||
fn sections_to_image(
|
||||
sections: &[Section],
|
||||
) -> Result<(Image<'static>, SectionCache<'static>), Error> {
|
||||
let mut image_cache = SectionCache::new(Some("image_cache")).map_err(error_from_pt_error)?;
|
||||
let mut image = Image::new(Some("image")).map_err(error_from_pt_error)?;
|
||||
|
||||
for s in sections {
|
||||
let isid = image_cache.add_file(&s.file_path, s.file_offset, s.size, s.virtual_address);
|
||||
if let Err(e) = isid {
|
||||
log::warn!(
|
||||
"Error while caching {} {} - skipped",
|
||||
s.file_path,
|
||||
e.to_string()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = image.add_cached(&mut image_cache, isid.unwrap(), Asid::default()) {
|
||||
log::warn!(
|
||||
"Error while adding cache to image {} {} - skipped",
|
||||
s.file_path,
|
||||
e.to_string()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((image, image_cache))
|
||||
}
|
||||
|
@ -12,707 +12,20 @@
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
use std::{
|
||||
borrow::ToOwned,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::{
|
||||
boxed::Box,
|
||||
ffi::{CStr, CString},
|
||||
fmt::Debug,
|
||||
format, fs,
|
||||
ops::RangeInclusive,
|
||||
os::{
|
||||
fd::{AsRawFd, FromRawFd, OwnedFd},
|
||||
raw::c_void,
|
||||
},
|
||||
path::Path,
|
||||
ptr, slice,
|
||||
sync::LazyLock,
|
||||
};
|
||||
use std::fs;
|
||||
use std::{borrow::ToOwned, string::String, vec::Vec};
|
||||
|
||||
use raw_cpuid::CpuId;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use arbitrary_int::u4;
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
use bitbybit::bitfield;
|
||||
#[cfg(target_os = "linux")]
|
||||
use caps::{CapSet, Capability};
|
||||
use libafl_bolts::Error;
|
||||
#[cfg(target_os = "linux")]
|
||||
use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut};
|
||||
use libipt::PtError;
|
||||
#[cfg(target_os = "linux")]
|
||||
use libipt::{
|
||||
block::BlockDecoder, AddrConfig, AddrFilter, AddrFilterBuilder, AddrRange, BlockFlags,
|
||||
ConfigBuilder, Cpu, Image, PtErrorCode, Status,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
use num_enum::TryFromPrimitive;
|
||||
#[cfg(target_os = "linux")]
|
||||
use num_traits::{Euclid, SaturatingAdd};
|
||||
#[cfg(target_os = "linux")]
|
||||
use perf_event_open_sys::{
|
||||
bindings::{perf_event_attr, perf_event_mmap_page, PERF_FLAG_FD_CLOEXEC},
|
||||
ioctls::{DISABLE, ENABLE, SET_FILTER},
|
||||
perf_event_open,
|
||||
};
|
||||
use raw_cpuid::CpuId;
|
||||
pub use linux::*;
|
||||
|
||||
/// Size of a memory page
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const PT_EVENT_PATH: &str = "/sys/bus/event_source/devices/intel_pt";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
static NR_ADDR_FILTERS: LazyLock<Result<u32, String>> = LazyLock::new(|| {
|
||||
// This info is available in two different files, use the second path as fail-over
|
||||
let path = format!("{PT_EVENT_PATH}/nr_addr_filters");
|
||||
let path2 = format!("{PT_EVENT_PATH}/caps/num_address_ranges");
|
||||
let err = format!("Failed to read Intel PT number of address filters from {path} and {path2}");
|
||||
|
||||
let s = fs::read_to_string(&path);
|
||||
if let Ok(s) = s {
|
||||
let n = s.trim().parse::<u32>();
|
||||
if let Ok(n) = n {
|
||||
return Ok(n);
|
||||
}
|
||||
}
|
||||
|
||||
let s2 = fs::read_to_string(&path2).map_err(|_| err.clone())?;
|
||||
s2.trim().parse::<u32>().map_err(|_| err)
|
||||
});
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
static CURRENT_CPU: LazyLock<Option<Cpu>> = LazyLock::new(|| {
|
||||
let cpuid = CpuId::new();
|
||||
cpuid
|
||||
.get_feature_info()
|
||||
.map(|fi| Cpu::intel(fi.family_id().into(), fi.model_id(), fi.stepping_id()))
|
||||
});
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
static PERF_EVENT_TYPE: LazyLock<Result<u32, String>> = LazyLock::new(|| {
|
||||
let path = format!("{PT_EVENT_PATH}/type");
|
||||
let s = fs::read_to_string(&path)
|
||||
.map_err(|_| format!("Failed to read Intel PT perf event type from {path}"))?;
|
||||
s.trim()
|
||||
.parse::<u32>()
|
||||
.map_err(|_| format!("Failed to parse Intel PT perf event type in {path}"))
|
||||
});
|
||||
|
||||
/// Intel PT mode of operation with KVM
|
||||
///
|
||||
/// Check out <https://github.com/torvalds/linux/blob/c2ee9f594da826bea183ed14f2cc029c719bf4da/arch/x86/kvm/vmx/capabilities.h#L373-L381>
|
||||
/// for more details
|
||||
#[cfg(target_os = "linux")]
|
||||
#[derive(TryFromPrimitive, Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum KvmPTMode {
|
||||
/// trace both host/guest and output to host buffer
|
||||
System = 0,
|
||||
/// trace host and guest simultaneously and output to their respective buffer
|
||||
HostGuest = 1,
|
||||
}
|
||||
|
||||
/// Intel Processor Trace (PT)
|
||||
#[cfg(target_os = "linux")]
|
||||
#[derive(Debug)]
|
||||
pub struct IntelPT {
|
||||
fd: OwnedFd,
|
||||
perf_buffer: *mut c_void,
|
||||
perf_aux_buffer: *mut c_void,
|
||||
perf_buffer_size: usize,
|
||||
perf_aux_buffer_size: usize,
|
||||
aux_head: *mut u64,
|
||||
aux_tail: *mut u64,
|
||||
previous_decode_head: u64,
|
||||
ip_filters: Vec<RangeInclusive<usize>>,
|
||||
#[cfg(feature = "export_raw")]
|
||||
last_decode_trace: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl IntelPT {
|
||||
/// Create a default builder
|
||||
///
|
||||
/// Checkout [`IntelPTBuilder::default()`] for more details
|
||||
#[must_use]
|
||||
pub fn builder() -> IntelPTBuilder {
|
||||
IntelPTBuilder::default()
|
||||
}
|
||||
|
||||
/// Set filters based on Instruction Pointer (IP)
|
||||
///
|
||||
/// Only instructions in `filters` ranges will be traced.
|
||||
pub fn set_ip_filters(&mut self, filters: &[RangeInclusive<usize>]) -> Result<(), Error> {
|
||||
let str_filter = filters
|
||||
.iter()
|
||||
.map(|filter| {
|
||||
let size = filter.end() - filter.start();
|
||||
format!("filter {:#016x}/{:#016x} ", filter.start(), size)
|
||||
})
|
||||
.reduce(|acc, s| acc + &s)
|
||||
.unwrap_or_default();
|
||||
|
||||
// SAFETY: CString::from_vec_unchecked is safe because no null bytes are added to str_filter
|
||||
let c_str_filter = unsafe { CString::from_vec_unchecked(str_filter.into_bytes()) };
|
||||
match unsafe { SET_FILTER(self.fd.as_raw_fd(), c_str_filter.into_raw()) } {
|
||||
-1 => {
|
||||
let availability = match availability() {
|
||||
Ok(()) => String::new(),
|
||||
Err(reasons) => format!(" Possible reasons: {reasons}"),
|
||||
};
|
||||
Err(Error::last_os_error(format!(
|
||||
"Failed to set IP filters.{availability}"
|
||||
)))
|
||||
}
|
||||
0 => {
|
||||
self.ip_filters = filters.to_vec();
|
||||
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<RangeInclusive<usize>> {
|
||||
self.ip_filters.clone()
|
||||
}
|
||||
|
||||
fn ip_filters_to_addr_filter(&self) -> AddrFilter {
|
||||
let mut builder = AddrFilterBuilder::new();
|
||||
let mut iter = self
|
||||
.ip_filters
|
||||
.iter()
|
||||
.map(|f| AddrRange::new(*f.start() as u64, *f.end() as u64, AddrConfig::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.finish()
|
||||
}
|
||||
|
||||
/// Start tracing
|
||||
///
|
||||
/// Be aware that the tracing is not started on [`IntelPT`] construction.
|
||||
pub fn enable_tracing(&mut self) -> Result<(), Error> {
|
||||
match unsafe { ENABLE(self.fd.as_raw_fd(), 0) } {
|
||||
-1 => {
|
||||
let availability = match availability() {
|
||||
Ok(()) => String::new(),
|
||||
Err(reasons) => format!(" Possible reasons: {reasons}"),
|
||||
};
|
||||
Err(Error::last_os_error(format!(
|
||||
"Failed to enable tracing.{availability}"
|
||||
)))
|
||||
}
|
||||
0 => Ok(()),
|
||||
ret => Err(Error::unsupported(format!(
|
||||
"Failed to enable tracing, ioctl returned unexpected value {ret}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop tracing
|
||||
///
|
||||
/// This doesn't drop [`IntelPT`], the configuration will be preserved.
|
||||
pub fn disable_tracing(&mut self) -> Result<(), Error> {
|
||||
match unsafe { DISABLE(self.fd.as_raw_fd(), 0) } {
|
||||
-1 => Err(Error::last_os_error("Failed to disable tracing")),
|
||||
0 => Ok(()),
|
||||
ret => Err(Error::unsupported(format!(
|
||||
"Failed to disable tracing, ioctl returned unexpected value {ret}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
// // 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 {
|
||||
// // ptr::copy_nonoverlapping(src, dst, size);
|
||||
// // }
|
||||
// // };
|
||||
// #[allow(clippy::cast_possible_wrap)]
|
||||
// fn decode_with_callback<F: Fn(&mut [u8], u64)>(
|
||||
// &mut self,
|
||||
// read_memory: F,
|
||||
// copy_buffer: Option<&mut Vec<u8>>,
|
||||
// ) -> Result<Vec<u64>, Error> {
|
||||
// self.decode(
|
||||
// Some(|buff: &mut [u8], addr: u64, _: Asid| {
|
||||
// debug_assert!(i32::try_from(buff.len()).is_ok());
|
||||
// read_memory(buff, addr);
|
||||
// buff.len() as i32
|
||||
// }),
|
||||
// None,
|
||||
// copy_buffer,
|
||||
// )
|
||||
// }
|
||||
|
||||
/// Fill the coverage map by decoding the PT traces
|
||||
///
|
||||
/// This function consumes the traces.
|
||||
pub fn decode_traces_into_map<T>(
|
||||
&mut self,
|
||||
image: &mut Image,
|
||||
map: &mut [T],
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: SaturatingAdd + From<u8> + Debug,
|
||||
{
|
||||
let head = unsafe { self.aux_head.read_volatile() };
|
||||
let tail = unsafe { self.aux_tail.read_volatile() };
|
||||
if head < tail {
|
||||
return Err(Error::unknown(
|
||||
"Intel PT: aux buffer head is behind aux tail.",
|
||||
));
|
||||
}
|
||||
if self.previous_decode_head < tail {
|
||||
return Err(Error::unknown(
|
||||
"Intel PT: aux previous head is behind aux tail.",
|
||||
));
|
||||
}
|
||||
let len = (head - tail) as usize;
|
||||
if len >= self.perf_aux_buffer_size {
|
||||
log::warn!(
|
||||
"The fuzzer run filled the entire PT buffer. Consider increasing the aux buffer \
|
||||
size or refining the IP filters."
|
||||
);
|
||||
}
|
||||
let skip = self.previous_decode_head - tail;
|
||||
|
||||
let head_wrap = wrap_aux_pointer(head, self.perf_aux_buffer_size);
|
||||
let tail_wrap = wrap_aux_pointer(tail, self.perf_aux_buffer_size);
|
||||
|
||||
// after reading the data_head value, user space should issue an rmb()
|
||||
// https://manpages.debian.org/bookworm/manpages-dev/perf_event_open.2.en.html#data_head
|
||||
smp_rmb();
|
||||
|
||||
let mut data = if head_wrap >= tail_wrap {
|
||||
unsafe {
|
||||
let ptr = self.perf_aux_buffer.add(tail_wrap as usize) as *mut u8;
|
||||
OwnedRefMut::Ref(slice::from_raw_parts_mut(ptr, len))
|
||||
}
|
||||
} else {
|
||||
// Head pointer wrapped, the trace is split
|
||||
OwnedRefMut::Owned(self.join_split_trace(head_wrap, tail_wrap))
|
||||
};
|
||||
#[cfg(feature = "export_raw")]
|
||||
{
|
||||
self.last_decode_trace = data.as_ref().to_vec();
|
||||
}
|
||||
|
||||
let mut config = ConfigBuilder::new(data.as_mut()).map_err(error_from_pt_error)?;
|
||||
config.filter(self.ip_filters_to_addr_filter());
|
||||
if let Some(cpu) = &*CURRENT_CPU {
|
||||
config.cpu(*cpu);
|
||||
}
|
||||
let flags = BlockFlags::END_ON_CALL.union(BlockFlags::END_ON_JUMP);
|
||||
config.flags(flags);
|
||||
let mut decoder = BlockDecoder::new(&config.finish()).map_err(error_from_pt_error)?;
|
||||
decoder
|
||||
.set_image(Some(image))
|
||||
.map_err(error_from_pt_error)?;
|
||||
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.code() != PtErrorCode::Eos {
|
||||
log::trace!("PT error in sync forward {e:?}");
|
||||
}
|
||||
break 'sync;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn join_split_trace(&self, head_wrap: u64, tail_wrap: u64) -> Box<[u8]> {
|
||||
let first_ptr = unsafe { self.perf_aux_buffer.add(tail_wrap as usize) as *mut u8 };
|
||||
let first_len = self.perf_aux_buffer_size - tail_wrap as usize;
|
||||
|
||||
let second_ptr = self.perf_aux_buffer as *mut u8;
|
||||
let second_len = head_wrap as usize;
|
||||
|
||||
let mut vec = Vec::with_capacity(first_len + second_len);
|
||||
vec.extend_from_slice(unsafe { slice::from_raw_parts(first_ptr, first_len) });
|
||||
vec.extend_from_slice(unsafe { slice::from_raw_parts(second_ptr, second_len) });
|
||||
vec.into_boxed_slice()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn decode_blocks<T>(
|
||||
decoder: &mut BlockDecoder<()>,
|
||||
status: &mut Status,
|
||||
previous_block_end_ip: &mut u64,
|
||||
skip: u64,
|
||||
map: &mut [T],
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: SaturatingAdd + From<u8> + Debug,
|
||||
{
|
||||
'block: loop {
|
||||
while status.event_pending() {
|
||||
match decoder.event() {
|
||||
Ok((_, s)) => {
|
||||
*status = s;
|
||||
}
|
||||
Err(e) => {
|
||||
log::trace!("PT error in event {e:?}");
|
||||
break 'block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match decoder.next() {
|
||||
Ok((b, s)) => {
|
||||
*status = s;
|
||||
let offset = decoder.offset().map_err(error_from_pt_error)?;
|
||||
|
||||
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
|
||||
let map_loc = unsafe { map.get_unchecked_mut(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 {
|
||||
log::trace!("PT error in block next {e:?}");
|
||||
}
|
||||
break 'block;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the raw trace used in the last decoding
|
||||
#[cfg(feature = "export_raw")]
|
||||
#[must_use]
|
||||
pub fn last_decode_trace(&self) -> Vec<u8> {
|
||||
self.last_decode_trace.clone()
|
||||
}
|
||||
|
||||
/// Dump the raw trace used in the last decoding to the file
|
||||
/// /// `./traces/trace_<unix epoch in micros>`
|
||||
#[cfg(feature = "export_raw")]
|
||||
pub fn dump_last_trace_to_file(&self) -> Result<(), Error> {
|
||||
use std::{fs, io::Write, path::Path, time};
|
||||
|
||||
let traces_dir = Path::new("traces");
|
||||
fs::create_dir_all(traces_dir)?;
|
||||
let timestamp = time::SystemTime::now()
|
||||
.duration_since(time::UNIX_EPOCH)
|
||||
.map_err(|e| Error::unknown(e.to_string()))?
|
||||
.as_micros();
|
||||
let mut file = fs::File::create(traces_dir.join(format!("trace_{timestamp}")))?;
|
||||
file.write_all(&self.last_decode_trace())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl Drop for IntelPT {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let ret = libc::munmap(self.perf_aux_buffer, self.perf_aux_buffer_size);
|
||||
assert_eq!(ret, 0, "Intel PT: Failed to unmap perf aux buffer");
|
||||
let ret = libc::munmap(self.perf_buffer, self.perf_buffer_size);
|
||||
assert_eq!(ret, 0, "Intel PT: Failed to unmap perf buffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`IntelPT`]
|
||||
#[cfg(target_os = "linux")]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct IntelPTBuilder {
|
||||
pid: Option<i32>,
|
||||
cpu: i32,
|
||||
exclude_kernel: bool,
|
||||
exclude_hv: bool,
|
||||
inherit: bool,
|
||||
perf_buffer_size: usize,
|
||||
perf_aux_buffer_size: usize,
|
||||
ip_filters: Vec<RangeInclusive<usize>>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl Default for IntelPTBuilder {
|
||||
/// Create a default builder for [`IntelPT`]
|
||||
///
|
||||
/// The default configuration corresponds to:
|
||||
/// ```rust
|
||||
/// use libafl_intelpt::{IntelPTBuilder, PAGE_SIZE};
|
||||
/// let builder = IntelPTBuilder::default()
|
||||
/// .pid(None)
|
||||
/// .all_cpus()
|
||||
/// .exclude_kernel(true)
|
||||
/// .exclude_hv(true)
|
||||
/// .inherit(false)
|
||||
/// .perf_buffer_size(128 * PAGE_SIZE + PAGE_SIZE).unwrap()
|
||||
/// .perf_aux_buffer_size(2 * 1024 * 1024).unwrap()
|
||||
/// .ip_filters(&[]);
|
||||
/// assert_eq!(builder, IntelPTBuilder::default());
|
||||
/// ```
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pid: None,
|
||||
cpu: -1,
|
||||
exclude_kernel: true,
|
||||
exclude_hv: true,
|
||||
inherit: false,
|
||||
perf_buffer_size: 128 * PAGE_SIZE + PAGE_SIZE,
|
||||
perf_aux_buffer_size: 2 * 1024 * 1024,
|
||||
ip_filters: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl IntelPTBuilder {
|
||||
/// Build the [`IntelPT`] struct
|
||||
pub fn build(&self) -> Result<IntelPT, Error> {
|
||||
self.check_config();
|
||||
let mut perf_event_attr = new_perf_event_attr_intel_pt()?;
|
||||
perf_event_attr.set_exclude_kernel(self.exclude_kernel.into());
|
||||
perf_event_attr.set_exclude_hv(self.exclude_hv.into());
|
||||
perf_event_attr.set_inherit(self.inherit.into());
|
||||
|
||||
// SAFETY: perf_event_attr is properly initialized
|
||||
let fd = match unsafe {
|
||||
perf_event_open(
|
||||
ptr::from_mut(&mut perf_event_attr),
|
||||
self.pid.unwrap_or(0),
|
||||
self.cpu,
|
||||
-1,
|
||||
PERF_FLAG_FD_CLOEXEC.into(),
|
||||
)
|
||||
} {
|
||||
-1 => {
|
||||
let availability = match availability() {
|
||||
Ok(()) => String::new(),
|
||||
Err(reasons) => format!(" Possible reasons: {reasons}"),
|
||||
};
|
||||
return Err(Error::last_os_error(format!(
|
||||
"Failed to open Intel PT perf event.{availability}"
|
||||
)));
|
||||
}
|
||||
fd => {
|
||||
// SAFETY: On success, perf_event_open() returns a new file descriptor.
|
||||
// On error, -1 is returned, and it is checked above
|
||||
unsafe { OwnedFd::from_raw_fd(fd) }
|
||||
}
|
||||
};
|
||||
|
||||
let perf_buffer = setup_perf_buffer(&fd, self.perf_buffer_size)?;
|
||||
|
||||
// the first perf_buff page is a metadata page
|
||||
let buff_metadata = perf_buffer.cast::<perf_event_mmap_page>();
|
||||
let aux_offset = unsafe { &raw mut (*buff_metadata).aux_offset };
|
||||
let aux_size = unsafe { &raw mut (*buff_metadata).aux_size };
|
||||
let data_offset = unsafe { &raw mut (*buff_metadata).data_offset };
|
||||
let data_size = unsafe { &raw mut (*buff_metadata).data_size };
|
||||
|
||||
unsafe {
|
||||
aux_offset.write_volatile(next_page_aligned_addr(
|
||||
data_offset.read_volatile() + data_size.read_volatile(),
|
||||
));
|
||||
aux_size.write_volatile(self.perf_aux_buffer_size as u64);
|
||||
}
|
||||
|
||||
let perf_aux_buffer = unsafe {
|
||||
setup_perf_aux_buffer(&fd, aux_size.read_volatile(), aux_offset.read_volatile())?
|
||||
};
|
||||
|
||||
let aux_head = unsafe { &raw mut (*buff_metadata).aux_head };
|
||||
let aux_tail = unsafe { &raw mut (*buff_metadata).aux_tail };
|
||||
|
||||
let mut intel_pt = IntelPT {
|
||||
fd,
|
||||
perf_buffer,
|
||||
perf_aux_buffer,
|
||||
perf_buffer_size: self.perf_buffer_size,
|
||||
perf_aux_buffer_size: self.perf_aux_buffer_size,
|
||||
aux_head,
|
||||
aux_tail,
|
||||
previous_decode_head: 0,
|
||||
ip_filters: Vec::with_capacity(*NR_ADDR_FILTERS.as_ref().unwrap_or(&0) as usize),
|
||||
#[cfg(feature = "export_raw")]
|
||||
last_decode_trace: Vec::new(),
|
||||
};
|
||||
if !self.ip_filters.is_empty() {
|
||||
intel_pt.set_ip_filters(&self.ip_filters)?;
|
||||
}
|
||||
Ok(intel_pt)
|
||||
}
|
||||
|
||||
/// Warn if the configuration is not recommended
|
||||
#[inline]
|
||||
fn check_config(&self) {
|
||||
if self.inherit && self.cpu == -1 {
|
||||
log::warn!(
|
||||
"IntelPT set up on all CPUs with process inheritance enabled. This configuration \
|
||||
is not recommended and might not work as expected"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Set the process to be traced via its `PID`. Set to `None` to trace the current process.
|
||||
pub fn pid(mut self, pid: Option<i32>) -> Self {
|
||||
self.pid = pid;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Set the CPU to be traced
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The function will panic if `cpu` is greater than `i32::MAX`
|
||||
pub fn cpu(mut self, cpu: usize) -> Self {
|
||||
self.cpu = cpu.try_into().unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Trace all the CPUs
|
||||
pub fn all_cpus(mut self) -> Self {
|
||||
self.cpu = -1;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Do not trace kernel code
|
||||
pub fn exclude_kernel(mut self, exclude_kernel: bool) -> Self {
|
||||
self.exclude_kernel = exclude_kernel;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Do not trace Hypervisor code
|
||||
pub fn exclude_hv(mut self, exclude_hv: bool) -> Self {
|
||||
self.exclude_hv = exclude_hv;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Child processes are traced
|
||||
pub fn inherit(mut self, inherit: bool) -> Self {
|
||||
self.inherit = inherit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the size of the perf buffer
|
||||
pub fn perf_buffer_size(mut self, perf_buffer_size: usize) -> Result<Self, Error> {
|
||||
let err = Err(Error::illegal_argument(
|
||||
"IntelPT perf_buffer_size should be 1+2^n pages",
|
||||
));
|
||||
if perf_buffer_size < PAGE_SIZE {
|
||||
return err;
|
||||
}
|
||||
let (q, r) = (perf_buffer_size - PAGE_SIZE).div_rem_euclid(&PAGE_SIZE);
|
||||
if !q.is_power_of_two() || r != 0 {
|
||||
return err;
|
||||
}
|
||||
|
||||
self.perf_buffer_size = perf_buffer_size;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Set the size of the perf aux buffer (actual PT traces buffer)
|
||||
pub fn perf_aux_buffer_size(mut self, perf_aux_buffer_size: usize) -> Result<Self, Error> {
|
||||
// todo:replace with is_multiple_of once stable
|
||||
if perf_aux_buffer_size % PAGE_SIZE != 0 {
|
||||
return Err(Error::illegal_argument(
|
||||
"IntelPT perf_aux_buffer must be page aligned",
|
||||
));
|
||||
}
|
||||
if !perf_aux_buffer_size.is_power_of_two() {
|
||||
return Err(Error::illegal_argument(
|
||||
"IntelPT perf_aux_buffer must be a power of two",
|
||||
));
|
||||
}
|
||||
|
||||
self.perf_aux_buffer_size = perf_aux_buffer_size;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[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<usize>]) -> Self {
|
||||
self.ip_filters = filters.to_vec();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Perf event config for `IntelPT`
|
||||
///
|
||||
/// (This is almost mapped to `IA32_RTIT_CTL MSR` by perf)
|
||||
#[cfg(target_os = "linux")]
|
||||
#[bitfield(u64, default = 0)]
|
||||
struct PtConfig {
|
||||
/// Disable call return address compression. AKA DisRETC in Intel SDM.
|
||||
#[bit(11, rw)]
|
||||
noretcomp: bool,
|
||||
/// Indicates the frequency of PSB packets. AKA PSBFreq in Intel SDM.
|
||||
#[bits(24..=27, rw)]
|
||||
psb_period: u4,
|
||||
}
|
||||
|
||||
/// Number of address filters available on the running CPU
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn nr_addr_filters() -> Result<u32, String> {
|
||||
NR_ADDR_FILTERS.clone()
|
||||
}
|
||||
|
||||
/// Check if Intel PT is available on the current system.
|
||||
///
|
||||
/// Returns `Ok(())` if Intel PT is available and has the features used by `LibAFL`, otherwise
|
||||
@ -797,190 +110,8 @@ pub fn availability_in_qemu_kvm() -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert [`PtError`] into [`Error`]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn error_from_pt_error(err: PtError) -> Error {
|
||||
Error::unknown(err.to_string())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn availability_in_linux() -> Result<(), String> {
|
||||
let mut reasons = Vec::new();
|
||||
match linux_version() {
|
||||
// https://docs.rs/perf-event-open-sys/4.0.0/perf_event_open_sys/#kernel-versions
|
||||
Ok(ver) if ver >= (5, 19, 4) => {}
|
||||
Ok((major, minor, patch)) => reasons.push(format!(
|
||||
"Kernel version {major}.{minor}.{patch} is older than 5.19.4 and might not work."
|
||||
)),
|
||||
Err(()) => reasons.push("Failed to retrieve kernel version".to_owned()),
|
||||
}
|
||||
|
||||
if let Err(e) = &*PERF_EVENT_TYPE {
|
||||
reasons.push(e.clone());
|
||||
}
|
||||
|
||||
if let Err(e) = &*NR_ADDR_FILTERS {
|
||||
reasons.push(e.clone());
|
||||
}
|
||||
|
||||
// official way of knowing if perf_event_open() support is enabled
|
||||
// https://man7.org/linux/man-pages/man2/perf_event_open.2.html
|
||||
let perf_event_support_path = "/proc/sys/kernel/perf_event_paranoid";
|
||||
if !Path::new(perf_event_support_path).exists() {
|
||||
reasons.push(format!(
|
||||
"perf_event_open() support is not enabled: {perf_event_support_path} not found"
|
||||
));
|
||||
}
|
||||
|
||||
// TODO check also the value of perf_event_paranoid, check which values are required by pt
|
||||
// https://www.kernel.org/doc/Documentation/sysctl/kernel.txt
|
||||
// also, looks like it is distribution dependent
|
||||
// https://askubuntu.com/questions/1400874/what-does-perf-paranoia-level-four-do
|
||||
// CAP_SYS_ADMIN might make this check useless
|
||||
|
||||
match caps::read(None, CapSet::Permitted) {
|
||||
Ok(current_capabilities) => {
|
||||
let required_caps = [
|
||||
Capability::CAP_IPC_LOCK,
|
||||
Capability::CAP_SYS_PTRACE,
|
||||
Capability::CAP_SYS_ADMIN, // TODO: CAP_PERFMON doesn't look to be enough!?
|
||||
Capability::CAP_SYSLOG,
|
||||
];
|
||||
|
||||
for rc in required_caps {
|
||||
if !current_capabilities.contains(&rc) {
|
||||
reasons.push(format!("Required capability {rc} missing"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => reasons.push(format!("Failed to read linux capabilities: {e}")),
|
||||
}
|
||||
|
||||
if reasons.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(reasons.join("; "))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn new_perf_event_attr_intel_pt() -> Result<perf_event_attr, Error> {
|
||||
let type_ = match &*PERF_EVENT_TYPE {
|
||||
Ok(t) => Ok(*t),
|
||||
Err(e) => Err(Error::unsupported(e.clone())),
|
||||
}?;
|
||||
let config = PtConfig::builder()
|
||||
.with_noretcomp(true)
|
||||
.with_psb_period(u4::new(0))
|
||||
.build()
|
||||
.raw_value;
|
||||
|
||||
let mut attr = perf_event_attr {
|
||||
size: size_of::<perf_event_attr>() as u32,
|
||||
type_,
|
||||
config,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Do not enable tracing as soon as the perf_event_open syscall is issued
|
||||
attr.set_disabled(true.into());
|
||||
|
||||
Ok(attr)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn setup_perf_buffer(fd: &OwnedFd, perf_buffer_size: usize) -> Result<*mut c_void, Error> {
|
||||
match unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
perf_buffer_size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_SHARED,
|
||||
fd.as_raw_fd(),
|
||||
0,
|
||||
)
|
||||
} {
|
||||
libc::MAP_FAILED => Err(Error::last_os_error("IntelPT: Failed to mmap perf buffer")),
|
||||
mmap_addr => Ok(mmap_addr),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn setup_perf_aux_buffer(fd: &OwnedFd, size: u64, offset: u64) -> Result<*mut c_void, Error> {
|
||||
match unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
size as usize,
|
||||
// PROT_WRITE sets PT to stop when the buffer is full
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_SHARED,
|
||||
fd.as_raw_fd(),
|
||||
i64::try_from(offset)?,
|
||||
)
|
||||
} {
|
||||
libc::MAP_FAILED => Err(Error::last_os_error(
|
||||
"IntelPT: Failed to mmap perf aux buffer",
|
||||
)),
|
||||
mmap_addr => Ok(mmap_addr),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn linux_version() -> Result<(usize, usize, usize), ()> {
|
||||
let mut uname_data = libc::utsname {
|
||||
sysname: [0; 65],
|
||||
nodename: [0; 65],
|
||||
release: [0; 65],
|
||||
version: [0; 65],
|
||||
machine: [0; 65],
|
||||
domainname: [0; 65],
|
||||
};
|
||||
|
||||
if unsafe { libc::uname(&mut uname_data) } != 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let release = unsafe { CStr::from_ptr(uname_data.release.as_ptr()) };
|
||||
let mut parts = release
|
||||
.to_bytes()
|
||||
.split(|&c| c == b'.' || c == b'-')
|
||||
.take(3)
|
||||
.map(|s| String::from_utf8_lossy(s).parse::<usize>());
|
||||
if let (Some(Ok(major)), Some(Ok(minor)), Some(Ok(patch))) =
|
||||
(parts.next(), parts.next(), parts.next())
|
||||
{
|
||||
Ok((major, minor, patch))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
const fn next_page_aligned_addr(address: u64) -> u64 {
|
||||
(address + PAGE_SIZE as u64 - 1) & !(PAGE_SIZE as u64 - 1)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
fn smp_rmb() {
|
||||
// SAFETY: just a memory barrier
|
||||
unsafe {
|
||||
core::arch::asm!("lfence", options(nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
const fn wrap_aux_pointer(ptr: u64, perf_aux_buffer_size: usize) -> u64 {
|
||||
ptr & (perf_aux_buffer_size as u64 - 1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(target_os = "linux")]
|
||||
use arbitrary_int::Number;
|
||||
use static_assertions::assert_eq_size;
|
||||
|
||||
use super::*;
|
||||
@ -1005,65 +136,4 @@ mod test {
|
||||
Err(e) => println!("❌\tReasons: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn intel_pt_builder_default_values_are_valid() {
|
||||
let default = IntelPT::builder();
|
||||
IntelPT::builder()
|
||||
.perf_buffer_size(default.perf_buffer_size)
|
||||
.unwrap();
|
||||
IntelPT::builder()
|
||||
.perf_aux_buffer_size(default.perf_aux_buffer_size)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn intel_pt_pt_config_noretcomp_format() {
|
||||
let ptconfig_noretcomp = PtConfig::DEFAULT.with_noretcomp(true).raw_value;
|
||||
let path = format!("{PT_EVENT_PATH}/format/noretcomp");
|
||||
let s = fs::read_to_string(&path).expect("Failed to read Intel PT config noretcomp format");
|
||||
assert!(
|
||||
s.starts_with("config:"),
|
||||
"Unexpected Intel PT config noretcomp format"
|
||||
);
|
||||
let bit = s["config:".len()..]
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.expect("Failed to parse Intel PT config noretcomp format");
|
||||
assert_eq!(
|
||||
ptconfig_noretcomp,
|
||||
0b1 << bit,
|
||||
"Unexpected Intel PT config noretcomp format"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn intel_pt_pt_config_psb_period_format() {
|
||||
let ptconfig_psb_period = PtConfig::DEFAULT.with_psb_period(u4::MAX).raw_value;
|
||||
let path = format!("{PT_EVENT_PATH}/format/psb_period");
|
||||
let s =
|
||||
fs::read_to_string(&path).expect("Failed to read Intel PT config psb_period format");
|
||||
assert!(
|
||||
s.starts_with("config:"),
|
||||
"Unexpected Intel PT config psb_period format"
|
||||
);
|
||||
let from = s["config:".len().."config:".len() + 2]
|
||||
.parse::<u32>()
|
||||
.expect("Failed to parse Intel PT config psb_period format");
|
||||
let to = s["config:".len() + 3..]
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.expect("Failed to parse Intel PT config psb_period format");
|
||||
let mut format = 0;
|
||||
for bit in from..=to {
|
||||
format |= 0b1 << bit;
|
||||
}
|
||||
assert_eq!(
|
||||
ptconfig_psb_period, format,
|
||||
"Unexpected Intel PT config psb_period format"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
1007
libafl_intelpt/src/linux.rs
Normal file
1007
libafl_intelpt/src/linux.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,7 @@
|
||||
|
||||
use std::{arch::asm, process};
|
||||
|
||||
use libafl_intelpt::{availability, IntelPT};
|
||||
use libipt::Image;
|
||||
use libafl_intelpt::{availability, Image, IntelPT};
|
||||
use nix::{
|
||||
sys::{
|
||||
signal::{kill, raise, Signal},
|
||||
@ -69,11 +68,7 @@ fn intel_pt_trace_fork() {
|
||||
None,
|
||||
map.start() as u64,
|
||||
) {
|
||||
Err(e) => println!(
|
||||
"Error adding mapping for {:?}: {:?}, skipping",
|
||||
map.filename().unwrap(),
|
||||
e
|
||||
),
|
||||
Err(e) => println!("skipping mapping of {:?}: {:?}", map.filename().unwrap(), e),
|
||||
Ok(()) => println!(
|
||||
"mapping for {:?} added successfully {:#x} - {:#x}",
|
||||
map.filename().unwrap(),
|
||||
@ -85,7 +80,8 @@ fn intel_pt_trace_fork() {
|
||||
}
|
||||
|
||||
let mut map = vec![0u16; 0x10_00];
|
||||
pt.decode_traces_into_map(&mut image, &mut map).unwrap();
|
||||
pt.decode_traces_into_map(&mut image, map.as_mut_ptr(), map.len())
|
||||
.unwrap();
|
||||
|
||||
let assembly_jump_id = map.iter().position(|count| *count >= 254);
|
||||
assert!(
|
||||
|
@ -9,3 +9,13 @@ for test_bin in ../target/debug/deps/integration_tests_linux-*; do
|
||||
done
|
||||
|
||||
cargo test intel_pt_trace_fork -- --show-output
|
||||
|
||||
cargo test --release intel_pt_trace_fork --no-run
|
||||
|
||||
for test_bin in ../target/release/deps/integration_tests_linux-*; do
|
||||
if file "$test_bin" | grep -q "ELF"; then
|
||||
sudo setcap cap_ipc_lock,cap_sys_ptrace,cap_sys_admin,cap_syslog=ep "$test_bin"
|
||||
fi
|
||||
done
|
||||
|
||||
cargo test --release intel_pt_trace_fork -- --show-output
|
||||
|
@ -37,6 +37,16 @@ pub enum DiskImageFileFormat {
|
||||
Raw,
|
||||
}
|
||||
|
||||
#[derive(Debug, strum_macros::Display, Clone)]
|
||||
#[strum(prefix = "cache=", serialize_all = "lowercase")]
|
||||
pub enum DriveCache {
|
||||
WriteBack,
|
||||
None,
|
||||
WriteThrough,
|
||||
DirectSync,
|
||||
Unsafe,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, TypedBuilder)]
|
||||
pub struct Drive {
|
||||
#[builder(default, setter(strip_option, into))]
|
||||
@ -45,6 +55,8 @@ pub struct Drive {
|
||||
format: Option<DiskImageFileFormat>,
|
||||
#[builder(default, setter(strip_option))]
|
||||
interface: Option<DriveInterface>,
|
||||
#[builder(default, setter(strip_option))]
|
||||
cache: Option<DriveCache>,
|
||||
}
|
||||
|
||||
impl Display for Drive {
|
||||
@ -70,25 +82,96 @@ impl Display for Drive {
|
||||
if let Some(interface) = &self.interface {
|
||||
write!(f, "{}{interface}", separator())?;
|
||||
}
|
||||
if let Some(cache) = &self.cache {
|
||||
write!(f, "{}{cache}", separator())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, strum_macros::Display, Clone)]
|
||||
#[strum(prefix = "-serial ", serialize_all = "lowercase")]
|
||||
#[derive(Debug, Clone, TypedBuilder)]
|
||||
pub struct Tcp {
|
||||
#[builder(default, setter(strip_option))]
|
||||
host: Option<String>,
|
||||
port: u16,
|
||||
#[builder(default, setter(strip_option))]
|
||||
server: Option<bool>,
|
||||
#[builder(default, setter(strip_option))]
|
||||
wait: Option<bool>,
|
||||
#[builder(default, setter(strip_option))]
|
||||
nodelay: Option<bool>,
|
||||
#[builder(default, setter(strip_option))]
|
||||
reconnect_ms: Option<usize>,
|
||||
}
|
||||
|
||||
impl Display for Tcp {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "tcp:{}", self.host.as_deref().unwrap_or(""))?;
|
||||
write!(f, ":{}", self.port)?;
|
||||
let server = match self.server {
|
||||
Some(true) => ",server=on",
|
||||
Some(false) => ",server=off",
|
||||
None => "",
|
||||
};
|
||||
write!(f, "{server}")?;
|
||||
let wait = match self.wait {
|
||||
Some(true) => ",wait=on",
|
||||
Some(false) => ",wait=off",
|
||||
None => "",
|
||||
};
|
||||
write!(f, "{wait}")?;
|
||||
let nodelay = match self.nodelay {
|
||||
Some(true) => ",nodelay=on",
|
||||
Some(false) => ",nodelay=off",
|
||||
None => "",
|
||||
};
|
||||
write!(f, "{nodelay}")?;
|
||||
if let Some(ms) = self.reconnect_ms {
|
||||
write!(f, "{ms}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Serial {
|
||||
None,
|
||||
Null,
|
||||
Stdio,
|
||||
Tcp(Tcp),
|
||||
}
|
||||
|
||||
#[derive(Debug, strum_macros::Display, Clone)]
|
||||
#[strum(prefix = "-monitor ", serialize_all = "lowercase")]
|
||||
impl Display for Serial {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "-serial ")?;
|
||||
match self {
|
||||
Serial::None => write!(f, "none"),
|
||||
Serial::Null => write!(f, "null"),
|
||||
Serial::Stdio => write!(f, "stdio"),
|
||||
Serial::Tcp(tcp) => write!(f, "{tcp}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Monitor {
|
||||
None,
|
||||
Null,
|
||||
Stdio,
|
||||
Tcp(Tcp),
|
||||
}
|
||||
|
||||
impl Display for Monitor {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "-monitor ")?;
|
||||
match self {
|
||||
Monitor::None => write!(f, "none"),
|
||||
Monitor::Null => write!(f, "null"),
|
||||
Monitor::Stdio => write!(f, "stdio"),
|
||||
Monitor::Tcp(tcp) => write!(f, "{tcp}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the directory for the BIOS, VGA BIOS and keymaps.
|
||||
@ -137,6 +220,50 @@ impl<R: AsRef<Path>> From<R> for Kernel {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppendKernelCmd {
|
||||
cmdline: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
impl Display for AppendKernelCmd {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "-append {}", self.cmdline)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
impl<R: AsRef<str>> From<R> for AppendKernelCmd {
|
||||
fn from(cmdline: R) -> Self {
|
||||
Self {
|
||||
cmdline: cmdline.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InitRD {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
impl Display for InitRD {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "-initrd {}", self.path.to_str().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
impl<R: AsRef<Path>> From<R> for InitRD {
|
||||
fn from(path: R) -> Self {
|
||||
Self {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoadVM {
|
||||
path: PathBuf,
|
||||
@ -175,6 +302,25 @@ impl<R: AsRef<str>> From<R> for Machine {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpu {
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl Display for Cpu {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "-cpu {}", self.model)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsRef<str>> From<R> for Cpu {
|
||||
fn from(model: R) -> Self {
|
||||
Self {
|
||||
model: model.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, strum_macros::Display)]
|
||||
pub enum Snapshot {
|
||||
#[strum(serialize = "-snapshot")]
|
||||
@ -274,6 +420,26 @@ impl From<bool> for VgaPci {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[derive(Debug, Clone, strum_macros::Display)]
|
||||
pub enum DefaultDevices {
|
||||
#[strum(serialize = "")]
|
||||
ENABLE,
|
||||
#[strum(serialize = "-nodefaults")]
|
||||
DISABLE,
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
impl From<bool> for DefaultDevices {
|
||||
fn from(default_devices: bool) -> Self {
|
||||
if default_devices {
|
||||
DefaultDevices::ENABLE
|
||||
} else {
|
||||
DefaultDevices::DISABLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Program {
|
||||
@ -304,6 +470,8 @@ pub struct QemuConfig {
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[builder(default, setter(strip_option, into))]
|
||||
bios: Option<Bios>,
|
||||
#[builder(default, setter(strip_option, into))]
|
||||
cpu: Option<Cpu>,
|
||||
#[builder(default, setter(into))]
|
||||
drives: Vec<Drive>,
|
||||
#[cfg(feature = "systemmode")]
|
||||
@ -329,6 +497,15 @@ pub struct QemuConfig {
|
||||
vga_pci: Option<VgaPci>,
|
||||
#[builder(default, setter(strip_option, into))]
|
||||
start_cpu: Option<StartCPU>,
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[builder(default, setter(strip_option, into))]
|
||||
default_devices: Option<DefaultDevices>,
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[builder(default, setter(strip_option, into))]
|
||||
append_kernel_cmd: Option<AppendKernelCmd>,
|
||||
#[cfg(feature = "systemmode")]
|
||||
#[builder(default, setter(strip_option, into))]
|
||||
initrd: Option<InitRD>,
|
||||
#[cfg(feature = "usermode")]
|
||||
#[builder(setter(into))]
|
||||
program: Program,
|
||||
|
@ -11,13 +11,15 @@ use std::{
|
||||
ffi::{c_void, CString},
|
||||
fmt::{Display, Formatter, Write},
|
||||
mem::{transmute, MaybeUninit},
|
||||
ops::Range,
|
||||
ops::{Deref, Range},
|
||||
pin::Pin,
|
||||
ptr::copy_nonoverlapping,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use libafl_bolts::os::unix_signals::Signal;
|
||||
#[cfg(feature = "systemmode")]
|
||||
use libafl_bolts::Error;
|
||||
use libafl_qemu_sys::{
|
||||
libafl_flush_jit, libafl_get_exit_reason, libafl_page_from_addr, libafl_qemu_add_gdb_cmd,
|
||||
libafl_qemu_cpu_index, libafl_qemu_current_cpu, libafl_qemu_gdb_reply, libafl_qemu_get_cpu,
|
||||
@ -26,6 +28,8 @@ use libafl_qemu_sys::{
|
||||
libafl_qemu_write_reg, CPUArchState, CPUStatePtr, FatPtr, GuestAddr, GuestPhysAddr, GuestUsize,
|
||||
GuestVirtAddr,
|
||||
};
|
||||
#[cfg(feature = "systemmode")]
|
||||
use libafl_qemu_sys::{libafl_qemu_remove_hw_breakpoint, libafl_qemu_set_hw_breakpoint};
|
||||
use num_traits::Num;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@ -110,7 +114,8 @@ pub struct Qemu {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum QemuParams {
|
||||
Config(QemuConfig),
|
||||
// QemuConfig is quite big, at least 240 bytes so we use a Box
|
||||
Config(Box<QemuConfig>),
|
||||
Cli(Vec<String>),
|
||||
}
|
||||
|
||||
@ -178,7 +183,7 @@ impl Display for QemuExitReason {
|
||||
|
||||
impl From<QemuConfig> for QemuParams {
|
||||
fn from(config: QemuConfig) -> Self {
|
||||
QemuParams::Config(config)
|
||||
QemuParams::Config(Box::new(config))
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,7 +544,7 @@ impl Qemu {
|
||||
match ¶ms {
|
||||
QemuParams::Config(cfg) => {
|
||||
QEMU_CONFIG
|
||||
.set(cfg.clone())
|
||||
.set(cfg.deref().clone())
|
||||
.map_err(|_| unreachable!("QEMU_CONFIG was already set but Qemu was not init!"))
|
||||
.expect("Could not set QEMU Config.");
|
||||
}
|
||||
@ -861,6 +866,28 @@ impl Qemu {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
pub fn set_hw_breakpoint(&self, addr: GuestAddr) -> Result<(), Error> {
|
||||
let ret = unsafe { libafl_qemu_set_hw_breakpoint(addr.into()) };
|
||||
match ret {
|
||||
0 => Ok(()),
|
||||
errno => Err(Error::unsupported(format!(
|
||||
"Failed to set hw breakpoint errno: {errno}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "systemmode")]
|
||||
pub fn remove_hw_breakpoint(&self, addr: GuestAddr) -> Result<(), Error> {
|
||||
let ret = unsafe { libafl_qemu_remove_hw_breakpoint(addr.into()) };
|
||||
match ret {
|
||||
0 => Ok(()),
|
||||
errno => Err(Error::unsupported(format!(
|
||||
"Failed to set hw breakpoint errno: {errno}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_break(&self, addr: GuestAddr) {
|
||||
self.set_breakpoint(addr);
|
||||
unsafe {
|
||||
|
Loading…
x
Reference in New Issue
Block a user