Skip to content

Commit a7d735c

Browse files
authored
Add IntelPT tracing module to libafl_qemu systemmode with KVM (#2774)
* intelpt module
1 parent ec24513 commit a7d735c

File tree

7 files changed

+165
-6
lines changed

7 files changed

+165
-6
lines changed

.github/workflows/ubuntu-prepare/action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ runs:
55
steps:
66
- name: Install and cache deps
77
shell: bash
8-
run: sudo apt-get update && sudo apt-get install -y curl lsb-release wget software-properties-common gnupg ninja-build shellcheck pax-utils nasm libsqlite3-dev libc6-dev libgtk-3-dev gcc g++ gcc-arm-none-eabi gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libslirp-dev libz3-dev build-essential cmake
8+
run: sudo apt-get update && sudo apt-get install -y curl lsb-release wget software-properties-common gnupg ninja-build shellcheck pax-utils nasm libsqlite3-dev libc6-dev libgtk-3-dev gcc g++ gcc-arm-none-eabi gcc-arm-linux-gnueabi g++-arm-linux-gnueabi libslirp-dev libz3-dev build-essential
99
- uses: dtolnay/rust-toolchain@stable
1010
- name: install just
1111
uses: extractions/setup-just@v2

libafl_qemu/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ slirp = [
8888
"libafl_qemu_sys/slirp",
8989
] # build qemu with host libslirp (for user networking)
9090

91+
intel_pt = ["systemmode", "x86_64", "dep:libafl_intelpt"]
92+
intel_pt_export_raw = ["intel_pt", "libafl_intelpt/export_raw"]
93+
9194
# Requires the binary's build.rs to call `build_libafl_qemu`
9295
shared = ["libafl_qemu_sys/shared"]
9396

@@ -101,6 +104,7 @@ libafl_bolts = { workspace = true, features = ["std", "derive"] }
101104
libafl_targets = { workspace = true, default-features = true }
102105
libafl_qemu_sys = { workspace = true }
103106
libafl_derive = { workspace = true, default-features = true }
107+
libafl_intelpt = { workspace = true, default-features = true, optional = true }
104108

105109
serde = { workspace = true, default-features = false, features = [
106110
"alloc",

libafl_qemu/libafl_qemu_build/src/build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use core::str::FromStr;
21
use std::{
32
env, fs,
43
path::{Path, PathBuf},
54
process::Command,
5+
str::FromStr,
66
};
77

88
use which::which;

libafl_qemu/src/modules/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ pub use usermode::*;
1717

1818
#[cfg(feature = "systemmode")]
1919
pub mod systemmode;
20-
#[cfg(feature = "systemmode")]
21-
#[expect(unused_imports)]
20+
#[cfg(all(feature = "systemmode", feature = "intel_pt"))]
2221
pub use systemmode::*;
2322

2423
pub mod edges;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use std::{
2+
fmt::Debug,
3+
ops::{Range, RangeInclusive},
4+
};
5+
6+
use libafl::{HasMetadata, observers::ObserversTuple};
7+
pub use libafl_intelpt::SectionInfo;
8+
use libafl_intelpt::{Image, IntelPT, IntelPTBuilder};
9+
use libafl_qemu_sys::{CPUArchStatePtr, GuestAddr};
10+
use num_traits::SaturatingAdd;
11+
use typed_builder::TypedBuilder;
12+
13+
use crate::{
14+
EmulatorModules, NewThreadHook, Qemu, QemuParams,
15+
modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, ExitKind},
16+
};
17+
18+
#[derive(Debug, TypedBuilder)]
19+
pub struct IntelPTModule<T = u8> {
20+
#[builder(setter(skip), default)]
21+
pt: Option<IntelPT>,
22+
#[builder(default = IntelPTModule::default_pt_builder())]
23+
intel_pt_builder: IntelPTBuilder,
24+
#[builder(setter(transform = |sections: &[SectionInfo]| {
25+
let mut i = Image::new(None).unwrap();
26+
i.add_files_cached(sections, None).unwrap();
27+
i
28+
}))]
29+
image: Image,
30+
map_ptr: *mut T,
31+
map_len: usize,
32+
}
33+
34+
impl IntelPTModule {
35+
pub fn default_pt_builder() -> IntelPTBuilder {
36+
IntelPT::builder().exclude_kernel(false)
37+
}
38+
}
39+
40+
impl<I, S, T> EmulatorModule<I, S> for IntelPTModule<T>
41+
where
42+
I: Unpin,
43+
S: Unpin + HasMetadata,
44+
T: SaturatingAdd + From<u8> + Debug + 'static,
45+
{
46+
fn pre_qemu_init<ET>(
47+
&mut self,
48+
emulator_modules: &mut EmulatorModules<ET, I, S>,
49+
_qemu_params: &mut QemuParams,
50+
) where
51+
ET: EmulatorModuleTuple<I, S>,
52+
{
53+
emulator_modules
54+
.thread_creation(NewThreadHook::Function(intel_pt_new_thread::<ET, I, S, T>))
55+
.unwrap();
56+
// fixme: consider implementing a clean emulator_modules.thread_teradown
57+
}
58+
59+
fn pre_exec<ET>(
60+
&mut self,
61+
_qemu: Qemu,
62+
_emulator_modules: &mut EmulatorModules<ET, I, S>,
63+
_state: &mut S,
64+
_input: &I,
65+
) where
66+
ET: EmulatorModuleTuple<I, S>,
67+
{
68+
let pt = self.pt.as_mut().expect("Intel PT module not initialized.");
69+
pt.enable_tracing().unwrap();
70+
}
71+
72+
fn post_exec<OT, ET>(
73+
&mut self,
74+
_qemu: Qemu,
75+
_emulator_modules: &mut EmulatorModules<ET, I, S>,
76+
_state: &mut S,
77+
_input: &I,
78+
_observers: &mut OT,
79+
_exit_kind: &mut ExitKind,
80+
) where
81+
OT: ObserversTuple<I, S>,
82+
ET: EmulatorModuleTuple<I, S>,
83+
{
84+
let pt = self.pt.as_mut().expect("Intel PT module not initialized.");
85+
pt.disable_tracing().unwrap();
86+
87+
let _ = pt
88+
.decode_traces_into_map(&mut self.image, self.map_ptr, self.map_len)
89+
.inspect_err(|e| log::warn!("Intel PT trace decode failed: {e}"));
90+
91+
#[cfg(feature = "intel_pt_export_raw")]
92+
{
93+
let _ = pt
94+
.dump_last_trace_to_file()
95+
.inspect_err(|e| log::warn!("Intel PT trace save to file failed: {e}"));
96+
}
97+
}
98+
}
99+
100+
impl<T> AddressFilter for IntelPTModule<T>
101+
where
102+
T: Debug + 'static,
103+
{
104+
fn register(&mut self, address_range: &Range<GuestAddr>) {
105+
let pt = self.pt.as_mut().unwrap();
106+
let mut filters = pt.ip_filters();
107+
let range_inclusive =
108+
RangeInclusive::new(address_range.start as usize, address_range.end as usize - 1);
109+
filters.push(range_inclusive);
110+
pt.set_ip_filters(&filters).unwrap()
111+
}
112+
113+
fn allowed(&self, address: &GuestAddr) -> bool {
114+
let pt = self.pt.as_ref().unwrap();
115+
for f in pt.ip_filters() {
116+
if f.contains(&(*address as usize)) {
117+
return true;
118+
}
119+
}
120+
false
121+
}
122+
}
123+
124+
pub fn intel_pt_new_thread<ET, I, S, T>(
125+
emulator_modules: &mut EmulatorModules<ET, I, S>,
126+
_state: Option<&mut S>,
127+
_env: CPUArchStatePtr,
128+
tid: u32,
129+
) -> bool
130+
where
131+
I: Unpin,
132+
S: HasMetadata + Unpin,
133+
ET: EmulatorModuleTuple<I, S>,
134+
T: Debug + 'static,
135+
{
136+
let intel_pt_module = emulator_modules
137+
.modules_mut()
138+
.match_first_type_mut::<IntelPTModule<T>>()
139+
.unwrap();
140+
141+
if intel_pt_module.pt.is_some() {
142+
panic!("Intel PT module already initialized, only single core VMs are supported ATM.");
143+
}
144+
145+
let pt = intel_pt_module
146+
.intel_pt_builder
147+
.clone()
148+
.pid(Some(tid as i32))
149+
.build()
150+
.unwrap();
151+
152+
intel_pt_module.pt = Some(pt);
153+
154+
true
155+
}
+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
1+
#[cfg(feature = "intel_pt")]
2+
pub mod intel_pt;

scripts/parallellize_cargo_check.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"--exclude-features=prelude,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode,whole_archive "
2424
"--no-dev-deps --exclude libafl_libfuzzer --exclude libafl_qemu --exclude libafl_qemu_sys --print-command-list;"
2525
"DOCS_RS=1 cargo hack check -p libafl_qemu -p libafl_qemu_sys --each-feature --clean-per-run "
26-
"--exclude-features=prelude,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode,whole_archive,slirp "
26+
"--exclude-features=prelude,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode,whole_archive,slirp,intel_pt,intel_pt_export_raw "
2727
"--no-dev-deps --features usermode --print-command-list"
2828
)
2929

0 commit comments

Comments
 (0)