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 }
|
fastbloom = { version = "0.8.0", default-features = false }
|
||||||
hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible
|
hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible
|
||||||
libc = "0.2.159" # For (*nix) libc
|
libc = "0.2.159" # For (*nix) libc
|
||||||
libipt = "0.2.0"
|
libipt = "0.3.0"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
meminterval = "0.4.1"
|
meminterval = "0.4.1"
|
||||||
mimalloc = { version = "0.1.43", default-features = false }
|
mimalloc = { version = "0.1.43", default-features = false }
|
||||||
|
@ -8,7 +8,7 @@ use libafl::{
|
|||||||
corpus::{InMemoryCorpus, OnDiskCorpus},
|
corpus::{InMemoryCorpus, OnDiskCorpus},
|
||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::{
|
executors::{
|
||||||
hooks::intel_pt::{IntelPTHook, Section},
|
hooks::intel_pt::{IntelPTHook, SectionInfo},
|
||||||
inprocess::GenericInProcessExecutor,
|
inprocess::GenericInProcessExecutor,
|
||||||
ExitKind,
|
ExitKind,
|
||||||
},
|
},
|
||||||
@ -100,10 +100,10 @@ pub fn main() {
|
|||||||
let sections = process_maps
|
let sections = process_maps
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|pm| {
|
.filter_map(|pm| {
|
||||||
if pm.is_exec() && pm.filename().is_some() {
|
if pm.is_exec() && pm.filename().is_some() && pm.inode != 0 {
|
||||||
Some(Section {
|
Some(SectionInfo {
|
||||||
file_path: pm.filename().unwrap().to_string_lossy().to_string(),
|
filename: pm.filename().unwrap().to_string_lossy().to_string(),
|
||||||
file_offset: pm.offset as u64,
|
offset: pm.offset as u64,
|
||||||
size: pm.size() as u64,
|
size: pm.size() as u64,
|
||||||
virtual_address: pm.start() as u64,
|
virtual_address: pm.start() as u64,
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,7 @@ use libafl::{
|
|||||||
events::SimpleEventManager,
|
events::SimpleEventManager,
|
||||||
executors::{
|
executors::{
|
||||||
command::{CommandConfigurator, PTraceCommandConfigurator},
|
command::{CommandConfigurator, PTraceCommandConfigurator},
|
||||||
hooks::intel_pt::{IntelPTHook, Section},
|
hooks::intel_pt::{IntelPTHook, SectionInfo},
|
||||||
},
|
},
|
||||||
feedbacks::{CrashFeedback, MaxMapFeedback},
|
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||||
fuzzer::{Fuzzer, StdFuzzer},
|
fuzzer::{Fuzzer, StdFuzzer},
|
||||||
@ -105,9 +105,9 @@ pub fn main() {
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sections = [Section {
|
let sections = [SectionInfo {
|
||||||
file_path: target_path.to_string_lossy().to_string(),
|
filename: target_path.to_string_lossy().to_string(),
|
||||||
file_offset: 0x14000,
|
offset: 0x14000,
|
||||||
size: (*code_memory_addresses.end() - *code_memory_addresses.start() + 1) as u64,
|
size: (*code_memory_addresses.end() - *code_memory_addresses.start() + 1) as u64,
|
||||||
virtual_address: *code_memory_addresses.start() as u64,
|
virtual_address: *code_memory_addresses.start() as u64,
|
||||||
}];
|
}];
|
||||||
|
@ -111,13 +111,7 @@ regex = ["std", "dep:regex"]
|
|||||||
casr = ["libcasr", "std", "regex"]
|
casr = ["libcasr", "std", "regex"]
|
||||||
|
|
||||||
## Intel Processor Trace
|
## Intel Processor Trace
|
||||||
intel_pt = [
|
intel_pt = ["std", "dep:libafl_intelpt", "dep:nix", "dep:num_enum"]
|
||||||
"std",
|
|
||||||
"dep:libafl_intelpt",
|
|
||||||
"dep:libipt",
|
|
||||||
"dep:nix",
|
|
||||||
"dep:num_enum",
|
|
||||||
]
|
|
||||||
## Save all the Intel PT raw traces to files, use only for debug
|
## Save all the Intel PT raw traces to files, use only for debug
|
||||||
intel_pt_export_raw = ["intel_pt", "libafl_intelpt/export_raw"]
|
intel_pt_export_raw = ["intel_pt", "libafl_intelpt/export_raw"]
|
||||||
|
|
||||||
@ -307,7 +301,6 @@ document-features = { workspace = true, optional = true }
|
|||||||
# Optional
|
# Optional
|
||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
num_enum = { workspace = true, optional = true }
|
num_enum = { workspace = true, optional = true }
|
||||||
libipt = { workspace = true, optional = true }
|
|
||||||
fastbloom = { workspace = true, optional = true }
|
fastbloom = { workspace = true, optional = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
@ -1,37 +1,24 @@
|
|||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::{
|
|
||||||
ptr::slice_from_raw_parts_mut,
|
|
||||||
string::{String, ToString},
|
|
||||||
};
|
|
||||||
|
|
||||||
use libafl_intelpt::{error_from_pt_error, IntelPT};
|
pub use libafl_intelpt::SectionInfo;
|
||||||
use libipt::{Asid, Image, SectionCache};
|
use libafl_intelpt::{Image, IntelPT};
|
||||||
use num_traits::SaturatingAdd;
|
use num_traits::SaturatingAdd;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typed_builder::TypedBuilder;
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
use crate::{executors::hooks::ExecutorHook, Error};
|
use crate::executors::hooks::ExecutorHook;
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hook to enable Intel Processor Trace (PT) tracing
|
/// Hook to enable Intel Processor Trace (PT) tracing
|
||||||
#[derive(Debug, TypedBuilder)]
|
#[derive(Debug, TypedBuilder)]
|
||||||
pub struct IntelPTHook<T> {
|
pub struct IntelPTHook<T> {
|
||||||
#[builder(default = IntelPT::builder().build().unwrap())]
|
#[builder(default = IntelPT::builder().build().unwrap())]
|
||||||
intel_pt: IntelPT,
|
intel_pt: IntelPT,
|
||||||
#[builder(setter(transform = |sections: &[Section]| sections_to_image(sections).unwrap()))]
|
#[builder(setter(transform = |sections: &[SectionInfo]| {
|
||||||
image: (Image<'static>, SectionCache<'static>),
|
let mut i = Image::new(None).unwrap();
|
||||||
|
i.add_files_cached(sections, None).unwrap();
|
||||||
|
i
|
||||||
|
}))]
|
||||||
|
image: Image,
|
||||||
map_ptr: *mut T,
|
map_ptr: *mut T,
|
||||||
map_len: usize,
|
map_len: usize,
|
||||||
}
|
}
|
||||||
@ -51,9 +38,8 @@ where
|
|||||||
let pt = &mut self.intel_pt;
|
let pt = &mut self.intel_pt;
|
||||||
pt.disable_tracing().unwrap();
|
pt.disable_tracing().unwrap();
|
||||||
|
|
||||||
let slice = unsafe { &mut *slice_from_raw_parts_mut(self.map_ptr, self.map_len) };
|
|
||||||
let _ = pt
|
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}"));
|
.inspect_err(|e| log::warn!("Intel PT trace decoding failed: {e}"));
|
||||||
#[cfg(feature = "intel_pt_export_raw")]
|
#[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]
|
#[macro_use]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
use std::{
|
|
||||||
borrow::ToOwned,
|
|
||||||
string::{String, ToString},
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use std::{
|
use std::fs;
|
||||||
boxed::Box,
|
use std::{borrow::ToOwned, string::String, vec::Vec};
|
||||||
ffi::{CStr, CString},
|
|
||||||
fmt::Debug,
|
use raw_cpuid::CpuId;
|
||||||
format, fs,
|
|
||||||
ops::RangeInclusive,
|
|
||||||
os::{
|
|
||||||
fd::{AsRawFd, FromRawFd, OwnedFd},
|
|
||||||
raw::c_void,
|
|
||||||
},
|
|
||||||
path::Path,
|
|
||||||
ptr, slice,
|
|
||||||
sync::LazyLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use arbitrary_int::u4;
|
mod linux;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use bitbybit::bitfield;
|
pub use linux::*;
|
||||||
#[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;
|
|
||||||
|
|
||||||
/// Size of a memory page
|
/// Size of a memory page
|
||||||
pub const PAGE_SIZE: usize = 4096;
|
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.
|
/// 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
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
use arbitrary_int::Number;
|
|
||||||
use static_assertions::assert_eq_size;
|
use static_assertions::assert_eq_size;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -1005,65 +136,4 @@ mod test {
|
|||||||
Err(e) => println!("❌\tReasons: {e}"),
|
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 std::{arch::asm, process};
|
||||||
|
|
||||||
use libafl_intelpt::{availability, IntelPT};
|
use libafl_intelpt::{availability, Image, IntelPT};
|
||||||
use libipt::Image;
|
|
||||||
use nix::{
|
use nix::{
|
||||||
sys::{
|
sys::{
|
||||||
signal::{kill, raise, Signal},
|
signal::{kill, raise, Signal},
|
||||||
@ -69,11 +68,7 @@ fn intel_pt_trace_fork() {
|
|||||||
None,
|
None,
|
||||||
map.start() as u64,
|
map.start() as u64,
|
||||||
) {
|
) {
|
||||||
Err(e) => println!(
|
Err(e) => println!("skipping mapping of {:?}: {:?}", map.filename().unwrap(), e),
|
||||||
"Error adding mapping for {:?}: {:?}, skipping",
|
|
||||||
map.filename().unwrap(),
|
|
||||||
e
|
|
||||||
),
|
|
||||||
Ok(()) => println!(
|
Ok(()) => println!(
|
||||||
"mapping for {:?} added successfully {:#x} - {:#x}",
|
"mapping for {:?} added successfully {:#x} - {:#x}",
|
||||||
map.filename().unwrap(),
|
map.filename().unwrap(),
|
||||||
@ -85,7 +80,8 @@ fn intel_pt_trace_fork() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut map = vec![0u16; 0x10_00];
|
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);
|
let assembly_jump_id = map.iter().position(|count| *count >= 254);
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -9,3 +9,13 @@ for test_bin in ../target/debug/deps/integration_tests_linux-*; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
cargo test intel_pt_trace_fork -- --show-output
|
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,
|
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)]
|
#[derive(Debug, Clone, Default, TypedBuilder)]
|
||||||
pub struct Drive {
|
pub struct Drive {
|
||||||
#[builder(default, setter(strip_option, into))]
|
#[builder(default, setter(strip_option, into))]
|
||||||
@ -45,6 +55,8 @@ pub struct Drive {
|
|||||||
format: Option<DiskImageFileFormat>,
|
format: Option<DiskImageFileFormat>,
|
||||||
#[builder(default, setter(strip_option))]
|
#[builder(default, setter(strip_option))]
|
||||||
interface: Option<DriveInterface>,
|
interface: Option<DriveInterface>,
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
cache: Option<DriveCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Drive {
|
impl Display for Drive {
|
||||||
@ -70,25 +82,96 @@ impl Display for Drive {
|
|||||||
if let Some(interface) = &self.interface {
|
if let Some(interface) = &self.interface {
|
||||||
write!(f, "{}{interface}", separator())?;
|
write!(f, "{}{interface}", separator())?;
|
||||||
}
|
}
|
||||||
|
if let Some(cache) = &self.cache {
|
||||||
|
write!(f, "{}{cache}", separator())?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, strum_macros::Display, Clone)]
|
#[derive(Debug, Clone, TypedBuilder)]
|
||||||
#[strum(prefix = "-serial ", serialize_all = "lowercase")]
|
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 {
|
pub enum Serial {
|
||||||
None,
|
None,
|
||||||
Null,
|
Null,
|
||||||
Stdio,
|
Stdio,
|
||||||
|
Tcp(Tcp),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, strum_macros::Display, Clone)]
|
impl Display for Serial {
|
||||||
#[strum(prefix = "-monitor ", serialize_all = "lowercase")]
|
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 {
|
pub enum Monitor {
|
||||||
None,
|
None,
|
||||||
Null,
|
Null,
|
||||||
Stdio,
|
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.
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LoadVM {
|
pub struct LoadVM {
|
||||||
path: PathBuf,
|
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)]
|
#[derive(Debug, Clone, strum_macros::Display)]
|
||||||
pub enum Snapshot {
|
pub enum Snapshot {
|
||||||
#[strum(serialize = "-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")]
|
#[cfg(feature = "usermode")]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
@ -304,6 +470,8 @@ pub struct QemuConfig {
|
|||||||
#[cfg(feature = "systemmode")]
|
#[cfg(feature = "systemmode")]
|
||||||
#[builder(default, setter(strip_option, into))]
|
#[builder(default, setter(strip_option, into))]
|
||||||
bios: Option<Bios>,
|
bios: Option<Bios>,
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
cpu: Option<Cpu>,
|
||||||
#[builder(default, setter(into))]
|
#[builder(default, setter(into))]
|
||||||
drives: Vec<Drive>,
|
drives: Vec<Drive>,
|
||||||
#[cfg(feature = "systemmode")]
|
#[cfg(feature = "systemmode")]
|
||||||
@ -329,6 +497,15 @@ pub struct QemuConfig {
|
|||||||
vga_pci: Option<VgaPci>,
|
vga_pci: Option<VgaPci>,
|
||||||
#[builder(default, setter(strip_option, into))]
|
#[builder(default, setter(strip_option, into))]
|
||||||
start_cpu: Option<StartCPU>,
|
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")]
|
#[cfg(feature = "usermode")]
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
program: Program,
|
program: Program,
|
||||||
|
@ -11,13 +11,15 @@ use std::{
|
|||||||
ffi::{c_void, CString},
|
ffi::{c_void, CString},
|
||||||
fmt::{Display, Formatter, Write},
|
fmt::{Display, Formatter, Write},
|
||||||
mem::{transmute, MaybeUninit},
|
mem::{transmute, MaybeUninit},
|
||||||
ops::Range,
|
ops::{Deref, Range},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
ptr::copy_nonoverlapping,
|
ptr::copy_nonoverlapping,
|
||||||
sync::OnceLock,
|
sync::OnceLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
use libafl_bolts::os::unix_signals::Signal;
|
use libafl_bolts::os::unix_signals::Signal;
|
||||||
|
#[cfg(feature = "systemmode")]
|
||||||
|
use libafl_bolts::Error;
|
||||||
use libafl_qemu_sys::{
|
use libafl_qemu_sys::{
|
||||||
libafl_flush_jit, libafl_get_exit_reason, libafl_page_from_addr, libafl_qemu_add_gdb_cmd,
|
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,
|
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,
|
libafl_qemu_write_reg, CPUArchState, CPUStatePtr, FatPtr, GuestAddr, GuestPhysAddr, GuestUsize,
|
||||||
GuestVirtAddr,
|
GuestVirtAddr,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "systemmode")]
|
||||||
|
use libafl_qemu_sys::{libafl_qemu_remove_hw_breakpoint, libafl_qemu_set_hw_breakpoint};
|
||||||
use num_traits::Num;
|
use num_traits::Num;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
@ -110,7 +114,8 @@ pub struct Qemu {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum QemuParams {
|
pub enum QemuParams {
|
||||||
Config(QemuConfig),
|
// QemuConfig is quite big, at least 240 bytes so we use a Box
|
||||||
|
Config(Box<QemuConfig>),
|
||||||
Cli(Vec<String>),
|
Cli(Vec<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +183,7 @@ impl Display for QemuExitReason {
|
|||||||
|
|
||||||
impl From<QemuConfig> for QemuParams {
|
impl From<QemuConfig> for QemuParams {
|
||||||
fn from(config: QemuConfig) -> Self {
|
fn from(config: QemuConfig) -> Self {
|
||||||
QemuParams::Config(config)
|
QemuParams::Config(Box::new(config))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,7 +544,7 @@ impl Qemu {
|
|||||||
match ¶ms {
|
match ¶ms {
|
||||||
QemuParams::Config(cfg) => {
|
QemuParams::Config(cfg) => {
|
||||||
QEMU_CONFIG
|
QEMU_CONFIG
|
||||||
.set(cfg.clone())
|
.set(cfg.deref().clone())
|
||||||
.map_err(|_| unreachable!("QEMU_CONFIG was already set but Qemu was not init!"))
|
.map_err(|_| unreachable!("QEMU_CONFIG was already set but Qemu was not init!"))
|
||||||
.expect("Could not set QEMU Config.");
|
.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) {
|
pub fn entry_break(&self, addr: GuestAddr) {
|
||||||
self.set_breakpoint(addr);
|
self.set_breakpoint(addr);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user