Skip to content

Commit d1cf304

Browse files
committed
qemu intel PT: improve VM tracing
- Better filter out the VMX root traces during decoding thanks to the new `resync` API in libipt (bumped to 0.4.0). - Extract the Decoder from the linux intel_pt file, since the file/struct was getting too big and the decoder will eventually be compatible with windows. - PT tracing is now enabled manually by fuzzers to have more precise control, instead of beeing always on in vm operations. - Add KVM dirty tracing option to qemu config and raw string options
1 parent 4403cd0 commit d1cf304

File tree

6 files changed

+303
-256
lines changed

6 files changed

+303
-256
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ fastbloom = { version = "0.12.0", default-features = false }
106106
hashbrown = { version = "0.14.5", default-features = false } # A faster hashmap, nostd compatible
107107
just = "=1.40.0"
108108
libc = "0.2.159" # For (*nix) libc
109-
libipt = "0.3.0"
109+
libipt = { version = "0.4.0", features = ["libipt_master"] }
110110
log = "0.4.22"
111111
meminterval = "0.4.1"
112112
mimalloc = { version = "0.1.43", default-features = false }

crates/libafl_intelpt/src/decoder.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
use crate::error_from_pt_error;
2+
use core::fmt::Debug;
3+
use libafl_bolts::{Error, hash_64_fast};
4+
use libipt::block::BlockDecoder;
5+
use libipt::enc_dec_builder::EncoderDecoderBuilder;
6+
use libipt::error::PtErrorCode;
7+
use libipt::event::EventType;
8+
use libipt::image::Image;
9+
use libipt::status::Status;
10+
use num_traits::SaturatingAdd;
11+
12+
#[derive(Debug)]
13+
pub(crate) struct Decoder<'a, T> {
14+
decoder: BlockDecoder<'a>,
15+
status: Status,
16+
previous_block_end_ip: u64,
17+
vmx_non_root: Option<bool>,
18+
exclude_hv: bool,
19+
trace_skip: u64,
20+
map_ptr: *mut T,
21+
map_len: usize,
22+
}
23+
24+
impl<'a, T> Decoder<'a, T>
25+
where
26+
T: SaturatingAdd + From<u8> + Debug,
27+
{
28+
#[allow(clippy::too_many_arguments)]
29+
pub(crate) fn new(
30+
decoder_builder: EncoderDecoderBuilder<BlockDecoder<'static>>,
31+
exclude_hv: bool,
32+
image: &'a mut Image,
33+
trace_ptr: *mut u8,
34+
trace_len: usize,
35+
trace_skip: u64,
36+
map_ptr: *mut T,
37+
map_len: usize,
38+
) -> Result<Self, Error> {
39+
let builder = unsafe { decoder_builder.buffer_from_raw(trace_ptr, trace_len) };
40+
41+
let mut decoder = builder.build().map_err(error_from_pt_error)?;
42+
decoder
43+
.set_image(Some(image))
44+
.map_err(error_from_pt_error)?;
45+
let status = Status::empty();
46+
47+
Ok(Self {
48+
decoder,
49+
status,
50+
previous_block_end_ip: 0,
51+
vmx_non_root: None,
52+
exclude_hv,
53+
trace_skip,
54+
map_ptr,
55+
map_len,
56+
})
57+
}
58+
59+
pub(crate) fn decode_traces_into_map(mut self) -> Result<u64, Error> {
60+
'sync: loop {
61+
match self.decoder.sync_forward() {
62+
Ok(status) => {
63+
self.status = status;
64+
match self.decode_and_resync_loop() {
65+
Ok(()) if self.status.eos() => break 'sync,
66+
Ok(()) => (),
67+
Err(e) => log::warn!("{e:?}"),
68+
}
69+
}
70+
Err(e) => {
71+
if e.code() != PtErrorCode::Eos {
72+
log::warn!("PT error in sync forward {e:?}");
73+
}
74+
break 'sync;
75+
}
76+
}
77+
}
78+
79+
self.decoder.sync_backward().map_err(error_from_pt_error)?;
80+
self.decoder.sync_offset().map_err(error_from_pt_error)
81+
}
82+
83+
fn decode_and_resync_loop(&mut self) -> Result<(), Error> {
84+
loop {
85+
match self.decode_blocks_loop() {
86+
Ok(()) if self.status.eos() => return Ok(()),
87+
Ok(()) | Err(_) => (),
88+
}
89+
90+
match self.resync_loop() {
91+
Ok(()) if self.status.eos() => return Ok(()),
92+
Ok(()) => (),
93+
Err(e) => return Err(e),
94+
}
95+
}
96+
}
97+
98+
fn resync_loop(&mut self) -> Result<(), Error>
99+
where
100+
T: SaturatingAdd + From<u8> + Debug,
101+
{
102+
loop {
103+
match self.decoder.resync() {
104+
Ok(s) => {
105+
self.status = s;
106+
if self.status.eos() {
107+
return Ok(());
108+
}
109+
110+
// If exclude_hv is set and we are in root VMX operation, continue resyncing
111+
if self.exclude_hv || matches!(self.vmx_non_root, Some(true)) {
112+
return Ok(());
113+
}
114+
}
115+
Err(e) => match e.code() {
116+
PtErrorCode::Eos => return Ok(()),
117+
PtErrorCode::EventIgnored => self.handle_event()?,
118+
_ => return Err(Error::illegal_state(format!("PT error in resync {e:?}"))),
119+
},
120+
}
121+
}
122+
}
123+
124+
fn decode_blocks_loop(&mut self) -> Result<(), Error>
125+
where
126+
T: SaturatingAdd + From<u8> + Debug,
127+
{
128+
#[cfg(debug_assertions)]
129+
let mut trace_entry_iters: (u64, u64) = (0, 0);
130+
131+
loop {
132+
#[cfg(debug_assertions)]
133+
{
134+
let offset = self.decoder.offset().map_err(error_from_pt_error)?;
135+
if trace_entry_iters.0 == offset {
136+
trace_entry_iters.1 += 1;
137+
if trace_entry_iters.1 > 1000 {
138+
return Err(Error::illegal_state(format!(
139+
"PT Decoder got stuck at trace offset {offset:x}.\
140+
Make sure the decoder Image has the right content and offsets.",
141+
)));
142+
}
143+
} else {
144+
trace_entry_iters = (offset, 0);
145+
}
146+
}
147+
148+
while self.status.event_pending() {
149+
self.handle_event()?;
150+
}
151+
152+
// If exclude_hv is set and we are in root VMX operation, bail out
153+
if self.exclude_hv && matches!(self.vmx_non_root, Some(false)) {
154+
return Ok(());
155+
}
156+
157+
match self.decoder.decode_next() {
158+
Ok((b, s)) => {
159+
self.status = s;
160+
let offset = self.decoder.offset().map_err(error_from_pt_error)?;
161+
if b.ninsn() > 0 && self.trace_skip < offset {
162+
let id = hash_64_fast(self.previous_block_end_ip) ^ hash_64_fast(b.ip());
163+
// SAFETY: the index is < map_len since the modulo operation is applied
164+
unsafe {
165+
let map_loc = self.map_ptr.add(id as usize % self.map_len);
166+
*map_loc = (*map_loc).saturating_add(&1u8.into());
167+
}
168+
self.previous_block_end_ip = b.end_ip();
169+
}
170+
171+
if self.status.eos() {
172+
return Ok(());
173+
}
174+
}
175+
Err(e) => {
176+
if e.code() != PtErrorCode::Eos {
177+
let offset = self.decoder.offset().map_err(error_from_pt_error)?;
178+
log::info!(
179+
"PT error in block next {e:?} trace offset {offset:x} last decoded block end {:x}",
180+
self.previous_block_end_ip
181+
);
182+
}
183+
return Err(error_from_pt_error(e));
184+
}
185+
}
186+
}
187+
}
188+
189+
fn handle_event(&mut self) -> Result<(), Error> {
190+
match self.decoder.event() {
191+
Ok((event, s)) => {
192+
self.status = s;
193+
match event.event_type() {
194+
EventType::Paging(p) => self.vmx_non_root = Some(p.non_root()),
195+
EventType::AsyncPaging(p) => self.vmx_non_root = Some(p.non_root()),
196+
_ => (),
197+
}
198+
Ok(())
199+
}
200+
Err(e) => Err(Error::illegal_state(format!("PT error in event {e:?}"))),
201+
}
202+
}
203+
}

crates/libafl_intelpt/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ use std::fs;
2020

2121
use raw_cpuid::CpuId;
2222

23+
#[cfg(target_os = "linux")]
24+
// This should be windows compatible. It's behind linux check just to avoid unused errors etc.
25+
mod decoder;
2326
#[cfg(target_os = "linux")]
2427
mod linux;
2528
#[cfg(target_os = "linux")]

0 commit comments

Comments
 (0)