Skip to content

Commit

Permalink
trace decoding draft
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcondiro committed Aug 18, 2024
1 parent 42302fc commit 93f2a5a
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 83 deletions.
3 changes: 2 additions & 1 deletion libafl_qemu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ memmap2 = "0.9"
getset = "0.1"
bitflags = "2.6"
perf-event-open-sys = "4" # Uses Linux 5.19.4 headers, consider forking if we need to bump
caps = "0.5" # TODO: Mark deps used only in systemmode as optional
caps = "0.5" # TODO: Mark deps used only in systemmode as optional (requires fixing linux_build.rs)
libipt = {git = "https://github.com/sum-catnip/libipt-rs", rev = "1dc124b"} # v2 is not on crates.io
# Document all features of this crate (for `cargo doc`)
document-features = { version = "0.2", optional = true }

Expand Down
95 changes: 43 additions & 52 deletions libafl_qemu/src/modules/systemmode/intel_pt.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use libafl_qemu_sys::GuestVirtAddr;
use libafl::inputs::UsesInput;
use libafl::observers::ObserversTuple;
use libafl_targets::EDGES_MAP;
use crate::{modules::{EmulatorModule, ExitKind}, qemu::intel_pt::IntelPT, CpuPreRunHook, EmulatorModules, NewThreadHook};
use crate::modules::EmulatorModuleTuple;
use libafl::{inputs::UsesInput, observers::ObserversTuple, HasMetadata};
use libafl_qemu_sys::{CPUArchStatePtr, GuestVirtAddr};

use crate::{
modules::{EmulatorModule, EmulatorModuleTuple, ExitKind},
qemu::intel_pt::IntelPT,
EmulatorModules,
};

#[derive(Debug)]
pub struct IntelPTModule {
Expand All @@ -16,70 +18,59 @@ impl IntelPTModule {
}
}

pub fn intel_pt_new_thread<ET, S>(
emulator_modules: &mut EmulatorModules<ET, S>,
state: Option<&mut S>,
env: CPUArchStatePtr,
tid: u32
) -> Option<u64>
where
S: HasMetadata + Unpin + UsesInput,
ET: EmulatorModuleTuple<S>,
{
// do something each time a thread in created in QEMU
let intel_pt_module: IntelPTModule = match emulator_modules.modules().match_first_type_mut::<IntelPTModule>();
// pub fn intel_pt_new_thread<ET, S>(
// emulator_modules: &mut EmulatorModules<ET, S>,
// _state: Option<&mut S>,
// _env: CPUArchStatePtr,
// tid: u32
// ) -> bool
// where
// S: HasMetadata + Unpin + UsesInput,
// ET: EmulatorModuleTuple<S>,
// {
// let intel_pt_module = emulator_modules.modules().match_first_type_mut::<IntelPTModule>().unwrap();

if let Some(pt) = intel_pt_module.pt {
// update PT state
}
}
// if let Some(pt) = &mut intel_pt_module.pt {
// // update PT state
// }

pub fn intel_pt_pre_cpu_exec<ET, S>(
emulator_modules: &mut EmulatorModules<ET, S>,
state: Option<&mut S>,
cpu: CPUStatePtr,
) -> Option<u64>
where
S: HasMetadata + Unpin + UsesInput,
ET: EmulatorModuleTuple<S>,
{
// do something each time a thread in created in QEMU
let intel_pt_module: IntelPTModule = match emulator_modules.modules().match_first_type_mut::<IntelPTModule>();

if let Some(pt) = intel_pt_module.pt {
// update PT state
}
}
// // Why a bool here?
// true
// }

impl<S> EmulatorModule<S> for IntelPTModule
where
S: Unpin + UsesInput,
S: Unpin + UsesInput + HasMetadata,
{
fn first_exec<ET>(&mut self, emulator_modules: &mut EmulatorModules<ET, S>)
where
ET: EmulatorModuleTuple<S>,
{
emulator_modules.thread_creation(
NewThreadHook::Function(intel_pt_new_thread::<ET, S>)
);
// emulator_modules.thread_creation(
// NewThreadHook::Function(intel_pt_new_thread::<ET, S>)
// );

emulator_modules.cpu_runs(
CpuPreRunHook::Function(...),
CpuPostRunHook::Function(...),
);
// emulator_modules.cpu_runs(
// CpuPostRunHook::Function(...),
// );
}

fn post_exec<OT, ET>(&mut self, _emulator_modules: &mut EmulatorModules<ET, S>, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind)
where
fn post_exec<OT, ET>(
&mut self,
_emulator_modules: &mut EmulatorModules<ET, S>,
_input: &S::Input,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S>,
ET: EmulatorModuleTuple<S>,
{
// 1. decode traces
// Output: List of block's IPs we are going through during the fuzzer's run (after filtering)
let indexes: Vec<GuestVirtAddr> = {
// result of decoding
// use libxdc...
};
// let indexes: Vec<GuestVirtAddr> = {
// // result of decoding
// // use libxdc...
// };

// 2. update map
// for idx in indexes {
Expand Down
131 changes: 101 additions & 30 deletions libafl_qemu/src/qemu/systemmode/intel_pt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::{
use bitflags::bitflags;
use caps::{CapSet, Capability};
use libafl::Error;
use libipt::{block::BlockDecoder, ConfigBuilder};
use num_enum::TryFromPrimitive;
use perf_event_open_sys::{
bindings::{perf_event_attr, perf_event_mmap_page, PERF_FLAG_FD_CLOEXEC},
Expand Down Expand Up @@ -46,39 +47,26 @@ bitflags! {
}
}

pub trait IntelPTDecoder {
fn decode(&mut self, traces: &IntelPTTraces) -> IntelPTTracesResult;
}

pub struct IntelPTTracesResult {}

pub struct IntelPTTraces {}

/// Intel official Intel PT trace decoder
pub struct Libipt {}
// pub trait IntelPTDecoder {
// fn decode(&mut self, traces: &IntelPTTraces) -> Result((), Error);
// }

/// kAFL Intel PT trace decoder
pub struct Libxdc {}
// /// Intel official Intel PT trace decoder
// pub struct Libipt {}

impl IntelPTDecoder for Libipt {
fn decode(&mut self, traces: &IntelPTTraces) -> IntelPTTracesResult {
todo!()
}
}

impl IntelPTDecoder for Libxdc {
fn decode(&mut self, traces: &IntelPTTraces) -> IntelPTTracesResult {
todo!()
}
}
// impl IntelPTDecoder for Libipt {
// fn decode(&mut self, traces: &IntelPTTraces) -> IntelPTTracesResult {
// todo!()
// }
// }

// TODO generic decoder: D,
#[derive(Debug)]
pub struct IntelPT<D> {
pub struct IntelPT {
fd: OwnedFd,
perf_buffer: *mut c_void,
perf_aux_buffer: *mut c_void,
buff_metadata: *mut perf_event_mmap_page,
decoder: D,
}

impl IntelPT {
Expand Down Expand Up @@ -171,7 +159,9 @@ impl IntelPT {
}
}

pub fn read_trace_into<T: Write>(&self, buff: &mut T) -> Result<(), Error> {
// TODO remove
#[deprecated]
fn read_trace_into<T: Write>(&self, buff: &mut T) -> Result<(), Error> {
// TODO: should we read also the normal buffer?
let aux_head = unsafe { ptr::addr_of_mut!((*self.buff_metadata).aux_head) };
let aux_tail = unsafe { ptr::addr_of_mut!((*self.buff_metadata).aux_tail) };
Expand All @@ -195,6 +185,73 @@ impl IntelPT {
Ok(())
}

pub fn decode(&mut self) -> Vec<u64> {
let mut ips = Vec::new();

let aux_head = unsafe { ptr::addr_of_mut!((*self.buff_metadata).aux_head) };
let aux_tail = unsafe { ptr::addr_of_mut!((*self.buff_metadata).aux_tail) };

let head = wrap_aux_pointer(unsafe { aux_head.read_volatile() });
let tail = wrap_aux_pointer(unsafe { aux_tail.read_volatile() });
let data = unsafe { self.perf_aux_buffer.add(tail as usize) } as *mut u8;
let len = (head - tail) as usize;

debug_assert!(head >= tail, "Intel PT: aux head is behind aux tail");

smp_rmb(); // TODO double check impl

// TODO config.cpu = <cpu identifier>; ?
// TODO handle decoding failures with config.decode.callback = <decode function>; config.decode.context = <decode context>;
// TODO remove unwrap()
let config = ConfigBuilder::new(unsafe { slice::from_raw_parts_mut(data, len) })
.unwrap()
.finish();
let mut decoder = BlockDecoder::new(&config).unwrap();

// TODO rewrite decently
loop {
let mut status = match decoder.sync_forward() {
Ok(s) => s,
Err(e) => break,
};

loop {
if loop {
if !status.event_pending() {
break Ok(());
}
match decoder.event() {
Ok((_, s)) => {
status = s;
}
Err(e) => break Err(e),
};
}
.is_err()
{
break;
}

let packet = decoder.next();
match packet {
Err(e) => {
println!("pterror in packet next {:?}", e);
break;
}
Ok((b, s)) => {
ips.push(b.ip());

if s.eos() {
break;
}
}
}
}
}
// TODO load the binary
ips
}

/// Check if Intel PT is available on the current system.
///
/// This function can be helpful when `IntelPT::try_new` or `set_ip_filter` fail for an unclear
Expand Down Expand Up @@ -239,6 +296,12 @@ impl IntelPT {
));
}

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

let kvm_pt_mode_path = "/sys/module/kvm_intel/parameters/pt_mode";
if let Ok(s) = fs::read_to_string(kvm_pt_mode_path) {
match s.trim().parse::<i32>().map(|i| i.try_into()) {
Expand Down Expand Up @@ -332,7 +395,7 @@ fn setup_perf_aux_buffer(fd: &OwnedFd, size: u64, offset: u64) -> Result<*mut c_

fn new_perf_event_attr_intel_pt() -> Result<perf_event_attr, Error> {
let mut attr: perf_event_attr = unsafe { core::mem::zeroed() };
attr.size = core::mem::size_of::<perf_event_attr>() as u32;
attr.size = size_of::<perf_event_attr>() as u32;
attr.type_ = intel_pt_perf_type()?;
attr.set_disabled(1);
attr.config |= PtConfig::NORETCOMP.bits();
Expand Down Expand Up @@ -375,6 +438,13 @@ const fn wrap_aux_pointer(ptr: u64) -> u64 {
ptr & (PERF_AUX_BUFFER_SIZE as u64 - 1)
}

#[inline]
pub fn smp_rmb() {
unsafe {
core::arch::asm!("lfence", options(nostack, preserves_flags));
}
}

#[cfg(test)]
mod test {
use std::{fs::OpenOptions, process};
Expand Down Expand Up @@ -459,9 +529,10 @@ mod test {

waitpid(pid, None).expect("Failed to wait for the child process");

// pt.read_trace_into(&mut file)
// .expect("Failed to write traces");
// fs::remove_file(trace_path).expect("Failed to remove trace file");
println!("Intel PT traces: {:?}", pt.decode());
pt.disable_tracing().expect("Failed to disable tracing");
pt.read_trace_into(&mut file)
.expect("Failed to write traces");
fs::remove_file(trace_path).expect("Failed to remove trace file");
}
}

0 comments on commit 93f2a5a

Please sign in to comment.