Skip to content

Commit

Permalink
WIP: IntelPT qemu systemmode
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcondiro committed Aug 2, 2024
1 parent ea428d1 commit 615b87b
Show file tree
Hide file tree
Showing 6 changed files with 2,915 additions and 3 deletions.
3 changes: 2 additions & 1 deletion libafl_qemu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ goblin = "0.8"
libc = "0.2"
strum = "0.26"
strum_macros = "0.26"
syscall-numbers = "3.0"
syscall-numbers = "4.0"
meminterval = "0.4"
thread_local = "1.1.4"
capstone = "0.12.0"
Expand All @@ -129,6 +129,7 @@ bytes-utils = "0.1"
typed-builder = "0.18"
memmap2 = "0.9"
getset = "0.1"
bitflags = "2.6"
# Document all features of this crate (for `cargo doc`)
document-features = { version = "0.2", optional = true }

Expand Down
12 changes: 11 additions & 1 deletion libafl_qemu/build_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn build() {
} else if cfg!(feature = "systemmode") {
"systemmode".to_string()
} else {
env::var("EMULATION_MODE").unwrap_or_else(|_| "usermode".to_string())
env::var("EMULATION_MODE").unwrap_or_else(|_| "systemmode".to_string())
};

let src_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
Expand Down Expand Up @@ -160,4 +160,14 @@ pub fn build() {
.expect("make failed")
.success());
}

// TODO specific flag for kvm/intel_pt? or just use bindgen-cli?
if emulation_mode == "systemmode" {
bindgen::Builder::default()
.header("/usr/include/linux/perf_event.h")
.generate()
.expect("Unable to generate perf_event.h bindings")
.write_to_file("src/qemu/systemmode/perf_event_bindings.rs")
.expect("Unable to write perf_event.h bindings");
}
}
2 changes: 1 addition & 1 deletion libafl_qemu/libafl_qemu_sys/build_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn build() {
println!(
"cargo:warning=No emulation mode feature enabled or EMULATION_MODE env specified for libafl_qemu, supported: usermode, systemmmode - defaulting to usermode"
);
"usermode".to_string()
"systemmode".to_string()
})
};
println!("cargo::rustc-check-cfg=cfg(emulation_mode, values(\"usermode\", \"systemmode\"))");
Expand Down
192 changes: 192 additions & 0 deletions libafl_qemu/src/qemu/systemmode/intel_pt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
mod perf_events {
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(unused)]
pub mod bindings {
include!("perf_event_bindings.rs");
}
use std::fs;

pub use bindings::*;
use bitflags::bitflags;

const PT_EVENT_TYPE_PATH: &str = "/sys/bus/event_source/devices/intel_pt/type";

bitflags! {
/// IA32_RTIT_CTL MSR flags
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct PtConfig: u64 {
/// Disable call return address compression.
/// AKA DisRETC in Intel SDM
const NORETCOMP = 0b1 << 11;
}
}

impl perf_event_attr {
pub fn new_intel_pt() -> Result<Self, ()> {
let mut attr: perf_event_attr;
unsafe {
attr = core::mem::zeroed();
}
attr.size = core::mem::size_of::<perf_event_attr>() as u32;
attr.type_ = intel_pt_perf_type()?;
attr.set_disabled(1);
attr.config |= PtConfig::NORETCOMP.bits();

Ok(attr)
}
}

fn intel_pt_perf_type() -> Result<u32, ()> {
let s = fs::read_to_string(PT_EVENT_TYPE_PATH).map_err(|_| ())?;
s.parse::<u32>().map_err(|_| ())
// TODO better Err()
}
}

use std::os::{
fd::{AsRawFd, FromRawFd, OwnedFd},
raw::c_void,
};

use libc::{ioctl, syscall};
use perf_events::{perf_event_attr, perf_event_mmap_page, PERF_FLAG_FD_CLOEXEC};
use syscall_numbers::x86_64::SYS_perf_event_open;

const PAGE_SIZE: usize = 4096;
const PERF_BUFFER_SIZE: usize = ((1 + (1 << 7)) * PAGE_SIZE) as usize;
const PERF_AUX_BUFFER_SIZE: usize = 64 * 1024 * 1024;

const _: () = assert!(
((PERF_BUFFER_SIZE - PAGE_SIZE) & (PERF_BUFFER_SIZE - PAGE_SIZE - 1)) == 0,
"PERF_BUFFER_SIZE should be 1+2^n pages"
);
const _: () = assert!(
(PERF_AUX_BUFFER_SIZE % PAGE_SIZE) == 0,
"PERF_AUX_BUFFER_SIZE must be page aligned"
);
const _: () = assert!(
(PERF_AUX_BUFFER_SIZE & (PERF_AUX_BUFFER_SIZE - 1)) == 0,
"PERF_AUX_BUFFER_SIZE must be a power of two"
);
// Ensure we can use usize and u64 interchangeably
const _: () = assert!(
size_of::<usize>() == size_of::<u64>(),
"IntelPT: Only 64-bit systems are supported"
);

// TODO use libaflerr instead of () for Result

pub struct IntelPT {
perf_event_attr: perf_event_attr,
fd: OwnedFd,
// TODO use proper types
perf_buffer: *mut c_void,
perf_aux_buffer: *mut c_void,
}

impl IntelPT {
pub fn new() -> Self {
// TODO change the unwrap
let perf_event_attr = perf_event_attr::new_intel_pt().unwrap();
let fd = perf_event_open(&perf_event_attr, 0).unwrap();
let perf_buffer = setup_perf_buffer(&fd).unwrap();

// the first page is a metadata page
let buff_metadata = &mut unsafe { *perf_buffer.cast::<perf_event_mmap_page>() };
buff_metadata.aux_offset =
next_page_aligned_addr((buff_metadata.data_offset + buff_metadata.data_size) as usize)
as u64;
buff_metadata.aux_size = PERF_AUX_BUFFER_SIZE as u64;

let perf_aux_buffer = setup_perf_aux_buffer(&fd, buff_metadata).unwrap();

Self {
perf_event_attr,
fd,
perf_buffer,
perf_aux_buffer,
}
}

pub fn set_ip_filter(&self) -> Result<(), ()> {
let filter = "filter 0x7c00/512";
let ret = unsafe {
// TODO: clever way of using perf_event.h PERF_EVENT_IOC_SET_FILTER
// check why bindgen fails
ioctl(self.fd.as_raw_fd(), 0xdeadbeef, filter.as_ptr())
};
if ret == 0 {
Ok(())
} else {
Err(())
}
// TODO save ip filters somewhere in the struct
}
}

#[inline]
const fn next_page_aligned_addr(address: usize) -> usize {
(address + PAGE_SIZE - 1) & !(PAGE_SIZE - 1)
}

#[inline]
fn perf_event_open(attr: &perf_event_attr, pid: u32) -> Result<OwnedFd, &'static str> {
unsafe {
let res = syscall(
SYS_perf_event_open,
attr as *const _,
pid,
-1,
-1,
PERF_FLAG_FD_CLOEXEC,
);
if res == -1 {
Err("IntelPT: Failed to open perf event")
// TODO read errno and better Err()
} else {
Ok(OwnedFd::from_raw_fd(res as i32))
}
}
}

fn setup_perf_buffer(fd: &OwnedFd) -> Result<*mut c_void, &'static str> {
let mmap_addr = unsafe {
libc::mmap(
std::ptr::null_mut(),
PERF_BUFFER_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd.as_raw_fd(),
0,
)
};
if mmap_addr == libc::MAP_FAILED {
Err("IntelPT: Failed to mmap perf buffer")
} else {
Ok(mmap_addr)
}
}

fn setup_perf_aux_buffer(
fd: &OwnedFd,
buff_metadata: &perf_event_mmap_page,
) -> Result<*mut c_void, &'static str> {
// PROT_WRITE sets PT to stop when the buffer is full
let mmap_addr = unsafe {
libc::mmap(
std::ptr::null_mut(),
buff_metadata.aux_size as usize,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd.as_raw_fd(),
buff_metadata.aux_offset as i64,
)
};
if mmap_addr == libc::MAP_FAILED {
Err("IntelPT: Failed to mmap perf aux buffer")
} else {
Ok(mmap_addr)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ use crate::{
CPU,
};

mod intel_pt;
use intel_pt::*;

pub(super) extern "C" fn qemu_cleanup_atexit() {
unsafe {
qemu_cleanup();
Expand Down
Loading

0 comments on commit 615b87b

Please sign in to comment.