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:
Marco C. 2025-02-17 14:50:07 +01:00 committed by GitHub
parent 530a3cc6aa
commit 47f7978b91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1260 additions and 1026 deletions

View File

@ -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 }

View File

@ -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,
})

View File

@ -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,
}];

View File

@ -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]

View File

@ -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))
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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!(

View File

@ -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

View File

@ -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,

View File

@ -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 &params {
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 {