From 20898ae9e462ee9786b556a7c513ca047146d94f Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 17 Apr 2026 19:22:27 +0200 Subject: [PATCH 01/28] Start working on windows pt --- crates/libafl_intelpt/Cargo.toml | 9 +++++++++ crates/libafl_intelpt/src/lib.rs | 19 ++++++++++++++++++- crates/libafl_intelpt/src/windows.rs | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 crates/libafl_intelpt/src/windows.rs diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index 2d415477ba1..c40c199e5ac 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -41,6 +41,15 @@ perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] ptcov = { version = "0.1.0", features = ["retc"] } +windows-sys = { version = "0.61.2", features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_Diagnostics_Debug", + "Win32_System_IO", + "Win32_System_Threading", + "Win32_Storage_FileSystem", + "Win32_Security", +] } [lints] workspace = true diff --git a/crates/libafl_intelpt/src/lib.rs b/crates/libafl_intelpt/src/lib.rs index f587aa2e959..053afa131cd 100644 --- a/crates/libafl_intelpt/src/lib.rs +++ b/crates/libafl_intelpt/src/lib.rs @@ -24,6 +24,11 @@ mod linux; #[cfg(target_os = "linux")] pub use linux::*; +#[cfg(target_os = "windows")] +mod windows; +#[cfg(target_os = "windows")] +pub use windows::*; + /// Size of a memory page pub const PAGE_SIZE: usize = 4096; @@ -41,6 +46,7 @@ pub fn availability() -> Result<(), String> { let mut reasons = Vec::new(); let cpuid = CpuId::new(); + if let Some(vendor) = cpuid.get_vendor_info() { if vendor.as_str() != "GenuineIntel" && vendor.as_str() != "GenuineIotel" { reasons.push("Only Intel CPUs are supported".to_owned()); @@ -57,11 +63,22 @@ pub fn availability() -> Result<(), String> { reasons.push("Failed to read CPU Extended Features".to_owned()); } + if let Some(pti) = cpuid.get_processor_trace_info() { + if pti.configurable_address_ranges() == 0 { + reasons.push( + "The current CPU does not support Intel PT filtering: no address filters available" + .to_owned(), + ); + } + } else { + reasons.push("Failed to read CPU Processor Trace Info".to_owned()); + } + #[cfg(target_os = "linux")] if let Err(r) = availability_in_linux() { reasons.push(r); } - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "linux", target_os = "windows")))] reasons.push("Only linux hosts are supported at the moment".to_owned()); if reasons.is_empty() { diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs new file mode 100644 index 00000000000..e46c7d5d0ce --- /dev/null +++ b/crates/libafl_intelpt/src/windows.rs @@ -0,0 +1,20 @@ +pub use ptcov::PtCoverageDecoder; +use raw_cpuid::CpuId; + +/// Number of address filters available on the running CPU +pub fn nr_addr_filters() -> Result { + let cpuid = CpuId::new(); + cpuid + .get_processor_trace_info() + .ok_or("Failed to read CPU Processor Trace Info") + .map(|pti| pti.configurable_address_ranges()) +} + +// /// Intel Processor Trace (PT) +// #[derive(Debug)] +// pub struct IntelPT<'a> { +// previous_decode_head: u64, +// ptcov_decoder: PtCoverageDecoder<'a>, +// #[cfg(feature = "export_raw")] +// last_decode_trace: Vec, +// } From 0b3d3a9410a44707a3760e2cdc4f010c42c466fd Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Mon, 20 Apr 2026 15:05:42 +0200 Subject: [PATCH 02/28] get ipt handle --- crates/libafl_intelpt/src/lib.rs | 4 ++ crates/libafl_intelpt/src/windows.rs | 77 ++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/crates/libafl_intelpt/src/lib.rs b/crates/libafl_intelpt/src/lib.rs index 053afa131cd..5699df8a413 100644 --- a/crates/libafl_intelpt/src/lib.rs +++ b/crates/libafl_intelpt/src/lib.rs @@ -78,6 +78,10 @@ pub fn availability() -> Result<(), String> { if let Err(r) = availability_in_linux() { reasons.push(r); } + #[cfg(target_os = "windows")] + if let Err(r) = availability_in_windows() { + reasons.push(r); + } #[cfg(not(any(target_os = "linux", target_os = "windows")))] reasons.push("Only linux hosts are supported at the moment".to_owned()); diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index e46c7d5d0ce..cd429810f54 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -1,8 +1,50 @@ +#![allow(dead_code)] // todo remove + +use alloc::vec::Vec; +use core::ptr; +use std::{io, prelude::rust_2015::String}; + pub use ptcov::PtCoverageDecoder; use raw_cpuid::CpuId; +use windows_sys::Win32::{ + Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + Storage::FileSystem::{ + CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, + FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, + }, +}; + +/// Intel Processor Trace (PT) +#[derive(Debug)] +pub struct IntelPT<'a> { + ipt_handle: HANDLE, + + previous_decode_head: u64, + ptcov_decoder: PtCoverageDecoder<'a>, + #[cfg(feature = "export_raw")] + last_decode_trace: Vec, +} + +pub(crate) fn availability_in_windows() -> Result<(), String> { + let mut reasons = Vec::new(); + + if let Err(e) = get_ipt_handle() { + let err = format!( + "Failed to open IPT device: {e}; \n\ + Make sure the ipt service is running with `sc start ipt` from an admin shell." + ); + reasons.push(err); + } + + if reasons.is_empty() { + Ok(()) + } else { + Err(reasons.join("; ")) + } +} /// Number of address filters available on the running CPU -pub fn nr_addr_filters() -> Result { +fn nr_addr_filters() -> Result { let cpuid = CpuId::new(); cpuid .get_processor_trace_info() @@ -10,11 +52,28 @@ pub fn nr_addr_filters() -> Result { .map(|pti| pti.configurable_address_ranges()) } -// /// Intel Processor Trace (PT) -// #[derive(Debug)] -// pub struct IntelPT<'a> { -// previous_decode_head: u64, -// ptcov_decoder: PtCoverageDecoder<'a>, -// #[cfg(feature = "export_raw")] -// last_decode_trace: Vec, -// } +fn get_ipt_handle() -> io::Result { + let ipt_path: Vec = "\\??\\IPT\0".encode_utf16().collect(); + + let handle = unsafe { + CreateFileW( + ipt_path.as_ptr(), + FILE_GENERIC_READ, + FILE_SHARE_READ, + ptr::null(), + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_NO_BUFFERING, + ptr::null_mut(), + ) + }; + + if handle == INVALID_HANDLE_VALUE { + let err = io::Error::last_os_error(); + return Err(err); + } + + Ok(handle) +} + +#[cfg(test)] +mod tests {} From dcc2b1f8cf62abf796330cd5c0f0269bb312da2e Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Mon, 20 Apr 2026 19:28:28 +0200 Subject: [PATCH 03/28] WIP windows --- crates/libafl_intelpt/src/lib.rs | 3 + crates/libafl_intelpt/src/linux.rs | 14 +-- crates/libafl_intelpt/src/utils.rs | 14 +++ crates/libafl_intelpt/src/windows.rs | 142 +++++++++++++++++++++++++-- 4 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 crates/libafl_intelpt/src/utils.rs diff --git a/crates/libafl_intelpt/src/lib.rs b/crates/libafl_intelpt/src/lib.rs index 5699df8a413..c3034551f72 100644 --- a/crates/libafl_intelpt/src/lib.rs +++ b/crates/libafl_intelpt/src/lib.rs @@ -29,6 +29,9 @@ mod windows; #[cfg(target_os = "windows")] pub use windows::*; +#[cfg(any(target_os = "linux", target_os = "windows"))] +mod utils; + /// Size of a memory page pub const PAGE_SIZE: usize = 4096; diff --git a/crates/libafl_intelpt/src/linux.rs b/crates/libafl_intelpt/src/linux.rs index b71dd554144..577d4739b9b 100644 --- a/crates/libafl_intelpt/src/linux.rs +++ b/crates/libafl_intelpt/src/linux.rs @@ -36,6 +36,7 @@ use ptcov::{PtCpu, PtCpuVendor}; use raw_cpuid::CpuId; use super::{PAGE_SIZE, availability}; +use crate::utils::current_cpu; const PT_EVENT_PATH: &str = "/sys/bus/event_source/devices/intel_pt"; @@ -107,7 +108,6 @@ impl<'a> IntelPT<'a> { /// Set filters based on Instruction Pointer (IP) /// /// Only instructions in `filters` ranges will be traced. - /// NOTE: only filters of type `AddrFilterType::FILTER` are supported. fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> Result<(), Error> { let str_filter = filters .iter() @@ -761,18 +761,6 @@ const fn wrap_aux_pointer(ptr: u64, perf_aux_buffer_size: usize) -> u64 { ptr & (perf_aux_buffer_size as u64 - 1) } -fn current_cpu() -> Option { - let cpuid = CpuId::new(); - cpuid.get_feature_info().map(|fi| { - PtCpu::new( - PtCpuVendor::Intel, - fi.family_id().into(), - fi.model_id(), - fi.stepping_id(), - ) - }) -} - #[cfg(test)] mod test { use std::path::PathBuf; diff --git a/crates/libafl_intelpt/src/utils.rs b/crates/libafl_intelpt/src/utils.rs new file mode 100644 index 00000000000..affccd546c2 --- /dev/null +++ b/crates/libafl_intelpt/src/utils.rs @@ -0,0 +1,14 @@ +use ptcov::{PtCpu, PtCpuVendor}; +use raw_cpuid::CpuId; + +pub fn current_cpu() -> Option { + let cpuid = CpuId::new(); + cpuid.get_feature_info().map(|fi| { + PtCpu::new( + PtCpuVendor::Intel, + fi.family_id().into(), + fi.model_id(), + fi.stepping_id(), + ) + }) +} diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index cd429810f54..17deaa8a544 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -1,34 +1,153 @@ #![allow(dead_code)] // todo remove +#![allow(unused_imports)] // todo remove use alloc::vec::Vec; use core::ptr; use std::{io, prelude::rust_2015::String}; - +use std::ops::RangeInclusive; pub use ptcov::PtCoverageDecoder; +use ptcov::{PtCoverageDecoderBuilder, PtImage}; use raw_cpuid::CpuId; use windows_sys::Win32::{ - Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, }, }; +use libafl_bolts::Error; +use crate::utils::current_cpu; + +#[derive(Debug)] +#[repr(u32)] +pub enum IptInputType { + GetTraceVersion = 0, + GetProcessTraceSize, + GetProcessTrace, + StartCoreTracing, + RegisterExtendedImageForTracing, + StartProcessTrace, + StopProcessTrace, + PauseThreadTrace, + ResumeThreadTrace, + QueryProcessTrace, + QueryCoreTrace, + StopTraceOnEachCore = 12, + ConfigureThreadAddressFilterRange, + QueryThreadAddressFilterRange, + QueryThreadTraceStopRangeEntered, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct ConfigureThreadAddressFilterRange { + thread_handle: u64, + range_index: u32, + range_config: u32, + start_address: u64, + end_address: u64, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct IptBufferVersion { + buffer_major_version: u32, + buffer_minor_version: u32, +} + +#[repr(C)] +union IptInputUnion { + // get_trace_size: GetProcessTraceSize, + // get_trace: GetProcessTrace, + // start_trace: StartProcessTrace, + // stop_trace: StopProcessTrace, + configure_thread_address_filter_range: ConfigureThreadAddressFilterRange, + // query_filter: QueryThreadFilter, + // pause_resume_thread: PauseResumeThreadTrace, + _pad: [u8; 32], +} + +#[repr(C)] +pub struct IptInputBuffer { + version: IptBufferVersion, + input_type: IptInputType, + _padding: u32, + u: IptInputUnion, +} /// Intel Processor Trace (PT) #[derive(Debug)] pub struct IntelPT<'a> { - ipt_handle: HANDLE, + ipt_handle: PtHandle, - previous_decode_head: u64, + // previous_decode_head: u64, ptcov_decoder: PtCoverageDecoder<'a>, #[cfg(feature = "export_raw")] last_decode_trace: Vec, } +impl<'a> IntelPT<'a> { + /// Create a default builder + /// + /// Checkout [`IntelPTBuilder::default()`] for more details + #[must_use] + pub fn builder() -> IntelPTBuilder<'a> { + IntelPTBuilder::default() + } + + // /// Set filters based on Instruction Pointer (IP) + // /// + // /// Only instructions in `filters` ranges will be traced. + // fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> Result<(), Error> { + // Ok(()) + // } +} + +#[derive(Debug, Default)] +pub struct IntelPTBuilder<'a> { + images: &'a [PtImage<'a>], +} + +impl<'a> IntelPTBuilder<'a> { + pub fn build(self) -> io::Result> { + let ipt_handle = open_ipt_handle()?; + + let ptcov_decoder = PtCoverageDecoderBuilder::new() + .cpu(current_cpu()) + .images(self.images) + .build(); + + Ok(IntelPT { + ipt_handle, + ptcov_decoder, + }) + } + + #[must_use] + pub fn images(mut self, images: &'a [PtImage<'_>]) -> Self { + self.images = images; + self + } +} + +#[derive(Debug)] +struct PtHandle { + inner: HANDLE, +} + +impl Drop for PtHandle { + fn drop(&mut self) { + if unsafe { CloseHandle(self.inner) } == 0 { + let err = io::Error::last_os_error(); + panic!("Failed to close ipt handle: {}", err); + } + } +} + pub(crate) fn availability_in_windows() -> Result<(), String> { let mut reasons = Vec::new(); - if let Err(e) = get_ipt_handle() { + if let Err(e) = open_ipt_handle() { let err = format!( "Failed to open IPT device: {e}; \n\ Make sure the ipt service is running with `sc start ipt` from an admin shell." @@ -52,7 +171,7 @@ fn nr_addr_filters() -> Result { .map(|pti| pti.configurable_address_ranges()) } -fn get_ipt_handle() -> io::Result { +fn open_ipt_handle() -> io::Result { let ipt_path: Vec = "\\??\\IPT\0".encode_utf16().collect(); let handle = unsafe { @@ -72,8 +191,15 @@ fn get_ipt_handle() -> io::Result { return Err(err); } - Ok(handle) + Ok(PtHandle { inner: handle }) } #[cfg(test)] -mod tests {} +mod tests { + use crate::*; + + #[test] + fn ipt_input_buffer_size() { + assert_eq!(size_of::(), 0x30); + } +} From 5b2adfae83200dc73e22f2122cfba438c2bf4eb1 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 24 Apr 2026 18:24:09 +0200 Subject: [PATCH 04/28] WIP windows --- crates/libafl_intelpt/Cargo.toml | 16 ++- crates/libafl_intelpt/src/windows.rs | 199 +++++++++++++++++++++------ 2 files changed, 164 insertions(+), 51 deletions(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index c40c199e5ac..73437ac9336 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -41,15 +41,19 @@ perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] ptcov = { version = "0.1.0", features = ["retc"] } -windows-sys = { version = "0.61.2", features = [ - "Win32_Foundation", - "Win32_System_Diagnostics_ToolHelp", - "Win32_System_Diagnostics_Debug", +windows = { workspace = true, features = [ "Win32_System_IO", - "Win32_System_Threading", "Win32_Storage_FileSystem", - "Win32_Security", ] } +#windows-sys = { version = "0.61.2", features = [ +# "Win32_Foundation", +# "Win32_System_Diagnostics_ToolHelp", +# "Win32_System_Diagnostics_Debug", +# "Win32_System_IO", +# "Win32_System_Threading", +# "Win32_Storage_FileSystem", +# "Win32_Security", +#] } [lints] workspace = true diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 17deaa8a544..596527c6c99 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -1,26 +1,38 @@ #![allow(dead_code)] // todo remove #![allow(unused_imports)] // todo remove -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::ptr; -use std::{io, prelude::rust_2015::String}; -use std::ops::RangeInclusive; +use std::{io, mem::MaybeUninit, ops::RangeInclusive}; + pub use ptcov::PtCoverageDecoder; use ptcov::{PtCoverageDecoderBuilder, PtImage}; use raw_cpuid::CpuId; -use windows_sys::Win32::{ - Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}, - Storage::FileSystem::{ - CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, - FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, +use windows::{ + Win32::{ + Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}, + Storage::FileSystem::{ + CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, + FILE_GENERIC_READ, FILE_SHARE_MODE, FILE_SHARE_READ, OPEN_EXISTING, + }, + System::{ + IO::DeviceIoControl, + Threading::{GetCurrentThreadId, OpenThread, THREAD_GET_CONTEXT}, + }, }, + core::{HSTRING, w}, }; -use libafl_bolts::Error; + use crate::utils::current_cpu; +#[repr(u32)] +enum IptIoctl { + Request = 0x220004, +} + #[derive(Debug)] #[repr(u32)] -pub enum IptInputType { +enum IptInputType { GetTraceVersion = 0, GetProcessTraceSize, GetProcessTrace, @@ -38,12 +50,20 @@ pub enum IptInputType { QueryThreadTraceStopRangeEntered, } +#[repr(u32)] +#[derive(Debug, Clone, Copy)] +enum IptFilterRangeSettings { + IptFilterRangeDisable = 0, + IptFilterRangeIp = 1, + IptFilterRangeTraceStop = 2, +} + #[repr(C)] #[derive(Debug, Clone, Copy)] struct ConfigureThreadAddressFilterRange { thread_handle: u64, range_index: u32, - range_config: u32, + range_config: IptFilterRangeSettings, start_address: u64, end_address: u64, } @@ -51,12 +71,13 @@ struct ConfigureThreadAddressFilterRange { #[repr(C)] #[derive(Debug, Clone, Copy)] struct IptBufferVersion { - buffer_major_version: u32, - buffer_minor_version: u32, + major: u32, + minor: u32, } +const IPT_BUFFER_VERSION: IptBufferVersion = IptBufferVersion { major: 1, minor: 0 }; #[repr(C)] -union IptInputUnion { +union IptInputPayload { // get_trace_size: GetProcessTraceSize, // get_trace: GetProcessTrace, // start_trace: StartProcessTrace, @@ -67,19 +88,48 @@ union IptInputUnion { _pad: [u8; 32], } +impl From for IptInputPayload { + fn from(value: ConfigureThreadAddressFilterRange) -> Self { + Self { + configure_thread_address_filter_range: value, + } + } +} + #[repr(C)] -pub struct IptInputBuffer { +struct IptInputBuffer { version: IptBufferVersion, input_type: IptInputType, _padding: u32, - u: IptInputUnion, + payload: IptInputPayload, +} + +impl IptInputBuffer { + fn new(input_type: IptInputType, payload: IptInputPayload) -> Self { + IptInputBuffer { + version: IPT_BUFFER_VERSION, + input_type, + _padding: 0, + payload, + } + } +} + +#[repr(C)] +union IptOutputBuffer { + // pub get_trace_version: OutGetTraceVersion, + // pub get_trace_size: OutGetTraceSize, + // pub query_filter: OutQueryThreadFilter, + // pub pause_trace: OutPauseResumeTrace, + // pub resume_trace: OutPauseResumeTrace, + _pad: [u8; 24], } /// Intel Processor Trace (PT) #[derive(Debug)] pub struct IntelPT<'a> { ipt_handle: PtHandle, - + target_thread_handle: PtHandle, // previous_decode_head: u64, ptcov_decoder: PtCoverageDecoder<'a>, #[cfg(feature = "export_raw")] @@ -95,32 +145,98 @@ impl<'a> IntelPT<'a> { IntelPTBuilder::default() } - // /// Set filters based on Instruction Pointer (IP) - // /// - // /// Only instructions in `filters` ranges will be traced. - // fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> Result<(), Error> { - // Ok(()) - // } + /// Set filters based on Instruction Pointer (IP) + /// + /// Only instructions in `filters` ranges will be traced. + fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> windows::core::Result<()> { + for (i, filter) in filters.iter().enumerate() { + let ipt_payload = ConfigureThreadAddressFilterRange { + thread_handle: 0, // todo + range_index: i.try_into()?, + range_config: IptFilterRangeSettings::IptFilterRangeIp, + start_address: *filter.start(), + end_address: *filter.end(), + }; + let input = IptInputBuffer::new( + IptInputType::ConfigureThreadAddressFilterRange, + ipt_payload.into(), + ); + + let mut out = MaybeUninit::::uninit(); + let mut out_size = 0; + unsafe { + DeviceIoControl( + self.ipt_handle.inner, + IptIoctl::Request as u32, + Some(&raw const input as *const std::ffi::c_void), + size_of::() as u32, + Some(out.as_mut_ptr() as *mut std::ffi::c_void), + size_of::() as u32, + Some(&mut out_size), + None, + ) + }?; + } + Ok(()) + } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct IntelPTBuilder<'a> { + ip_filters: Vec>, images: &'a [PtImage<'a>], + target_thread_id: u32, +} + +impl Default for IntelPTBuilder<'_> { + fn default() -> Self { + let target_thread_id = unsafe { GetCurrentThreadId() }; + Self { + ip_filters: Vec::new(), + images: &[], + target_thread_id, + } + } } impl<'a> IntelPTBuilder<'a> { - pub fn build(self) -> io::Result> { + pub fn build(self) -> Result, libafl_bolts::Error> { let ipt_handle = open_ipt_handle()?; + let target_thread_handle = + unsafe { OpenThread(THREAD_GET_CONTEXT, false, self.target_thread_id) } + .map(|h| PtHandle { inner: h }) + .map_err(|e| { + libafl_bolts::Error::os_error(e.into(), "Failed to get target thread handle") + })?; + let ptcov_decoder = PtCoverageDecoderBuilder::new() .cpu(current_cpu()) .images(self.images) .build(); - Ok(IntelPT { + let mut intel_pt = IntelPT { ipt_handle, + target_thread_handle, ptcov_decoder, - }) + }; + intel_pt.set_ip_filters(&self.ip_filters).map_err(|e| { + libafl_bolts::Error::os_error(e.into(), "Failed to set IntelPT ip filters") + })?; + Ok(intel_pt) + } + + #[must_use] + pub fn thread_id(mut self, thread_id: u32) -> Self { + self.target_thread_id = thread_id; + self + } + + #[must_use] + /// Set filters based on Instruction Pointer (IP) + pub fn ip_filters(mut self, filters: Vec>) -> Self { + self.ip_filters = filters; + self } #[must_use] @@ -137,9 +253,8 @@ struct PtHandle { impl Drop for PtHandle { fn drop(&mut self) { - if unsafe { CloseHandle(self.inner) } == 0 { - let err = io::Error::last_os_error(); - panic!("Failed to close ipt handle: {}", err); + if let Err(err) = unsafe { CloseHandle(self.inner) } { + panic!("Failed to close handle: {}", err); } } } @@ -171,32 +286,26 @@ fn nr_addr_filters() -> Result { .map(|pti| pti.configurable_address_ranges()) } -fn open_ipt_handle() -> io::Result { - let ipt_path: Vec = "\\??\\IPT\0".encode_utf16().collect(); +fn open_ipt_handle() -> windows::core::Result { + let ipt_path = w!("\\??\\IPT\0"); - let handle = unsafe { + unsafe { CreateFileW( - ipt_path.as_ptr(), - FILE_GENERIC_READ, + ipt_path, + FILE_GENERIC_READ.0, FILE_SHARE_READ, - ptr::null(), + None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_NO_BUFFERING, - ptr::null_mut(), + None, ) - }; - - if handle == INVALID_HANDLE_VALUE { - let err = io::Error::last_os_error(); - return Err(err); } - - Ok(PtHandle { inner: handle }) + .map(|h| PtHandle { inner: h }) } #[cfg(test)] mod tests { - use crate::*; + use super::*; #[test] fn ipt_input_buffer_size() { From cbcfe27a42f0948a41d5285d246f31ee287ac52a Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Sat, 25 Apr 2026 11:29:25 +0200 Subject: [PATCH 05/28] WIP windows integration test --- .../tests/integration_tests_windows.rs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 crates/libafl_intelpt/tests/integration_tests_windows.rs diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs new file mode 100644 index 00000000000..c81c0cd1f24 --- /dev/null +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -0,0 +1,58 @@ +#![cfg(feature = "std")] +#![cfg(target_os = "windows")] + +use std::ffi::c_void; + +use libafl_intelpt::{IntelPT, availability}; +use windows::Win32::{ + Foundation::{CloseHandle, WAIT_OBJECT_0}, + System::Threading::{ + CreateThread, GetThreadId, INFINITE, Sleep, THREAD_CREATION_FLAGS, WaitForSingleObject, + }, +}; + +extern "system" fn worker_main(_lp_parameter: *mut c_void) -> u32 { + unsafe { Sleep(2000) }; + 0 +} + +#[test] +fn intel_pt_trace_thread() { + if let Err(reason) = availability() { + println!("Intel PT is not available, skipping test. Reasons:"); + println!("{reason}"); + return; + } + + let thread_handle = unsafe { + CreateThread( + None, + 0, + Some(worker_main), + Some(std::ptr::null_mut()), + THREAD_CREATION_FLAGS(0), + None, + ) + } + .expect("Failed to create worker thread"); + + let thread_id = unsafe { GetThreadId(thread_handle) }; + assert!( + thread_id != 0, + "Failed to read worker thread ID from parent" + ); + + let _pt = IntelPT::builder() + .thread_id(thread_id) + .images(&[]) + .build() + .expect("Failed to create IntelPT for worker thread"); + + let wait_result = unsafe { WaitForSingleObject(thread_handle, INFINITE) }; + assert_eq!( + wait_result, WAIT_OBJECT_0, + "Worker thread did not terminate cleanly" + ); + + let _ = unsafe { CloseHandle(thread_handle) }; +} From 6b5f6125a7adbdc15534b06c004288b8f1dc13a2 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Thu, 30 Apr 2026 17:34:32 +0200 Subject: [PATCH 06/28] WIP win --- crates/libafl_intelpt/src/windows.rs | 117 +++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 596527c6c99..c2b6952739c 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -5,6 +5,8 @@ use alloc::{string::String, vec::Vec}; use core::ptr; use std::{io, mem::MaybeUninit, ops::RangeInclusive}; +use arbitrary_int::{u3, u4, u36}; +use bitbybit::bitfield; pub use ptcov::PtCoverageDecoder; use ptcov::{PtCoverageDecoderBuilder, PtImage}; use raw_cpuid::CpuId; @@ -17,7 +19,10 @@ use windows::{ }, System::{ IO::DeviceIoControl, - Threading::{GetCurrentThreadId, OpenThread, THREAD_GET_CONTEXT}, + Threading::{ + GetCurrentThreadId, GetProcessIdOfThread, OpenProcess, OpenThread, + PROCESS_QUERY_INFORMATION, THREAD_GET_CONTEXT, THREAD_QUERY_LIMITED_INFORMATION, + }, }, }, core::{HSTRING, w}, @@ -68,6 +73,48 @@ struct ConfigureThreadAddressFilterRange { end_address: u64, } +/// Layout (from winipt): +/// - OptionVersion: bits 0-3 (4 bits) - Must be set to 1 +/// - TimingSettings: bits 4-7 (4 bits) - IPT_TIMING_SETTINGS +/// - MtcFrequency: bits 8-11 (4 bits) - Bits 14:17 in IA32_RTIT_CTL +/// - CycThreshold: bits 12-15 (4 bits) - Bits 19:22 in IA32_RTIT_CTL +/// - TopaPagesPow2: bits 16-19 (4 bits) - Size of buffer as 4KB powers of 2 (4KB->128MB) +/// - MatchSettings: bits 20-22 (3 bits) - IPT_MATCH_SETTINGS +/// - Inherit: bit 23 (1 bit) - Children will be automatically traced +/// - ModeSettings: bits 24-27 (4 bits) - IPT_MODE_SETTINGS +/// - Reserved: bits 28-63 (36 bits) +#[bitfield(u64, default = 0)] +#[derive(Debug)] +pub struct IptOptions { + #[bits(0..=3, rw)] + pub option_version: u4, + #[bits(4..=7, rw)] + pub timing_settings: u4, + #[bits(8..=11, rw)] + pub mtc_frequency: u4, + #[bits(12..=15, rw)] + pub cyc_threshold: u4, + #[bits(16..=19, rw)] + pub topa_pages_pow2: u4, + /// Not relevant when tracing by process handle + #[bits(20..=22, rw)] + pub match_settings: u3, + #[bits(23..=23, rw)] + pub inherit: bool, + #[bits(24..=27, rw)] + pub mode_settings: u4, + #[bits(28..=63)] + reserved: u36, +} +const IPT_OPTION_VERSION: u4 = u4::new(4); + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct StartProcessTrace { + pub process_handle: u64, + pub options: IptOptions, +} + #[repr(C)] #[derive(Debug, Clone, Copy)] struct IptBufferVersion { @@ -80,7 +127,7 @@ const IPT_BUFFER_VERSION: IptBufferVersion = IptBufferVersion { major: 1, minor: union IptInputPayload { // get_trace_size: GetProcessTraceSize, // get_trace: GetProcessTrace, - // start_trace: StartProcessTrace, + start_trace: StartProcessTrace, // stop_trace: StopProcessTrace, configure_thread_address_filter_range: ConfigureThreadAddressFilterRange, // query_filter: QueryThreadFilter, @@ -96,6 +143,12 @@ impl From for IptInputPayload { } } +impl From for IptInputPayload { + fn from(value: StartProcessTrace) -> Self { + Self { start_trace: value } + } +} + #[repr(C)] struct IptInputBuffer { version: IptBufferVersion, @@ -129,6 +182,7 @@ union IptOutputBuffer { #[derive(Debug)] pub struct IntelPT<'a> { ipt_handle: PtHandle, + target_process_handle: PtHandle, target_thread_handle: PtHandle, // previous_decode_head: u64, ptcov_decoder: PtCoverageDecoder<'a>, @@ -179,6 +233,39 @@ impl<'a> IntelPT<'a> { } Ok(()) } + + fn enable_tracing(&mut self) -> windows::core::Result<()> { + let options = IptOptions::builder() + .with_inherit(false) + .with_option_version(IPT_OPTION_VERSION) + .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg + .value; + + let ipt_payload = StartProcessTrace { + process_handle: self.target_process_handle.inner.0 as u64, + options, + }; + + let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); + let mut out = MaybeUninit::::uninit(); + let mut out_size = 0; + unsafe { + DeviceIoControl( + self.ipt_handle.inner, + IptIoctl::Request as u32, + Some(&raw const input as *const std::ffi::c_void), + size_of::() as u32, + Some(out.as_mut_ptr() as *mut std::ffi::c_void), + size_of::() as u32, + Some(&mut out_size), + None, + ) + }?; + + // todo + + Ok(()) + } } #[derive(Debug)] @@ -203,12 +290,25 @@ impl<'a> IntelPTBuilder<'a> { pub fn build(self) -> Result, libafl_bolts::Error> { let ipt_handle = open_ipt_handle()?; - let target_thread_handle = - unsafe { OpenThread(THREAD_GET_CONTEXT, false, self.target_thread_id) } - .map(|h| PtHandle { inner: h }) - .map_err(|e| { - libafl_bolts::Error::os_error(e.into(), "Failed to get target thread handle") - })?; + let target_thread_handle = unsafe { + OpenThread( + THREAD_GET_CONTEXT | THREAD_QUERY_LIMITED_INFORMATION, + false, + self.target_thread_id, + ) + } + .map(|h| PtHandle { inner: h }) + .map_err(|e| { + libafl_bolts::Error::os_error(e.into(), "Failed to get target thread handle") + })?; + + let pid = unsafe { GetProcessIdOfThread(target_thread_handle.inner) }; + + let target_process_handle = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION, false, pid) } + .map(|h| PtHandle { inner: h }) + .map_err(|e| { + libafl_bolts::Error::os_error(e.into(), "Failed to get target process handle") + })?; let ptcov_decoder = PtCoverageDecoderBuilder::new() .cpu(current_cpu()) @@ -217,6 +317,7 @@ impl<'a> IntelPTBuilder<'a> { let mut intel_pt = IntelPT { ipt_handle, + target_process_handle, target_thread_handle, ptcov_decoder, }; From 818210fc7f287a82a4140dd26492f05cc47527a1 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Thu, 7 May 2026 18:08:33 +0200 Subject: [PATCH 07/28] WIP win --- crates/libafl_intelpt/src/windows.rs | 141 ++++++++++++++---- .../tests/integration_tests_windows.rs | 12 +- 2 files changed, 120 insertions(+), 33 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index c2b6952739c..3e4172ca643 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -21,7 +21,8 @@ use windows::{ IO::DeviceIoControl, Threading::{ GetCurrentThreadId, GetProcessIdOfThread, OpenProcess, OpenThread, - PROCESS_QUERY_INFORMATION, THREAD_GET_CONTEXT, THREAD_QUERY_LIMITED_INFORMATION, + PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, THREAD_GET_CONTEXT, + THREAD_QUERY_LIMITED_INFORMATION, }, }, }, @@ -33,6 +34,7 @@ use crate::utils::current_cpu; #[repr(u32)] enum IptIoctl { Request = 0x220004, + ReadTrace = 0x220006, } #[derive(Debug)] @@ -106,13 +108,22 @@ pub struct IptOptions { #[bits(28..=63)] reserved: u36, } -const IPT_OPTION_VERSION: u4 = u4::new(4); +const IPT_OPTION_VERSION: u4 = u4::new(1); +const IPT_TRACE_VERSION: u16 = 1; #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct StartProcessTrace { + process_handle: u64, + options: IptOptions, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct GetProcessTrace { + pub trace_version: u16, + _padding: [u8; 6], pub process_handle: u64, - pub options: IptOptions, } #[repr(C)] @@ -125,8 +136,7 @@ const IPT_BUFFER_VERSION: IptBufferVersion = IptBufferVersion { major: 1, minor: #[repr(C)] union IptInputPayload { - // get_trace_size: GetProcessTraceSize, - // get_trace: GetProcessTrace, + get_trace: GetProcessTrace, start_trace: StartProcessTrace, // stop_trace: StopProcessTrace, configure_thread_address_filter_range: ConfigureThreadAddressFilterRange, @@ -149,6 +159,12 @@ impl From for IptInputPayload { } } +impl From for IptInputPayload { + fn from(value: GetProcessTrace) -> Self { + Self { get_trace: value } + } +} + #[repr(C)] struct IptInputBuffer { version: IptBufferVersion, @@ -169,9 +185,18 @@ impl IptInputBuffer { } #[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct OutGetTraceSize { + pub trace_version: u16, + pub _padding: [u8; 6], + pub trace_size: u64, +} + +#[repr(C)] + union IptOutputBuffer { // pub get_trace_version: OutGetTraceVersion, - // pub get_trace_size: OutGetTraceSize, + pub get_trace_size: OutGetTraceSize, // pub query_filter: OutQueryThreadFilter, // pub pause_trace: OutPauseResumeTrace, // pub resume_trace: OutPauseResumeTrace, @@ -215,26 +240,12 @@ impl<'a> IntelPT<'a> { IptInputType::ConfigureThreadAddressFilterRange, ipt_payload.into(), ); - - let mut out = MaybeUninit::::uninit(); - let mut out_size = 0; - unsafe { - DeviceIoControl( - self.ipt_handle.inner, - IptIoctl::Request as u32, - Some(&raw const input as *const std::ffi::c_void), - size_of::() as u32, - Some(out.as_mut_ptr() as *mut std::ffi::c_void), - size_of::() as u32, - Some(&mut out_size), - None, - ) - }?; + self.send_device_io_request(input)?; } Ok(()) } - fn enable_tracing(&mut self) -> windows::core::Result<()> { + pub fn enable_tracing(&mut self) -> windows::core::Result<()> { let options = IptOptions::builder() .with_inherit(false) .with_option_version(IPT_OPTION_VERSION) @@ -245,10 +256,79 @@ impl<'a> IntelPT<'a> { process_handle: self.target_process_handle.inner.0 as u64, options, }; - let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); + let (_, out_size) = self.send_device_io_request(input)?; + debug_assert_eq!(out_size, 0); + + Ok(()) + } + + // todo: must not be public, or even exist? + pub fn get_raw_trace(&mut self) -> windows::core::Result> { + // todo: this looks pretty racy, size might increas ebetween get_trace_size and the actual + // get trace + let trace_size = self.get_trace_size()?; + let mut trace_buffer = Vec::with_capacity(trace_size as usize); + + let ipt_payload = GetProcessTrace { + trace_version: IPT_TRACE_VERSION, + _padding: [0u8; 6], + process_handle: self.target_process_handle.inner.0 as u64, + }; + let input = IptInputBuffer::new( + IptInputType::GetProcessTrace, + IptInputPayload { + get_trace: ipt_payload.into(), + }, + ); + + let mut out_size = 0; + unsafe { + DeviceIoControl( + self.ipt_handle.inner, + IptIoctl::ReadTrace as u32, + Some(&raw const input as *const std::ffi::c_void), + size_of::() as u32, + Some(trace_buffer.as_mut_ptr() as *mut std::ffi::c_void), + trace_buffer.capacity() as u32, + Some(&mut out_size), + None, + ) + }?; + + assert!((out_size as usize) <= trace_buffer.capacity()); + unsafe{trace_buffer.set_len(out_size as usize);}; + + //todo trace_buffer has a header in it + + Ok(trace_buffer) + } + + fn get_trace_size(&self) -> windows::core::Result { + let ipt_payload = GetProcessTrace { + trace_version: IPT_TRACE_VERSION, + _padding: [0; 6], + process_handle: self.target_process_handle.inner.0 as u64, + }; + let input = IptInputBuffer::new(IptInputType::GetProcessTraceSize, ipt_payload.into()); + let (out, out_size) = self.send_device_io_request(input)?; + + debug_assert_eq!(out_size, size_of::() as u32); + + Ok(unsafe { out.get_trace_size.trace_size }) + } + + // pub fn disable_tracing(&mut self) -> windows::core::Result<()> { + // + // } + + fn send_device_io_request( + &self, + input: IptInputBuffer, + ) -> windows::core::Result<(IptOutputBuffer, u32)> { let mut out = MaybeUninit::::uninit(); let mut out_size = 0; + unsafe { DeviceIoControl( self.ipt_handle.inner, @@ -262,9 +342,7 @@ impl<'a> IntelPT<'a> { ) }?; - // todo - - Ok(()) + Ok((unsafe { out.assume_init() }, out_size)) } } @@ -304,11 +382,12 @@ impl<'a> IntelPTBuilder<'a> { let pid = unsafe { GetProcessIdOfThread(target_thread_handle.inner) }; - let target_process_handle = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION, false, pid) } - .map(|h| PtHandle { inner: h }) - .map_err(|e| { - libafl_bolts::Error::os_error(e.into(), "Failed to get target process handle") - })?; + let target_process_handle = + unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid) } + .map(|h| PtHandle { inner: h }) + .map_err(|e| { + libafl_bolts::Error::os_error(e.into(), "Failed to get target process handle") + })?; let ptcov_decoder = PtCoverageDecoderBuilder::new() .cpu(current_cpu()) diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs index c81c0cd1f24..d4a73c8fd35 100644 --- a/crates/libafl_intelpt/tests/integration_tests_windows.rs +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -1,7 +1,7 @@ #![cfg(feature = "std")] #![cfg(target_os = "windows")] -use std::ffi::c_void; +use std::{ffi::c_void, fs::File, io::Write}; use libafl_intelpt::{IntelPT, availability}; use windows::Win32::{ @@ -42,11 +42,12 @@ fn intel_pt_trace_thread() { "Failed to read worker thread ID from parent" ); - let _pt = IntelPT::builder() + let mut pt = IntelPT::builder() .thread_id(thread_id) .images(&[]) .build() .expect("Failed to create IntelPT for worker thread"); + pt.enable_tracing().expect("Failed to enable tracing"); let wait_result = unsafe { WaitForSingleObject(thread_handle, INFINITE) }; assert_eq!( @@ -54,5 +55,12 @@ fn intel_pt_trace_thread() { "Worker thread did not terminate cleanly" ); + let trace = pt + .get_raw_trace() + .expect("Failed to get raw trace from raw trace"); + + let mut file = File::create("output.bin").unwrap(); + file.write_all(&trace).unwrap(); + let _ = unsafe { CloseHandle(thread_handle) }; } From a9ca1dff6c17479a8be37808fbd666cf676c99bc Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Wed, 13 May 2026 19:29:25 +0200 Subject: [PATCH 08/28] WIP win --- crates/libafl_intelpt/Cargo.toml | 7 +- crates/libafl_intelpt/src/linux.rs | 2 +- crates/libafl_intelpt/src/windows.rs | 183 ++++++++++++++---- .../tests/integration_tests_windows.rs | 108 +++++++---- 4 files changed, 216 insertions(+), 84 deletions(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index 73437ac9336..329204d179d 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -19,10 +19,11 @@ export_raw = [] [dev-dependencies] static_assertions = { workspace = true } +proc-maps = "0.4.0" +env_logger = "0.11.10" [target.'cfg(target_os = "linux" )'.dev-dependencies] nix = { workspace = true } -proc-maps = "0.4.0" [dependencies] arbitrary-int = { workspace = true } @@ -32,7 +33,7 @@ libc = { workspace = true } log = { workspace = true } num_enum = { workspace = true, default-features = false } num-traits = { workspace = true, default-features = false } -ptcov = { version = "0.1.0" } +ptcov = { path = "../../../ptcov" } raw-cpuid = { version = "11.1.0" } [target.'cfg(target_os = "linux" )'.dependencies] @@ -40,7 +41,7 @@ caps = { version = "0.5.5" } perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] -ptcov = { version = "0.1.0", features = ["retc"] } +ptcov = { path = "../../../ptcov", features = ["retc", "log"] } windows = { workspace = true, features = [ "Win32_System_IO", "Win32_Storage_FileSystem", diff --git a/crates/libafl_intelpt/src/linux.rs b/crates/libafl_intelpt/src/linux.rs index 577d4739b9b..e5253e8db24 100644 --- a/crates/libafl_intelpt/src/linux.rs +++ b/crates/libafl_intelpt/src/linux.rs @@ -296,7 +296,7 @@ impl<'a> IntelPT<'a> { } /// Dump the raw trace used in the last decoding to the file - /// /// `./traces/trace_` + /// `./traces/trace_` #[cfg(feature = "export_raw")] pub fn dump_last_trace_to_file(&self) -> Result<(), Error> { use std::{fs, io::Write, path::Path, time}; diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 3e4172ca643..e1de0bb396b 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -1,14 +1,19 @@ #![allow(dead_code)] // todo remove #![allow(unused_imports)] // todo remove +// todo review all the pub use alloc::{string::String, vec::Vec}; -use core::ptr; -use std::{io, mem::MaybeUninit, ops::RangeInclusive}; +use core::{fmt::Debug, ptr}; +use std::{ + io, mem::MaybeUninit, ops::RangeInclusive, prelude::rust_2015::ToString, + ptr::slice_from_raw_parts_mut, +}; use arbitrary_int::{u3, u4, u36}; use bitbybit::bitfield; +use libafl_bolts::Error; pub use ptcov::PtCoverageDecoder; -use ptcov::{PtCoverageDecoderBuilder, PtImage}; +use ptcov::{CoverageEntry, PtCoverageDecoderBuilder, PtDecoderError, PtImage}; use raw_cpuid::CpuId; use windows::{ Win32::{ @@ -20,8 +25,8 @@ use windows::{ System::{ IO::DeviceIoControl, Threading::{ - GetCurrentThreadId, GetProcessIdOfThread, OpenProcess, OpenThread, - PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, THREAD_GET_CONTEXT, + GetCurrentProcessId, GetCurrentThreadId, GetProcessIdOfThread, OpenProcess, + OpenThread, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, THREAD_GET_CONTEXT, THREAD_QUERY_LIMITED_INFORMATION, }, }, @@ -68,7 +73,7 @@ enum IptFilterRangeSettings { #[repr(C)] #[derive(Debug, Clone, Copy)] struct ConfigureThreadAddressFilterRange { - thread_handle: u64, + thread_handle: HANDLE, range_index: u32, range_config: IptFilterRangeSettings, start_address: u64, @@ -193,7 +198,6 @@ pub struct OutGetTraceSize { } #[repr(C)] - union IptOutputBuffer { // pub get_trace_version: OutGetTraceVersion, pub get_trace_size: OutGetTraceSize, @@ -203,12 +207,31 @@ union IptOutputBuffer { _pad: [u8; 24], } +#[repr(C)] +#[derive(Debug)] +struct IptTraceData { + trace_version: u16, + valid_trace: u16, + trace_size: u32, +} + +#[repr(C, packed(4))] +#[derive(Debug)] +struct IptTraceHeader { + thread_id: u64, + timing_settings: u32, + mtc_frequency: u32, + frequency_to_tsc_ratio: u32, + ring_buffer_offset: u32, + trace_size: u32, +} + /// Intel Processor Trace (PT) #[derive(Debug)] pub struct IntelPT<'a> { ipt_handle: PtHandle, target_process_handle: PtHandle, - target_thread_handle: PtHandle, + tid: Option, // previous_decode_head: u64, ptcov_decoder: PtCoverageDecoder<'a>, #[cfg(feature = "export_raw")] @@ -224,13 +247,23 @@ impl<'a> IntelPT<'a> { IntelPTBuilder::default() } + pub fn set_tid(&mut self, tid: Option) { + self.tid = tid; + } + /// Set filters based on Instruction Pointer (IP) /// /// Only instructions in `filters` ranges will be traced. - fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> windows::core::Result<()> { + pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> windows::core::Result<()> { + let thread_handle = if let Some(tid) = self.tid { + unsafe { OpenThread(THREAD_GET_CONTEXT, false, tid)? } + } else { + INVALID_HANDLE_VALUE // TODO apply to all threads? + }; + for (i, filter) in filters.iter().enumerate() { let ipt_payload = ConfigureThreadAddressFilterRange { - thread_handle: 0, // todo + thread_handle, range_index: i.try_into()?, range_config: IptFilterRangeSettings::IptFilterRangeIp, start_address: *filter.start(), @@ -240,14 +273,15 @@ impl<'a> IntelPT<'a> { IptInputType::ConfigureThreadAddressFilterRange, ipt_payload.into(), ); - self.send_device_io_request(input)?; + let (_, out_size) = self.send_device_io_request(input)?; + debug_assert_eq!(out_size, 0); } Ok(()) } pub fn enable_tracing(&mut self) -> windows::core::Result<()> { let options = IptOptions::builder() - .with_inherit(false) + .with_inherit(true) // todo: expose this param? .with_option_version(IPT_OPTION_VERSION) .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg .value; @@ -263,12 +297,22 @@ impl<'a> IntelPT<'a> { Ok(()) } - // todo: must not be public, or even exist? - pub fn get_raw_trace(&mut self) -> windows::core::Result> { - // todo: this looks pretty racy, size might increas ebetween get_trace_size and the actual + /// Fill the coverage map by decoding the PT traces + /// + /// This function consumes the traces. + pub fn decode_traces_into_map( + &mut self, + // images: &[PtImage], todo: introduce support for JIT/ self modifying code ecc + map_ptr: *mut T, + map_len: usize, + ) -> Result<(), Error> + where + T: CoverageEntry, + { + // todo: this looks pretty racy, size might increase between get_trace_size and the actual // get trace let trace_size = self.get_trace_size()?; - let mut trace_buffer = Vec::with_capacity(trace_size as usize); + let mut trace_buffer: Vec = Vec::with_capacity(4096 + trace_size as usize); let ipt_payload = GetProcessTrace { trace_version: IPT_TRACE_VERSION, @@ -296,12 +340,58 @@ impl<'a> IntelPT<'a> { ) }?; - assert!((out_size as usize) <= trace_buffer.capacity()); - unsafe{trace_buffer.set_len(out_size as usize);}; + debug_assert!((out_size as usize) <= trace_buffer.capacity()); + // SAFETY: Windows driver should not overflow user buffer + unsafe { + trace_buffer.set_len(out_size as usize); + }; + + let trace_data = unsafe { + trace_buffer + .as_ptr() + .cast::() + .as_ref_unchecked() + }; + assert!(trace_data.valid_trace > 0); // todo better error handling + assert_eq!(trace_data.trace_version, IPT_TRACE_VERSION); + + let mut raw_traces = Vec::new(); + let mut slice = &trace_buffer[size_of::()..]; + while slice.len() >= size_of::() { + let inner_header = + unsafe { slice.as_ptr().cast::().as_ref_unchecked() }; + println!("header: {:?}", inner_header); + slice = &slice[size_of::()..]; + if self.tid.is_none() + || self + .tid + .is_some_and(|tid| tid as u64 == inner_header.thread_id) + { + raw_traces.push(&slice[..inner_header.ring_buffer_offset as usize]); //todo consider wrapping + } + slice = &slice[inner_header.trace_size as usize..]; + } + + if raw_traces.is_empty() { + return Err(Error::empty("no traces returned")); + } - //todo trace_buffer has a header in it + #[cfg(feature = "export_raw")] + { + self.last_decode_trace = raw_traces[0].to_vec(); + for i in 1..raw_traces.len() { + self.last_decode_trace.extend_from_slice(&raw_traces[i]); + } + } + + let coverage = unsafe { &mut *slice_from_raw_parts_mut(map_ptr, map_len) }; + // todo consider also other raw_traces[1..] probably there should be one decoder for thread + if let Err(e) = self.ptcov_decoder.coverage(raw_traces[0], coverage) { + log::warn!("PT trace decoding to coverage failed: {e:?}"); + coverage.fill(0.into()); + } - Ok(trace_buffer) + Ok(()) } fn get_trace_size(&self) -> windows::core::Result { @@ -344,22 +434,39 @@ impl<'a> IntelPT<'a> { Ok((unsafe { out.assume_init() }, out_size)) } + + #[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(()) + } } #[derive(Debug)] pub struct IntelPTBuilder<'a> { ip_filters: Vec>, images: &'a [PtImage<'a>], - target_thread_id: u32, + pid: u32, + tid: Option, } impl Default for IntelPTBuilder<'_> { fn default() -> Self { - let target_thread_id = unsafe { GetCurrentThreadId() }; + let pid = unsafe { GetCurrentProcessId() }; Self { ip_filters: Vec::new(), images: &[], - target_thread_id, + pid, + tid: None, } } } @@ -368,22 +475,8 @@ impl<'a> IntelPTBuilder<'a> { pub fn build(self) -> Result, libafl_bolts::Error> { let ipt_handle = open_ipt_handle()?; - let target_thread_handle = unsafe { - OpenThread( - THREAD_GET_CONTEXT | THREAD_QUERY_LIMITED_INFORMATION, - false, - self.target_thread_id, - ) - } - .map(|h| PtHandle { inner: h }) - .map_err(|e| { - libafl_bolts::Error::os_error(e.into(), "Failed to get target thread handle") - })?; - - let pid = unsafe { GetProcessIdOfThread(target_thread_handle.inner) }; - let target_process_handle = - unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid) } + unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, self.pid) } .map(|h| PtHandle { inner: h }) .map_err(|e| { libafl_bolts::Error::os_error(e.into(), "Failed to get target process handle") @@ -397,8 +490,10 @@ impl<'a> IntelPTBuilder<'a> { let mut intel_pt = IntelPT { ipt_handle, target_process_handle, - target_thread_handle, + tid: self.tid, ptcov_decoder, + #[cfg(feature = "export_raw")] + last_decode_trace: Vec::new(), }; intel_pt.set_ip_filters(&self.ip_filters).map_err(|e| { libafl_bolts::Error::os_error(e.into(), "Failed to set IntelPT ip filters") @@ -407,8 +502,14 @@ impl<'a> IntelPTBuilder<'a> { } #[must_use] - pub fn thread_id(mut self, thread_id: u32) -> Self { - self.target_thread_id = thread_id; + pub fn thread_id(mut self, thread_id: Option) -> Self { + self.tid = thread_id; + self + } + + #[must_use] + pub fn pid(mut self, pid: u32) -> Self { + self.pid = pid; self } diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs index d4a73c8fd35..331fefd3e3a 100644 --- a/crates/libafl_intelpt/tests/integration_tests_windows.rs +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -1,66 +1,96 @@ #![cfg(feature = "std")] #![cfg(target_os = "windows")] -use std::{ffi::c_void, fs::File, io::Write}; - -use libafl_intelpt::{IntelPT, availability}; -use windows::Win32::{ - Foundation::{CloseHandle, WAIT_OBJECT_0}, - System::Threading::{ - CreateThread, GetThreadId, INFINITE, Sleep, THREAD_CREATION_FLAGS, WaitForSingleObject, +use std::{ + arch::asm, + slice, + sync::{ + Arc, Barrier, + mpsc::{Sender, channel}, }, + thread, }; -extern "system" fn worker_main(_lp_parameter: *mut c_void) -> u32 { - unsafe { Sleep(2000) }; - 0 +use libafl_intelpt::{IntelPT, availability}; +use log::LevelFilter; +use proc_maps::get_process_maps; +use ptcov::PtImage; +use windows::Win32::System::Threading::{GetCurrentProcessId, GetCurrentThreadId}; + +fn worker_main(thread_id_sender: Sender, barrier: Arc) -> u32 { + let tid = unsafe { GetCurrentThreadId() }; + thread_id_sender.send(tid).unwrap(); + + barrier.wait(); + + let mut count = 0; + unsafe { + asm!( + "2:", + "add {0:r}, 1", + "cmp {0:r}, 255", + "jle 2b", + inout(reg) count, + options(nostack) + ); + } + + barrier.wait(); + count } #[test] fn intel_pt_trace_thread() { + let _ = env_logger::builder() + .is_test(true) + .filter_level(LevelFilter::Trace) + .try_init(); + if let Err(reason) = availability() { println!("Intel PT is not available, skipping test. Reasons:"); println!("{reason}"); return; } - let thread_handle = unsafe { - CreateThread( - None, - 0, - Some(worker_main), - Some(std::ptr::null_mut()), - THREAD_CREATION_FLAGS(0), - None, - ) - } - .expect("Failed to create worker thread"); + let pid = unsafe { GetCurrentProcessId() }; - let thread_id = unsafe { GetThreadId(thread_handle) }; - assert!( - thread_id != 0, - "Failed to read worker thread ID from parent" - ); + let maps = get_process_maps(pid).expect("failed to get process maps"); + let images = maps + .iter() + .filter(|map| map.is_exec())// && map.filename().is_some()) + .map(|pm| { + println!("map: {:?}", pm); + let data = unsafe { slice::from_raw_parts(pm.start() as *const u8, pm.size()) }; + PtImage::new(data, pm.start() as u64) + }) + .collect::>(); let mut pt = IntelPT::builder() - .thread_id(thread_id) - .images(&[]) + .images(&images) .build() .expect("Failed to create IntelPT for worker thread"); pt.enable_tracing().expect("Failed to enable tracing"); - let wait_result = unsafe { WaitForSingleObject(thread_handle, INFINITE) }; - assert_eq!( - wait_result, WAIT_OBJECT_0, - "Worker thread did not terminate cleanly" - ); + let (thread_id_sender, thread_id_receiver) = channel(); + let barrier = Arc::new(Barrier::new(2)); + let worker_barrier = barrier.clone(); + let worker = thread::spawn(|| worker_main(thread_id_sender, worker_barrier)); + let worker_tid = thread_id_receiver.recv().unwrap(); + println!("Intel PT worker thread id: {}", worker_tid); + pt.set_tid(Some(worker_tid)); + barrier.wait(); - let trace = pt - .get_raw_trace() - .expect("Failed to get raw trace from raw trace"); + let mut map = vec![0u16; 0x10_00]; + pt.decode_traces_into_map(map.as_mut_ptr(), map.len()) + .unwrap(); + pt.dump_last_trace_to_file().unwrap(); - let mut file = File::create("output.bin").unwrap(); - file.write_all(&trace).unwrap(); + let assembly_jump_id = map.iter().position(|count| *count >= 254); + assert!( + assembly_jump_id.is_some(), + "Assembly jumps not found in traces" + ); - let _ = unsafe { CloseHandle(thread_handle) }; + barrier.wait(); + worker.join().expect("Worker thread panicked"); } From 03fcb58718286d2e690815ff8797ff38a6d0a693 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 22 May 2026 11:54:04 +0200 Subject: [PATCH 09/28] Bump ptcov to 0.1.1 --- crates/libafl_intelpt/Cargo.toml | 4 ++-- crates/libafl_intelpt/tests/integration_tests_windows.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index 329204d179d..41eb2846a5c 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -33,7 +33,7 @@ libc = { workspace = true } log = { workspace = true } num_enum = { workspace = true, default-features = false } num-traits = { workspace = true, default-features = false } -ptcov = { path = "../../../ptcov" } +ptcov = { version = "0.1.1" } raw-cpuid = { version = "11.1.0" } [target.'cfg(target_os = "linux" )'.dependencies] @@ -41,7 +41,7 @@ caps = { version = "0.5.5" } perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] -ptcov = { path = "../../../ptcov", features = ["retc", "log"] } +ptcov = { version = "0.1.1", features = ["retc"] } windows = { workspace = true, features = [ "Win32_System_IO", "Win32_Storage_FileSystem", diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs index 331fefd3e3a..5c466c0b1a4 100644 --- a/crates/libafl_intelpt/tests/integration_tests_windows.rs +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -83,7 +83,6 @@ fn intel_pt_trace_thread() { let mut map = vec![0u16; 0x10_00]; pt.decode_traces_into_map(map.as_mut_ptr(), map.len()) .unwrap(); - pt.dump_last_trace_to_file().unwrap(); let assembly_jump_id = map.iter().position(|count| *count >= 254); assert!( From 65ecefccc8fb2199f4216fc240f0bd3e56bf336c Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 22 May 2026 17:09:17 +0200 Subject: [PATCH 10/28] WIP win --- crates/libafl/src/executors/hooks/mod.rs | 2 +- crates/libafl_intelpt/src/windows.rs | 96 +++++++++++++++++------- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/crates/libafl/src/executors/hooks/mod.rs b/crates/libafl/src/executors/hooks/mod.rs index 8b4cd9cfd9a..0ce8d952ba9 100644 --- a/crates/libafl/src/executors/hooks/mod.rs +++ b/crates/libafl/src/executors/hooks/mod.rs @@ -21,7 +21,7 @@ pub mod inprocess; pub mod timer; /// Intel Processor Trace (PT) -#[cfg(all(feature = "intel_pt", target_os = "linux", target_arch = "x86_64"))] +#[cfg(all(feature = "intel_pt", any(windows, target_os = "linux"), target_arch = "x86_64"))] pub mod intel_pt; /// The hook that runs before and after the executor runs the target diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index e1de0bb396b..4bc4f6696c7 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -12,8 +12,8 @@ use std::{ use arbitrary_int::{u3, u4, u36}; use bitbybit::bitfield; use libafl_bolts::Error; -pub use ptcov::PtCoverageDecoder; -use ptcov::{CoverageEntry, PtCoverageDecoderBuilder, PtDecoderError, PtImage}; +pub use ptcov::{CoverageEntry, PtCoverageDecoder, PtImage}; +use ptcov::{PtCoverageDecoderBuilder, PtDecoderError}; use raw_cpuid::CpuId; use windows::{ Win32::{ @@ -131,6 +131,12 @@ pub struct GetProcessTrace { pub process_handle: u64, } +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct PauseResumeThreadTrace { + thread_handle: HANDLE, +} + #[repr(C)] #[derive(Debug, Clone, Copy)] struct IptBufferVersion { @@ -146,7 +152,7 @@ union IptInputPayload { // stop_trace: StopProcessTrace, configure_thread_address_filter_range: ConfigureThreadAddressFilterRange, // query_filter: QueryThreadFilter, - // pause_resume_thread: PauseResumeThreadTrace, + pause_resume_thread: PauseResumeThreadTrace, _pad: [u8; 32], } @@ -170,6 +176,14 @@ impl From for IptInputPayload { } } +impl From for IptInputPayload { + fn from(value: PauseResumeThreadTrace) -> Self { + Self { + pause_resume_thread: value, + } + } +} + #[repr(C)] struct IptInputBuffer { version: IptBufferVersion, @@ -255,15 +269,17 @@ impl<'a> IntelPT<'a> { /// /// Only instructions in `filters` ranges will be traced. pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> windows::core::Result<()> { - let thread_handle = if let Some(tid) = self.tid { - unsafe { OpenThread(THREAD_GET_CONTEXT, false, tid)? } - } else { - INVALID_HANDLE_VALUE // TODO apply to all threads? + let thread_handle = PtHandle { + inner: if let Some(tid) = self.tid { + unsafe { OpenThread(THREAD_GET_CONTEXT, false, tid)? } + } else { + INVALID_HANDLE_VALUE // TODO apply to all threads? + }, }; for (i, filter) in filters.iter().enumerate() { let ipt_payload = ConfigureThreadAddressFilterRange { - thread_handle, + thread_handle: thread_handle.inner, range_index: i.try_into()?, range_config: IptFilterRangeSettings::IptFilterRangeIp, start_address: *filter.start(), @@ -280,19 +296,35 @@ impl<'a> IntelPT<'a> { } pub fn enable_tracing(&mut self) -> windows::core::Result<()> { - let options = IptOptions::builder() - .with_inherit(true) // todo: expose this param? - .with_option_version(IPT_OPTION_VERSION) - .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg - .value; + self.toggle_tracing(IptInputType::ResumeThreadTrace) + } - let ipt_payload = StartProcessTrace { - process_handle: self.target_process_handle.inner.0 as u64, - options, + pub fn disable_tracing(&mut self) -> windows::core::Result<()> { + self.toggle_tracing(IptInputType::PauseThreadTrace) + } + + fn toggle_tracing(&mut self, input_type: IptInputType) -> windows::core::Result<()> { + let thread_handle = PtHandle { + inner: if let Some(tid) = self.tid { + unsafe { OpenThread(THREAD_GET_CONTEXT, false, tid)? } + } else { + INVALID_HANDLE_VALUE // TODO apply to all threads? + }, }; - let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); + + let ipt_payload = PauseResumeThreadTrace { + thread_handle: thread_handle.inner, + }; + let input = IptInputBuffer::new( + input_type, + IptInputPayload { + pause_resume_thread: ipt_payload.into(), + }, + ); + + // todo: this return the previous state, should we care? let (_, out_size) = self.send_device_io_request(input)?; - debug_assert_eq!(out_size, 0); + debug_assert_eq!(out_size, 24); Ok(()) } @@ -360,7 +392,6 @@ impl<'a> IntelPT<'a> { while slice.len() >= size_of::() { let inner_header = unsafe { slice.as_ptr().cast::().as_ref_unchecked() }; - println!("header: {:?}", inner_header); slice = &slice[size_of::()..]; if self.tid.is_none() || self @@ -408,10 +439,6 @@ impl<'a> IntelPT<'a> { Ok(unsafe { out.get_trace_size.trace_size }) } - // pub fn disable_tracing(&mut self) -> windows::core::Result<()> { - // - // } - fn send_device_io_request( &self, input: IptInputBuffer, @@ -473,6 +500,7 @@ impl Default for IntelPTBuilder<'_> { impl<'a> IntelPTBuilder<'a> { pub fn build(self) -> Result, libafl_bolts::Error> { + // todo: is there a way to start this all set to trace but "paused" as in linux? let ipt_handle = open_ipt_handle()?; let target_process_handle = @@ -495,9 +523,25 @@ impl<'a> IntelPTBuilder<'a> { #[cfg(feature = "export_raw")] last_decode_trace: Vec::new(), }; - intel_pt.set_ip_filters(&self.ip_filters).map_err(|e| { - libafl_bolts::Error::os_error(e.into(), "Failed to set IntelPT ip filters") - })?; + + let options = IptOptions::builder() + .with_inherit(true) // todo: expose this param? + .with_option_version(IPT_OPTION_VERSION) + .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg + .value; + + let ipt_payload = StartProcessTrace { + process_handle: intel_pt.target_process_handle.inner.0 as u64, + options, + }; + let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); + let (_, out_size) = intel_pt.send_device_io_request(input)?; + debug_assert_eq!(out_size, 0); + + // todo: review this, since tid could be set later, filter setup can fail or be useless here + intel_pt + .set_ip_filters(&self.ip_filters) + .map_err(|e| Error::os_error(e.into(), "Failed to set IntelPT ip filters"))?; Ok(intel_pt) } From bdf9dfb8234d497ec041d0282e508d97685a82e8 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 29 May 2026 16:36:40 +0200 Subject: [PATCH 11/28] Babyfuzzer finally working --- crates/libafl_intelpt/src/windows.rs | 99 ++++++++++--------- .../tests/integration_tests_windows.rs | 12 +-- .../intel_pt_baby_fuzzer/Cargo.toml | 4 + .../intel_pt_baby_fuzzer/src/main.rs | 17 +++- 4 files changed, 77 insertions(+), 55 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 4bc4f6696c7..983fc53e489 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -36,6 +36,7 @@ use windows::{ use crate::utils::current_cpu; +#[derive(Debug)] #[repr(u32)] enum IptIoctl { Request = 0x220004, @@ -90,45 +91,60 @@ struct ConfigureThreadAddressFilterRange { /// - Inherit: bit 23 (1 bit) - Children will be automatically traced /// - ModeSettings: bits 24-27 (4 bits) - IPT_MODE_SETTINGS /// - Reserved: bits 28-63 (36 bits) -#[bitfield(u64, default = 0)] +#[bitfield(u64)] #[derive(Debug)] -pub struct IptOptions { +struct IptOptions { #[bits(0..=3, rw)] - pub option_version: u4, + option_version: u4, #[bits(4..=7, rw)] - pub timing_settings: u4, + timing_settings: u4, #[bits(8..=11, rw)] - pub mtc_frequency: u4, + mtc_frequency: u4, #[bits(12..=15, rw)] - pub cyc_threshold: u4, + cyc_threshold: u4, #[bits(16..=19, rw)] - pub topa_pages_pow2: u4, + topa_pages_pow2: u4, /// Not relevant when tracing by process handle #[bits(20..=22, rw)] - pub match_settings: u3, + match_settings: u3, #[bits(23..=23, rw)] - pub inherit: bool, + inherit: bool, #[bits(24..=27, rw)] - pub mode_settings: u4, + mode_settings: u4, #[bits(28..=63)] reserved: u36, } -const IPT_OPTION_VERSION: u4 = u4::new(1); + +impl Default for IptOptions { + fn default() -> Self { + Self::builder() + .with_option_version(Self::VERSION) + .with_topa_pages_pow2(u4::new(4)) // 64 kB + .with_inherit(true) // todo: expose this param? + .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg + .value + } +} + +impl IptOptions { + const VERSION: u4 = u4::new(1); +} + const IPT_TRACE_VERSION: u16 = 1; #[repr(C)] #[derive(Debug, Clone, Copy)] -pub struct StartProcessTrace { +struct StartProcessTrace { process_handle: u64, options: IptOptions, } #[repr(C)] #[derive(Debug, Clone, Copy)] -pub struct GetProcessTrace { - pub trace_version: u16, +struct GetProcessTrace { + trace_version: u16, _padding: [u8; 6], - pub process_handle: u64, + process_handle: u64, } #[repr(C)] @@ -246,7 +262,7 @@ pub struct IntelPT<'a> { ipt_handle: PtHandle, target_process_handle: PtHandle, tid: Option, - // previous_decode_head: u64, + previous_decode_head: u32, ptcov_decoder: PtCoverageDecoder<'a>, #[cfg(feature = "export_raw")] last_decode_trace: Vec, @@ -341,10 +357,9 @@ impl<'a> IntelPT<'a> { where T: CoverageEntry, { - // todo: this looks pretty racy, size might increase between get_trace_size and the actual // get trace let trace_size = self.get_trace_size()?; - let mut trace_buffer: Vec = Vec::with_capacity(4096 + trace_size as usize); + let mut trace_buffer: Vec = Vec::with_capacity(trace_size as usize); let ipt_payload = GetProcessTrace { trace_version: IPT_TRACE_VERSION, @@ -387,7 +402,6 @@ impl<'a> IntelPT<'a> { assert!(trace_data.valid_trace > 0); // todo better error handling assert_eq!(trace_data.trace_version, IPT_TRACE_VERSION); - let mut raw_traces = Vec::new(); let mut slice = &trace_buffer[size_of::()..]; while slice.len() >= size_of::() { let inner_header = @@ -398,30 +412,30 @@ impl<'a> IntelPT<'a> { .tid .is_some_and(|tid| tid as u64 == inner_header.thread_id) { - raw_traces.push(&slice[..inner_header.ring_buffer_offset as usize]); //todo consider wrapping + let mut split_buffer = Vec::new(); + let trace= if inner_header.ring_buffer_offset >= self.previous_decode_head { + &slice[self.previous_decode_head as usize..inner_header.ring_buffer_offset as usize] + } else { + split_buffer.extend(&slice[self.previous_decode_head as usize..inner_header.trace_size as usize]); + split_buffer.extend(&slice[0..inner_header.ring_buffer_offset as usize]); + &split_buffer[..] + }; + self.previous_decode_head = inner_header.ring_buffer_offset; + + #[cfg(feature = "export_raw")] + { + self.last_decode_trace.extend(slice); + } + + let coverage = unsafe { &mut *slice_from_raw_parts_mut(map_ptr, map_len) }; + // todo: there should be one decoder for thread? + if let Err(e) = self.ptcov_decoder.coverage(trace, coverage) { + log::warn!("PT trace decoding to coverage failed: {e:?}"); + } } slice = &slice[inner_header.trace_size as usize..]; } - if raw_traces.is_empty() { - return Err(Error::empty("no traces returned")); - } - - #[cfg(feature = "export_raw")] - { - self.last_decode_trace = raw_traces[0].to_vec(); - for i in 1..raw_traces.len() { - self.last_decode_trace.extend_from_slice(&raw_traces[i]); - } - } - - let coverage = unsafe { &mut *slice_from_raw_parts_mut(map_ptr, map_len) }; - // todo consider also other raw_traces[1..] probably there should be one decoder for thread - if let Err(e) = self.ptcov_decoder.coverage(raw_traces[0], coverage) { - log::warn!("PT trace decoding to coverage failed: {e:?}"); - coverage.fill(0.into()); - } - Ok(()) } @@ -519,16 +533,13 @@ impl<'a> IntelPTBuilder<'a> { ipt_handle, target_process_handle, tid: self.tid, + previous_decode_head: 0, ptcov_decoder, #[cfg(feature = "export_raw")] last_decode_trace: Vec::new(), }; - let options = IptOptions::builder() - .with_inherit(true) // todo: expose this param? - .with_option_version(IPT_OPTION_VERSION) - .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg - .value; + let options = IptOptions::default(); let ipt_payload = StartProcessTrace { process_handle: intel_pt.target_process_handle.inner.0 as u64, diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs index 5c466c0b1a4..11973928466 100644 --- a/crates/libafl_intelpt/tests/integration_tests_windows.rs +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -3,7 +3,7 @@ use std::{ arch::asm, - slice, + process, slice, sync::{ Arc, Barrier, mpsc::{Sender, channel}, @@ -15,7 +15,7 @@ use libafl_intelpt::{IntelPT, availability}; use log::LevelFilter; use proc_maps::get_process_maps; use ptcov::PtImage; -use windows::Win32::System::Threading::{GetCurrentProcessId, GetCurrentThreadId}; +use windows::Win32::System::Threading::GetCurrentThreadId; fn worker_main(thread_id_sender: Sender, barrier: Arc) -> u32 { let tid = unsafe { GetCurrentThreadId() }; @@ -52,14 +52,13 @@ fn intel_pt_trace_thread() { return; } - let pid = unsafe { GetCurrentProcessId() }; + let pid = process::id(); let maps = get_process_maps(pid).expect("failed to get process maps"); let images = maps .iter() - .filter(|map| map.is_exec())// && map.filename().is_some()) + .filter(|map| map.is_exec()) .map(|pm| { - println!("map: {:?}", pm); let data = unsafe { slice::from_raw_parts(pm.start() as *const u8, pm.size()) }; PtImage::new(data, pm.start() as u64) }) @@ -69,15 +68,14 @@ fn intel_pt_trace_thread() { .images(&images) .build() .expect("Failed to create IntelPT for worker thread"); - pt.enable_tracing().expect("Failed to enable tracing"); let (thread_id_sender, thread_id_receiver) = channel(); let barrier = Arc::new(Barrier::new(2)); let worker_barrier = barrier.clone(); let worker = thread::spawn(|| worker_main(thread_id_sender, worker_barrier)); let worker_tid = thread_id_receiver.recv().unwrap(); - println!("Intel PT worker thread id: {}", worker_tid); pt.set_tid(Some(worker_tid)); + pt.enable_tracing().unwrap(); barrier.wait(); let mut map = vec![0u16; 0x10_00]; diff --git a/fuzzers/binary_only/intel_pt_baby_fuzzer/Cargo.toml b/fuzzers/binary_only/intel_pt_baby_fuzzer/Cargo.toml index 8d26d04ee82..c9c7d4eca39 100644 --- a/fuzzers/binary_only/intel_pt_baby_fuzzer/Cargo.toml +++ b/fuzzers/binary_only/intel_pt_baby_fuzzer/Cargo.toml @@ -15,6 +15,10 @@ tui = ["libafl/tui_monitor"] libafl = { path = "../../../crates/libafl", features = ["intel_pt"] } libafl_bolts = { path = "../../../crates/libafl_bolts" } proc-maps = "0.4.0" +env_logger = "0.11.10" + +[target.'cfg(target_os = "windows")'.dependencies] +windows = "0.62.2" [profile.release] lto = true diff --git a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs index 49b0c2d4f01..4ba8c8775e3 100644 --- a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs +++ b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs @@ -21,6 +21,7 @@ use libafl::{ }; use libafl_bolts::{current_nanos, nonnull_raw_mut, rands::StdRand, tuples::tuple_list}; use proc_maps::get_process_maps; +use windows::Win32::System::Threading::GetCurrentThreadId; // Edge coverage map. const MAP_SIZE: usize = 4096; @@ -28,6 +29,8 @@ static mut MAP: [u8; MAP_SIZE] = [0; MAP_SIZE]; static mut MAP_PTR: *mut u8 = &raw mut MAP as _; pub fn main() { + env_logger::init(); + // The function that we want to fuzz let mut harness = |input: &BytesInput| { let buf = input.target_bytes(); @@ -84,13 +87,15 @@ pub fn main() { // Get the memory map of the current process, copy the executable memory that will be // disassembled and used for Intel PT trace decoding - let my_pid = i32::try_from(process::id()).unwrap(); - let process_maps = get_process_maps(my_pid).unwrap(); + let my_pid = process::id(); + #[cfg_attr(windows, expect(clippy::useless_conversion))] + let process_maps = get_process_maps(my_pid.try_into().unwrap()).unwrap(); let images = process_maps .iter() .filter_map(|pm| { - if pm.is_exec() && pm.filename().is_some() && pm.inode != 0 { + if pm.is_exec() { let data = unsafe { slice::from_raw_parts(pm.start() as *const u8, pm.size()) }; + println!("image: {:?}", pm); Some(PtImage::new(data, pm.start() as u64)) } else { None @@ -99,7 +104,11 @@ pub fn main() { .collect::>(); // Pass the executable memory to the code responsible for Intel PT trace decoding - let pt = IntelPT::builder().images(&images).build().unwrap(); + let pt = IntelPT::builder() + .images(&images) + .thread_id(Some(unsafe { GetCurrentThreadId() })) + .build() + .unwrap(); // Intel PT hook that will handle the setup of Intel PT for each execution and fill the map let pt_hook = unsafe { IntelPTHook::builder() From c8992f580773fe8b1dd57e74ad45cc82b87e3828 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 29 May 2026 16:58:00 +0200 Subject: [PATCH 12/28] Clippy --- crates/libafl_intelpt/src/windows.rs | 83 +++++++++++++++------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 983fc53e489..fa45dbcb8df 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -66,9 +66,9 @@ enum IptInputType { #[repr(u32)] #[derive(Debug, Clone, Copy)] enum IptFilterRangeSettings { - IptFilterRangeDisable = 0, - IptFilterRangeIp = 1, - IptFilterRangeTraceStop = 2, + Disable = 0, + Ip = 1, + TraceStop = 2, } #[repr(C)] @@ -82,14 +82,14 @@ struct ConfigureThreadAddressFilterRange { } /// Layout (from winipt): -/// - OptionVersion: bits 0-3 (4 bits) - Must be set to 1 -/// - TimingSettings: bits 4-7 (4 bits) - IPT_TIMING_SETTINGS -/// - MtcFrequency: bits 8-11 (4 bits) - Bits 14:17 in IA32_RTIT_CTL -/// - CycThreshold: bits 12-15 (4 bits) - Bits 19:22 in IA32_RTIT_CTL -/// - TopaPagesPow2: bits 16-19 (4 bits) - Size of buffer as 4KB powers of 2 (4KB->128MB) -/// - MatchSettings: bits 20-22 (3 bits) - IPT_MATCH_SETTINGS +/// - `OptionVersion`: bits 0-3 (4 bits) - Must be set to 1 +/// - `TimingSettings`: bits 4-7 (4 bits) - `IPT_TIMING_SETTINGS` +/// - `MtcFrequency`: bits 8-11 (4 bits) - Bits 14:17 in `IA32_RTIT_CTL` +/// - `CycThreshold`: bits 12-15 (4 bits) - Bits 19:22 in `IA32_RTIT_CTL` +/// - `TopaPagesPow2`: bits 16-19 (4 bits) - Size of buffer as 4KB powers of 2 (4KB->128MB) +/// - `MatchSettings`: bits 20-22 (3 bits) - `IPT_MATCH_SETTINGS` /// - Inherit: bit 23 (1 bit) - Children will be automatically traced -/// - ModeSettings: bits 24-27 (4 bits) - IPT_MODE_SETTINGS +/// - `ModeSettings`: bits 24-27 (4 bits) - `IPT_MODE_SETTINGS` /// - Reserved: bits 28-63 (36 bits) #[bitfield(u64)] #[derive(Debug)] @@ -221,16 +221,16 @@ impl IptInputBuffer { #[repr(C)] #[derive(Debug, Clone, Copy)] -pub struct OutGetTraceSize { - pub trace_version: u16, - pub _padding: [u8; 6], - pub trace_size: u64, +struct OutGetTraceSize { + trace_version: u16, + _padding: [u8; 6], + trace_size: u64, } #[repr(C)] union IptOutputBuffer { // pub get_trace_version: OutGetTraceVersion, - pub get_trace_size: OutGetTraceSize, + get_trace_size: OutGetTraceSize, // pub query_filter: OutQueryThreadFilter, // pub pause_trace: OutPauseResumeTrace, // pub resume_trace: OutPauseResumeTrace, @@ -297,7 +297,7 @@ impl<'a> IntelPT<'a> { let ipt_payload = ConfigureThreadAddressFilterRange { thread_handle: thread_handle.inner, range_index: i.try_into()?, - range_config: IptFilterRangeSettings::IptFilterRangeIp, + range_config: IptFilterRangeSettings::Ip, start_address: *filter.start(), end_address: *filter.end(), }; @@ -305,7 +305,7 @@ impl<'a> IntelPT<'a> { IptInputType::ConfigureThreadAddressFilterRange, ipt_payload.into(), ); - let (_, out_size) = self.send_device_io_request(input)?; + let (_, out_size) = self.send_device_io_request(&input)?; debug_assert_eq!(out_size, 0); } Ok(()) @@ -334,12 +334,12 @@ impl<'a> IntelPT<'a> { let input = IptInputBuffer::new( input_type, IptInputPayload { - pause_resume_thread: ipt_payload.into(), + pause_resume_thread: ipt_payload, }, ); // todo: this return the previous state, should we care? - let (_, out_size) = self.send_device_io_request(input)?; + let (_, out_size) = self.send_device_io_request(&input)?; debug_assert_eq!(out_size, 24); Ok(()) @@ -348,6 +348,10 @@ impl<'a> IntelPT<'a> { /// Fill the coverage map by decoding the PT traces /// /// This function consumes the traces. + #[expect( + clippy::cast_ptr_alignment, + reason = "Given the structure of the ipt buffer, casts to headers are always aligned" + )] pub fn decode_traces_into_map( &mut self, // images: &[PtImage], todo: introduce support for JIT/ self modifying code ecc @@ -369,7 +373,7 @@ impl<'a> IntelPT<'a> { let input = IptInputBuffer::new( IptInputType::GetProcessTrace, IptInputPayload { - get_trace: ipt_payload.into(), + get_trace: ipt_payload, }, ); @@ -378,11 +382,11 @@ impl<'a> IntelPT<'a> { DeviceIoControl( self.ipt_handle.inner, IptIoctl::ReadTrace as u32, - Some(&raw const input as *const std::ffi::c_void), + Some(&raw const input as *const core::ffi::c_void), size_of::() as u32, - Some(trace_buffer.as_mut_ptr() as *mut std::ffi::c_void), + Some(trace_buffer.as_mut_ptr() as *mut core::ffi::c_void), trace_buffer.capacity() as u32, - Some(&mut out_size), + Some(&raw mut out_size), None, ) }?; @@ -393,6 +397,7 @@ impl<'a> IntelPT<'a> { trace_buffer.set_len(out_size as usize); }; + debug_assert!(trace_buffer.as_ptr().cast::().is_aligned()); let trace_data = unsafe { trace_buffer .as_ptr() @@ -404,19 +409,23 @@ impl<'a> IntelPT<'a> { let mut slice = &trace_buffer[size_of::()..]; while slice.len() >= size_of::() { + debug_assert!(slice.as_ptr().cast::().is_aligned()); let inner_header = unsafe { slice.as_ptr().cast::().as_ref_unchecked() }; slice = &slice[size_of::()..]; - if self.tid.is_none() - || self - .tid - .is_some_and(|tid| tid as u64 == inner_header.thread_id) + if self + .tid + .is_none_or(|tid| u64::from(tid) == inner_header.thread_id) { let mut split_buffer = Vec::new(); - let trace= if inner_header.ring_buffer_offset >= self.previous_decode_head { - &slice[self.previous_decode_head as usize..inner_header.ring_buffer_offset as usize] + let trace = if inner_header.ring_buffer_offset >= self.previous_decode_head { + &slice[self.previous_decode_head as usize + ..inner_header.ring_buffer_offset as usize] } else { - split_buffer.extend(&slice[self.previous_decode_head as usize..inner_header.trace_size as usize]); + split_buffer.extend( + &slice + [self.previous_decode_head as usize..inner_header.trace_size as usize], + ); split_buffer.extend(&slice[0..inner_header.ring_buffer_offset as usize]); &split_buffer[..] }; @@ -446,7 +455,7 @@ impl<'a> IntelPT<'a> { process_handle: self.target_process_handle.inner.0 as u64, }; let input = IptInputBuffer::new(IptInputType::GetProcessTraceSize, ipt_payload.into()); - let (out, out_size) = self.send_device_io_request(input)?; + let (out, out_size) = self.send_device_io_request(&input)?; debug_assert_eq!(out_size, size_of::() as u32); @@ -455,7 +464,7 @@ impl<'a> IntelPT<'a> { fn send_device_io_request( &self, - input: IptInputBuffer, + input: &IptInputBuffer, ) -> windows::core::Result<(IptOutputBuffer, u32)> { let mut out = MaybeUninit::::uninit(); let mut out_size = 0; @@ -464,11 +473,11 @@ impl<'a> IntelPT<'a> { DeviceIoControl( self.ipt_handle.inner, IptIoctl::Request as u32, - Some(&raw const input as *const std::ffi::c_void), + Some(&raw const input as *const core::ffi::c_void), size_of::() as u32, - Some(out.as_mut_ptr() as *mut std::ffi::c_void), + Some(out.as_mut_ptr() as *mut core::ffi::c_void), size_of::() as u32, - Some(&mut out_size), + Some(&raw mut out_size), None, ) }?; @@ -546,7 +555,7 @@ impl<'a> IntelPTBuilder<'a> { options, }; let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); - let (_, out_size) = intel_pt.send_device_io_request(input)?; + let (_, out_size) = intel_pt.send_device_io_request(&input)?; debug_assert_eq!(out_size, 0); // todo: review this, since tid could be set later, filter setup can fail or be useless here @@ -590,7 +599,7 @@ struct PtHandle { impl Drop for PtHandle { fn drop(&mut self) { if let Err(err) = unsafe { CloseHandle(self.inner) } { - panic!("Failed to close handle: {}", err); + panic!("Failed to close handle: {err}"); } } } From deb51aa481556abe3f78266e2f9b9c4750c51bde Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 29 May 2026 17:23:20 +0200 Subject: [PATCH 13/28] Clippy --- crates/libafl_intelpt/src/windows.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index fa45dbcb8df..79eb746f1f6 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -1,37 +1,33 @@ #![allow(dead_code)] // todo remove -#![allow(unused_imports)] // todo remove // todo review all the pub use alloc::{string::String, vec::Vec}; -use core::{fmt::Debug, ptr}; -use std::{ - io, mem::MaybeUninit, ops::RangeInclusive, prelude::rust_2015::ToString, - ptr::slice_from_raw_parts_mut, -}; +use core::{fmt::Debug, mem::MaybeUninit, ops::RangeInclusive, ptr::slice_from_raw_parts_mut}; +#[cfg(feature = "export_raw")] +use std::string::ToString; -use arbitrary_int::{u3, u4, u36}; +use arbitrary_int::{u3, u4}; use bitbybit::bitfield; use libafl_bolts::Error; +use ptcov::PtCoverageDecoderBuilder; pub use ptcov::{CoverageEntry, PtCoverageDecoder, PtImage}; -use ptcov::{PtCoverageDecoderBuilder, PtDecoderError}; use raw_cpuid::CpuId; use windows::{ Win32::{ Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, - FILE_GENERIC_READ, FILE_SHARE_MODE, FILE_SHARE_READ, OPEN_EXISTING, + FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, }, System::{ IO::DeviceIoControl, Threading::{ - GetCurrentProcessId, GetCurrentThreadId, GetProcessIdOfThread, OpenProcess, - OpenThread, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, THREAD_GET_CONTEXT, - THREAD_QUERY_LIMITED_INFORMATION, + GetCurrentProcessId, OpenProcess, OpenThread, PROCESS_QUERY_INFORMATION, + PROCESS_VM_READ, THREAD_GET_CONTEXT, }, }, }, - core::{HSTRING, w}, + core::w, }; use crate::utils::current_cpu; @@ -522,7 +518,7 @@ impl Default for IntelPTBuilder<'_> { } impl<'a> IntelPTBuilder<'a> { - pub fn build(self) -> Result, libafl_bolts::Error> { + pub fn build(self) -> Result, Error> { // todo: is there a way to start this all set to trace but "paused" as in linux? let ipt_handle = open_ipt_handle()?; @@ -530,7 +526,7 @@ impl<'a> IntelPTBuilder<'a> { unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, self.pid) } .map(|h| PtHandle { inner: h }) .map_err(|e| { - libafl_bolts::Error::os_error(e.into(), "Failed to get target process handle") + Error::os_error(e.into(), "Failed to get target process handle") })?; let ptcov_decoder = PtCoverageDecoderBuilder::new() @@ -555,7 +551,7 @@ impl<'a> IntelPTBuilder<'a> { options, }; let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); - let (_, out_size) = intel_pt.send_device_io_request(&input)?; + let (_, out_size) = intel_pt.send_device_io_request(&input).unwrap(); debug_assert_eq!(out_size, 0); // todo: review this, since tid could be set later, filter setup can fail or be useless here From 520c739abaa29f33b401054a76e06c43cdcd5492 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 29 May 2026 17:37:54 +0200 Subject: [PATCH 14/28] Fix --- crates/libafl_intelpt/src/windows.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 79eb746f1f6..9c02392ad1e 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -469,9 +469,9 @@ impl<'a> IntelPT<'a> { DeviceIoControl( self.ipt_handle.inner, IptIoctl::Request as u32, - Some(&raw const input as *const core::ffi::c_void), + Some((&raw const *input).cast()), size_of::() as u32, - Some(out.as_mut_ptr() as *mut core::ffi::c_void), + Some(out.as_mut_ptr().cast()), size_of::() as u32, Some(&raw mut out_size), None, From 57dc03239ac0e19d244c7a8aaefd19a3c82c93cd Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 29 May 2026 18:03:01 +0200 Subject: [PATCH 15/28] Clippy --- crates/libafl_intelpt/src/windows.rs | 33 +++++++++++++--------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 9c02392ad1e..439e45e83d6 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -1,6 +1,3 @@ -#![allow(dead_code)] // todo remove -// todo review all the pub - use alloc::{string::String, vec::Vec}; use core::{fmt::Debug, mem::MaybeUninit, ops::RangeInclusive, ptr::slice_from_raw_parts_mut}; #[cfg(feature = "export_raw")] @@ -11,7 +8,6 @@ use bitbybit::bitfield; use libafl_bolts::Error; use ptcov::PtCoverageDecoderBuilder; pub use ptcov::{CoverageEntry, PtCoverageDecoder, PtImage}; -use raw_cpuid::CpuId; use windows::{ Win32::{ Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}, @@ -41,6 +37,7 @@ enum IptIoctl { #[derive(Debug)] #[repr(u32)] +#[expect(dead_code, reason = "Not all the commands are used")] enum IptInputType { GetTraceVersion = 0, GetProcessTraceSize, @@ -62,9 +59,9 @@ enum IptInputType { #[repr(u32)] #[derive(Debug, Clone, Copy)] enum IptFilterRangeSettings { - Disable = 0, + // Disable = 0, Ip = 1, - TraceStop = 2, + // TraceStop = 2, } #[repr(C)] @@ -225,11 +222,11 @@ struct OutGetTraceSize { #[repr(C)] union IptOutputBuffer { - // pub get_trace_version: OutGetTraceVersion, + // get_trace_version: OutGetTraceVersion, get_trace_size: OutGetTraceSize, - // pub query_filter: OutQueryThreadFilter, - // pub pause_trace: OutPauseResumeTrace, - // pub resume_trace: OutPauseResumeTrace, + // query_filter: OutQueryThreadFilter, + // pause_trace: OutPauseResumeTrace, + // resume_trace: OutPauseResumeTrace, _pad: [u8; 24], } @@ -618,14 +615,14 @@ pub(crate) fn availability_in_windows() -> Result<(), String> { } } -/// Number of address filters available on the running CPU -fn nr_addr_filters() -> Result { - let cpuid = CpuId::new(); - cpuid - .get_processor_trace_info() - .ok_or("Failed to read CPU Processor Trace Info") - .map(|pti| pti.configurable_address_ranges()) -} +// /// Number of address filters available on the running CPU +// fn nr_addr_filters() -> Result { +// let cpuid = CpuId::new(); +// cpuid +// .get_processor_trace_info() +// .ok_or("Failed to read CPU Processor Trace Info") +// .map(|pti| pti.configurable_address_ranges()) +// } fn open_ipt_handle() -> windows::core::Result { let ipt_path = w!("\\??\\IPT\0"); From 09e3aa15471a01917abcb3f16cad7cebd470da17 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 29 May 2026 18:12:20 +0200 Subject: [PATCH 16/28] Lints --- crates/libafl_intelpt/src/windows.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 439e45e83d6..0efcef326ff 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -113,7 +113,7 @@ impl Default for IptOptions { Self::builder() .with_option_version(Self::VERSION) .with_topa_pages_pow2(u4::new(4)) // 64 kB - .with_inherit(true) // todo: expose this param? + .with_inherit(false) .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg .value } @@ -331,7 +331,6 @@ impl<'a> IntelPT<'a> { }, ); - // todo: this return the previous state, should we care? let (_, out_size) = self.send_device_io_request(&input)?; debug_assert_eq!(out_size, 24); @@ -397,8 +396,10 @@ impl<'a> IntelPT<'a> { .cast::() .as_ref_unchecked() }; - assert!(trace_data.valid_trace > 0); // todo better error handling - assert_eq!(trace_data.trace_version, IPT_TRACE_VERSION); + debug_assert_eq!(trace_data.trace_version, IPT_TRACE_VERSION); + if trace_data.valid_trace == 0 { + return Err(Error::runtime("Intel PT: failed to get a valid trace from ipt.sys")); + } let mut slice = &trace_buffer[size_of::()..]; while slice.len() >= size_of::() { From c9d7ce7ebbb2d03b38e8d477e61810a1c7f8ea2b Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Tue, 9 Jun 2026 12:03:44 +0200 Subject: [PATCH 17/28] Lints --- crates/libafl_intelpt/src/linux.rs | 2 -- fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/crates/libafl_intelpt/src/linux.rs b/crates/libafl_intelpt/src/linux.rs index e5253e8db24..cac8ecee0f5 100644 --- a/crates/libafl_intelpt/src/linux.rs +++ b/crates/libafl_intelpt/src/linux.rs @@ -32,8 +32,6 @@ use perf_event_open_sys::{ perf_event_open, }; pub use ptcov::{CoverageEntry, PtCoverageDecoder, PtCoverageDecoderBuilder, PtImage}; -use ptcov::{PtCpu, PtCpuVendor}; -use raw_cpuid::CpuId; use super::{PAGE_SIZE, availability}; use crate::utils::current_cpu; diff --git a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs index 4ba8c8775e3..5bf667b8c2f 100644 --- a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs +++ b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs @@ -95,7 +95,6 @@ pub fn main() { .filter_map(|pm| { if pm.is_exec() { let data = unsafe { slice::from_raw_parts(pm.start() as *const u8, pm.size()) }; - println!("image: {:?}", pm); Some(PtImage::new(data, pm.start() as u64)) } else { None From f93a80c6d694d8f4b33c671c5a893e5e57537dad Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Wed, 10 Jun 2026 16:19:33 +0200 Subject: [PATCH 18/28] WIP --- crates/libafl_intelpt/Cargo.toml | 11 +- crates/libafl_intelpt/src/windows.rs | 177 +++++++++++------- .../tests/integration_tests_windows.rs | 2 +- .../intel_pt_baby_fuzzer/src/main.rs | 13 +- 4 files changed, 115 insertions(+), 88 deletions(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index 41eb2846a5c..e5cebb3bd9e 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -41,20 +41,13 @@ caps = { version = "0.5.5" } perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] +hashbrown = {workspace = true} ptcov = { version = "0.1.1", features = ["retc"] } windows = { workspace = true, features = [ + "Win32_System_Diagnostics_ToolHelp", "Win32_System_IO", "Win32_Storage_FileSystem", ] } -#windows-sys = { version = "0.61.2", features = [ -# "Win32_Foundation", -# "Win32_System_Diagnostics_ToolHelp", -# "Win32_System_Diagnostics_Debug", -# "Win32_System_IO", -# "Win32_System_Threading", -# "Win32_Storage_FileSystem", -# "Win32_Security", -#] } [lints] workspace = true diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index 0efcef326ff..f38d6bc49a4 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -5,17 +5,22 @@ use std::string::ToString; use arbitrary_int::{u3, u4}; use bitbybit::bitfield; +use hashbrown::HashMap; use libafl_bolts::Error; use ptcov::PtCoverageDecoderBuilder; pub use ptcov::{CoverageEntry, PtCoverageDecoder, PtImage}; use windows::{ Win32::{ - Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}, + Foundation::{HANDLE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, }, System::{ + Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, + Thread32Next, + }, IO::DeviceIoControl, Threading::{ GetCurrentProcessId, OpenProcess, OpenThread, PROCESS_QUERY_INFORMATION, @@ -23,7 +28,7 @@ use windows::{ }, }, }, - core::w, + core::{Owned, w}, }; use crate::utils::current_cpu; @@ -35,7 +40,7 @@ enum IptIoctl { ReadTrace = 0x220006, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] #[repr(u32)] #[expect(dead_code, reason = "Not all the commands are used")] enum IptInputType { @@ -249,14 +254,21 @@ struct IptTraceHeader { trace_size: u32, } +#[derive(Debug)] +struct ThreadCoverageDecoder<'a> { + decoder: PtCoverageDecoder<'a>, + previous_decode_head: u32, +} + /// Intel Processor Trace (PT) #[derive(Debug)] pub struct IntelPT<'a> { - ipt_handle: PtHandle, - target_process_handle: PtHandle, - tid: Option, - previous_decode_head: u32, - ptcov_decoder: PtCoverageDecoder<'a>, + ipt_handle: Owned, + target_process_handle: Owned, + pid: u32, + thread_id: Option, + ptcov_decoders: HashMap>, + images: &'a [PtImage<'a>], #[cfg(feature = "export_raw")] last_decode_trace: Vec, } @@ -270,25 +282,25 @@ impl<'a> IntelPT<'a> { IntelPTBuilder::default() } - pub fn set_tid(&mut self, tid: Option) { - self.tid = tid; + pub fn set_thread_id(&mut self, thread_id: Option) { + self.thread_id = thread_id; } /// Set filters based on Instruction Pointer (IP) /// /// Only instructions in `filters` ranges will be traced. pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> windows::core::Result<()> { - let thread_handle = PtHandle { - inner: if let Some(tid) = self.tid { - unsafe { OpenThread(THREAD_GET_CONTEXT, false, tid)? } + let thread_handle = unsafe { + Owned::new(if let Some(thread_id) = self.thread_id { + OpenThread(THREAD_GET_CONTEXT, false, thread_id)? } else { INVALID_HANDLE_VALUE // TODO apply to all threads? - }, + }) }; for (i, filter) in filters.iter().enumerate() { let ipt_payload = ConfigureThreadAddressFilterRange { - thread_handle: thread_handle.inner, + thread_handle: *thread_handle, range_index: i.try_into()?, range_config: IptFilterRangeSettings::Ip, start_address: *filter.start(), @@ -313,16 +325,28 @@ impl<'a> IntelPT<'a> { } fn toggle_tracing(&mut self, input_type: IptInputType) -> windows::core::Result<()> { - let thread_handle = PtHandle { - inner: if let Some(tid) = self.tid { - unsafe { OpenThread(THREAD_GET_CONTEXT, false, tid)? } - } else { - INVALID_HANDLE_VALUE // TODO apply to all threads? - }, - }; + if let Some(thread_id) = self.thread_id { + let mut thread_handle = + unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) }; + self.toggle_thread_tracing(input_type, &mut thread_handle)?; + } else { + for thread_id in process_thread_ids(self.pid)? { + let mut thread_handle = + unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) }; + self.toggle_thread_tracing(input_type, &mut thread_handle)?; + } + } + Ok(()) + } + + fn toggle_thread_tracing( + &self, + input_type: IptInputType, + thread_handle: &mut HANDLE, + ) -> windows::core::Result<()> { let ipt_payload = PauseResumeThreadTrace { - thread_handle: thread_handle.inner, + thread_handle: *thread_handle, }; let input = IptInputBuffer::new( input_type, @@ -360,7 +384,7 @@ impl<'a> IntelPT<'a> { let ipt_payload = GetProcessTrace { trace_version: IPT_TRACE_VERSION, _padding: [0u8; 6], - process_handle: self.target_process_handle.inner.0 as u64, + process_handle: self.target_process_handle.0 as u64, }; let input = IptInputBuffer::new( IptInputType::GetProcessTrace, @@ -372,7 +396,7 @@ impl<'a> IntelPT<'a> { let mut out_size = 0; unsafe { DeviceIoControl( - self.ipt_handle.inner, + *self.ipt_handle, IptIoctl::ReadTrace as u32, Some(&raw const input as *const core::ffi::c_void), size_of::() as u32, @@ -398,7 +422,9 @@ impl<'a> IntelPT<'a> { }; debug_assert_eq!(trace_data.trace_version, IPT_TRACE_VERSION); if trace_data.valid_trace == 0 { - return Err(Error::runtime("Intel PT: failed to get a valid trace from ipt.sys")); + return Err(Error::runtime( + "Intel PT: failed to get a valid trace from ipt.sys", + )); } let mut slice = &trace_buffer[size_of::()..]; @@ -408,22 +434,33 @@ impl<'a> IntelPT<'a> { unsafe { slice.as_ptr().cast::().as_ref_unchecked() }; slice = &slice[size_of::()..]; if self - .tid - .is_none_or(|tid| u64::from(tid) == inner_header.thread_id) + .thread_id + .is_none_or(|thread_id| u64::from(thread_id) == inner_header.thread_id) { + let ptcov_decoder = self.ptcov_decoders.entry(inner_header.thread_id).or_insert( + ThreadCoverageDecoder { + decoder: PtCoverageDecoderBuilder::new() + .cpu(current_cpu()) + .images(self.images) + .build(), + previous_decode_head: 0, + }, + ); + let mut split_buffer = Vec::new(); - let trace = if inner_header.ring_buffer_offset >= self.previous_decode_head { - &slice[self.previous_decode_head as usize + let trace = if inner_header.ring_buffer_offset >= ptcov_decoder.previous_decode_head + { + &slice[ptcov_decoder.previous_decode_head as usize ..inner_header.ring_buffer_offset as usize] } else { split_buffer.extend( - &slice - [self.previous_decode_head as usize..inner_header.trace_size as usize], + &slice[ptcov_decoder.previous_decode_head as usize + ..inner_header.trace_size as usize], ); split_buffer.extend(&slice[0..inner_header.ring_buffer_offset as usize]); &split_buffer[..] }; - self.previous_decode_head = inner_header.ring_buffer_offset; + ptcov_decoder.previous_decode_head = inner_header.ring_buffer_offset; #[cfg(feature = "export_raw")] { @@ -431,8 +468,8 @@ impl<'a> IntelPT<'a> { } let coverage = unsafe { &mut *slice_from_raw_parts_mut(map_ptr, map_len) }; - // todo: there should be one decoder for thread? - if let Err(e) = self.ptcov_decoder.coverage(trace, coverage) { + + if let Err(e) = ptcov_decoder.decoder.coverage(trace, coverage) { log::warn!("PT trace decoding to coverage failed: {e:?}"); } } @@ -446,7 +483,7 @@ impl<'a> IntelPT<'a> { let ipt_payload = GetProcessTrace { trace_version: IPT_TRACE_VERSION, _padding: [0; 6], - process_handle: self.target_process_handle.inner.0 as u64, + process_handle: self.target_process_handle.0 as u64, }; let input = IptInputBuffer::new(IptInputType::GetProcessTraceSize, ipt_payload.into()); let (out, out_size) = self.send_device_io_request(&input)?; @@ -465,7 +502,7 @@ impl<'a> IntelPT<'a> { unsafe { DeviceIoControl( - self.ipt_handle.inner, + *self.ipt_handle, IptIoctl::Request as u32, Some((&raw const *input).cast()), size_of::() as u32, @@ -500,7 +537,6 @@ pub struct IntelPTBuilder<'a> { ip_filters: Vec>, images: &'a [PtImage<'a>], pid: u32, - tid: Option, } impl Default for IntelPTBuilder<'_> { @@ -510,7 +546,6 @@ impl Default for IntelPTBuilder<'_> { ip_filters: Vec::new(), images: &[], pid, - tid: None, } } } @@ -518,26 +553,22 @@ impl Default for IntelPTBuilder<'_> { impl<'a> IntelPTBuilder<'a> { pub fn build(self) -> Result, Error> { // todo: is there a way to start this all set to trace but "paused" as in linux? + // todo: better error suggesting to start the kernel driver let ipt_handle = open_ipt_handle()?; - let target_process_handle = - unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, self.pid) } - .map(|h| PtHandle { inner: h }) - .map_err(|e| { - Error::os_error(e.into(), "Failed to get target process handle") - })?; - - let ptcov_decoder = PtCoverageDecoderBuilder::new() - .cpu(current_cpu()) - .images(self.images) - .build(); + let target_process_handle = unsafe { + OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, self.pid) + .map(|h| Owned::new(h)) + } + .map_err(|e| Error::os_error(e.into(), "Failed to get target process handle"))?; let mut intel_pt = IntelPT { ipt_handle, target_process_handle, - tid: self.tid, - previous_decode_head: 0, - ptcov_decoder, + pid: self.pid, + thread_id: None, + ptcov_decoders: HashMap::new(), + images: self.images, #[cfg(feature = "export_raw")] last_decode_trace: Vec::new(), }; @@ -545,26 +576,20 @@ impl<'a> IntelPTBuilder<'a> { let options = IptOptions::default(); let ipt_payload = StartProcessTrace { - process_handle: intel_pt.target_process_handle.inner.0 as u64, + process_handle: intel_pt.target_process_handle.0 as u64, options, }; let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); - let (_, out_size) = intel_pt.send_device_io_request(&input).unwrap(); + let (_, out_size) = intel_pt.send_device_io_request(&input)?; debug_assert_eq!(out_size, 0); - // todo: review this, since tid could be set later, filter setup can fail or be useless here + // todo: review this, since thread_id could be set later, filter setup can fail or be useless here intel_pt .set_ip_filters(&self.ip_filters) .map_err(|e| Error::os_error(e.into(), "Failed to set IntelPT ip filters"))?; Ok(intel_pt) } - #[must_use] - pub fn thread_id(mut self, thread_id: Option) -> Self { - self.tid = thread_id; - self - } - #[must_use] pub fn pid(mut self, pid: u32) -> Self { self.pid = pid; @@ -585,17 +610,25 @@ impl<'a> IntelPTBuilder<'a> { } } -#[derive(Debug)] -struct PtHandle { - inner: HANDLE, -} +fn process_thread_ids(pid: u32) -> windows::core::Result> { + let snapshot = unsafe { Owned::new(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)?) }; + let mut entry = THREADENTRY32 { + dwSize: size_of::() as u32, + ..Default::default() + }; + unsafe { Thread32First(*snapshot, &raw mut entry)? }; -impl Drop for PtHandle { - fn drop(&mut self) { - if let Err(err) = unsafe { CloseHandle(self.inner) } { - panic!("Failed to close handle: {err}"); + let mut thread_ids = Vec::new(); + loop { + if entry.th32OwnerProcessID == pid { + thread_ids.push(entry.th32ThreadID); + } + if unsafe { Thread32Next(*snapshot, &raw mut entry) }.is_err() { + break; } } + + Ok(thread_ids) } pub(crate) fn availability_in_windows() -> Result<(), String> { @@ -625,7 +658,7 @@ pub(crate) fn availability_in_windows() -> Result<(), String> { // .map(|pti| pti.configurable_address_ranges()) // } -fn open_ipt_handle() -> windows::core::Result { +fn open_ipt_handle() -> windows::core::Result> { let ipt_path = w!("\\??\\IPT\0"); unsafe { @@ -639,7 +672,7 @@ fn open_ipt_handle() -> windows::core::Result { None, ) } - .map(|h| PtHandle { inner: h }) + .map(|h| unsafe { Owned::new(h) }) } #[cfg(test)] diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs index 11973928466..37198d93585 100644 --- a/crates/libafl_intelpt/tests/integration_tests_windows.rs +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -74,7 +74,7 @@ fn intel_pt_trace_thread() { let worker_barrier = barrier.clone(); let worker = thread::spawn(|| worker_main(thread_id_sender, worker_barrier)); let worker_tid = thread_id_receiver.recv().unwrap(); - pt.set_tid(Some(worker_tid)); + pt.set_thread_id(Some(worker_tid)); pt.enable_tracing().unwrap(); barrier.wait(); diff --git a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs index 5bf667b8c2f..eb0df648100 100644 --- a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs +++ b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs @@ -1,4 +1,4 @@ -use std::{hint::black_box, num::NonZero, path::PathBuf, process, slice, time::Duration}; +use std::{hint::black_box, num::NonZero, panic, path::PathBuf, process, slice, time::Duration}; use libafl::{ corpus::{InMemoryCorpus, OnDiskCorpus}, @@ -21,6 +21,7 @@ use libafl::{ }; use libafl_bolts::{current_nanos, nonnull_raw_mut, rands::StdRand, tuples::tuple_list}; use proc_maps::get_process_maps; +#[cfg(windows)] use windows::Win32::System::Threading::GetCurrentThreadId; // Edge coverage map. @@ -103,11 +104,11 @@ pub fn main() { .collect::>(); // Pass the executable memory to the code responsible for Intel PT trace decoding - let pt = IntelPT::builder() - .images(&images) - .thread_id(Some(unsafe { GetCurrentThreadId() })) - .build() - .unwrap(); + #[cfg_attr(not(windows), expect(unused_mut))] + let mut pt = IntelPT::builder().images(&images).build().unwrap(); + #[cfg(windows)] + pt.set_thread_id(Some(unsafe { GetCurrentThreadId() })); + // Intel PT hook that will handle the setup of Intel PT for each execution and fill the map let pt_hook = unsafe { IntelPTHook::builder() From 63c4b06ea0be8f8be128a6101009591666fc7ade Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Wed, 10 Jun 2026 17:33:22 +0200 Subject: [PATCH 19/28] Remove Diagnostics::ToolHelp --- crates/libafl_intelpt/Cargo.toml | 1 - crates/libafl_intelpt/src/windows.rs | 47 ++++++++++------------------ 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index e5cebb3bd9e..0fd7446abff 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -44,7 +44,6 @@ perf-event-open-sys = { version = "5.0.0" } hashbrown = {workspace = true} ptcov = { version = "0.1.1", features = ["retc"] } windows = { workspace = true, features = [ - "Win32_System_Diagnostics_ToolHelp", "Win32_System_IO", "Win32_Storage_FileSystem", ] } diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows.rs index f38d6bc49a4..b5f93314605 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows.rs @@ -17,10 +17,6 @@ use windows::{ FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, }, System::{ - Diagnostics::ToolHelp::{ - CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, - Thread32Next, - }, IO::DeviceIoControl, Threading::{ GetCurrentProcessId, OpenProcess, OpenThread, PROCESS_QUERY_INFORMATION, @@ -265,10 +261,10 @@ struct ThreadCoverageDecoder<'a> { pub struct IntelPT<'a> { ipt_handle: Owned, target_process_handle: Owned, - pid: u32, thread_id: Option, ptcov_decoders: HashMap>, images: &'a [PtImage<'a>], + last_decode_threads: Vec, #[cfg(feature = "export_raw")] last_decode_trace: Vec, } @@ -324,16 +320,24 @@ impl<'a> IntelPT<'a> { self.toggle_tracing(IptInputType::PauseThreadTrace) } + // If the target thread_id is not set, this function will be a best effort based on the threads + // seen in the last decoding. Enumerating the threads for every iteration kills performances. + // If a new thread is spawn it is traced by default. If a thread ends, the reativation will fail + // with a log message but without returning the error. fn toggle_tracing(&mut self, input_type: IptInputType) -> windows::core::Result<()> { if let Some(thread_id) = self.thread_id { let mut thread_handle = unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) }; self.toggle_thread_tracing(input_type, &mut thread_handle)?; } else { - for thread_id in process_thread_ids(self.pid)? { + for thread_id in &self.last_decode_threads { let mut thread_handle = - unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) }; - self.toggle_thread_tracing(input_type, &mut thread_handle)?; + unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, *thread_id)?) }; + let _ = self + .toggle_thread_tracing(input_type, &mut thread_handle) + .inspect_err(|e| { + log::info!("Failed to toggle tracing for thread {thread_id}: {}", e); + }); } } @@ -377,6 +381,8 @@ impl<'a> IntelPT<'a> { where T: CoverageEntry, { + self.last_decode_threads.clear(); + // get trace let trace_size = self.get_trace_size()?; let mut trace_buffer: Vec = Vec::with_capacity(trace_size as usize); @@ -433,6 +439,8 @@ impl<'a> IntelPT<'a> { let inner_header = unsafe { slice.as_ptr().cast::().as_ref_unchecked() }; slice = &slice[size_of::()..]; + self.last_decode_threads.push(inner_header.thread_id as u32); + if self .thread_id .is_none_or(|thread_id| u64::from(thread_id) == inner_header.thread_id) @@ -565,10 +573,10 @@ impl<'a> IntelPTBuilder<'a> { let mut intel_pt = IntelPT { ipt_handle, target_process_handle, - pid: self.pid, thread_id: None, ptcov_decoders: HashMap::new(), images: self.images, + last_decode_threads: vec![], #[cfg(feature = "export_raw")] last_decode_trace: Vec::new(), }; @@ -610,27 +618,6 @@ impl<'a> IntelPTBuilder<'a> { } } -fn process_thread_ids(pid: u32) -> windows::core::Result> { - let snapshot = unsafe { Owned::new(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)?) }; - let mut entry = THREADENTRY32 { - dwSize: size_of::() as u32, - ..Default::default() - }; - unsafe { Thread32First(*snapshot, &raw mut entry)? }; - - let mut thread_ids = Vec::new(); - loop { - if entry.th32OwnerProcessID == pid { - thread_ids.push(entry.th32ThreadID); - } - if unsafe { Thread32Next(*snapshot, &raw mut entry) }.is_err() { - break; - } - } - - Ok(thread_ids) -} - pub(crate) fn availability_in_windows() -> Result<(), String> { let mut reasons = Vec::new(); From 6fcc4d447a54d65bc959aaa9f0008c49cc104ece Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Thu, 11 Jun 2026 11:11:13 +0200 Subject: [PATCH 20/28] Refactoring --- crates/libafl_intelpt/src/windows/ipt.rs | 311 ++++++++++++++++ .../src/{windows.rs => windows/mod.rs} | 336 ++---------------- 2 files changed, 347 insertions(+), 300 deletions(-) create mode 100644 crates/libafl_intelpt/src/windows/ipt.rs rename crates/libafl_intelpt/src/{windows.rs => windows/mod.rs} (58%) diff --git a/crates/libafl_intelpt/src/windows/ipt.rs b/crates/libafl_intelpt/src/windows/ipt.rs new file mode 100644 index 00000000000..79d9cb23424 --- /dev/null +++ b/crates/libafl_intelpt/src/windows/ipt.rs @@ -0,0 +1,311 @@ +/// ipt.sys struct definitions, conversion fns and constants +use core::ops::RangeInclusive; + +use arbitrary_int::{u3, u4}; +use bitbybit::bitfield; +use windows::Win32::Foundation::HANDLE; + +pub const TRACE_VERSION: u16 = 1; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct BufferVersion { + major: u32, + minor: u32, +} +const BUFFER_VERSION: BufferVersion = BufferVersion { major: 1, minor: 0 }; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ConfigureThreadAddressFilterRange { + thread_handle: HANDLE, + range_index: u32, + range_config: FilterRangeSettings, + start_address: u64, + end_address: u64, +} + +impl ConfigureThreadAddressFilterRange { + pub const fn new( + thread_handle: HANDLE, + range_index: u32, + filter: &RangeInclusive, + ) -> Self { + Self { + thread_handle, + range_index, + range_config: FilterRangeSettings::Ip, + start_address: *filter.start(), + end_address: *filter.end(), + } + } +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy)] +#[expect(dead_code, reason = "Not all the config are yet used/supported")] +enum FilterRangeSettings { + Disable = 0, + Ip = 1, + TraceStop = 2, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct GetProcessTrace { + trace_version: u16, + _padding: [u8; 6], + process_handle: u64, +} + +impl GetProcessTrace { + fn new(process_handle: HANDLE) -> Self { + Self { + trace_version: TRACE_VERSION, + _padding: [0u8; 6], + process_handle: process_handle.0 as u64, + } + } +} + +#[repr(C)] +pub struct InputBuffer { + version: BufferVersion, + input_type: InputType, + _padding: u32, + payload: InputPayload, +} + +impl InputBuffer { + const fn new(input_type: InputType, payload: InputPayload) -> Self { + InputBuffer { + version: BUFFER_VERSION, + input_type, + _padding: 0, + payload, + } + } + + pub fn get_process_trace(process_handle: HANDLE) -> Self { + Self::new( + InputType::GetProcessTrace, + GetProcessTrace::new(process_handle).into(), + ) + } + + pub fn get_process_trace_size(process_handle: HANDLE) -> Self { + Self::new( + InputType::GetProcessTraceSize, + GetProcessTrace::new(process_handle).into(), + ) + } + + pub fn pause_thread_trace(thread_handle: HANDLE) -> Self { + Self::new( + InputType::PauseThreadTrace, + PauseResumeThreadTrace::new(thread_handle).into(), + ) + } + + pub fn resume_thread_trace(thread_handle: HANDLE) -> Self { + Self::new( + InputType::ResumeThreadTrace, + PauseResumeThreadTrace::new(thread_handle).into(), + ) + } + + pub fn set_thread_ip_filter( + thread_handle: HANDLE, + range_index: u32, + filter: &RangeInclusive, + ) -> Self { + Self::new( + InputType::ConfigureThreadAddressFilterRange, + ConfigureThreadAddressFilterRange::new(thread_handle, range_index, filter).into(), + ) + } + + pub fn start_process_trace(process_handle: HANDLE, options: Options) -> Self { + Self::new( + InputType::StartProcessTrace, + StartProcessTrace { + process_handle: process_handle.0 as u64, + options, + } + .into(), + ) + } +} + +#[repr(C)] +union InputPayload { + get_trace: GetProcessTrace, + start_trace: StartProcessTrace, + // stop_trace: StopProcessTrace, + configure_thread_address_filter_range: ConfigureThreadAddressFilterRange, + // query_filter: QueryThreadFilter, + pause_resume_thread: PauseResumeThreadTrace, + _pad: [u8; 32], +} + +impl From for InputPayload { + fn from(value: ConfigureThreadAddressFilterRange) -> Self { + Self { + configure_thread_address_filter_range: value, + } + } +} + +impl From for InputPayload { + fn from(value: StartProcessTrace) -> Self { + Self { start_trace: value } + } +} + +impl From for InputPayload { + fn from(value: GetProcessTrace) -> Self { + Self { get_trace: value } + } +} + +impl From for InputPayload { + fn from(value: PauseResumeThreadTrace) -> Self { + Self { + pause_resume_thread: value, + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u32)] +#[expect(dead_code, reason = "Not all the commands are used")] +enum InputType { + GetTraceVersion = 0, + GetProcessTraceSize, + GetProcessTrace, + StartCoreTracing, + RegisterExtendedImageForTracing, + StartProcessTrace, + StopProcessTrace, + PauseThreadTrace, + ResumeThreadTrace, + QueryProcessTrace, + QueryCoreTrace, + StopTraceOnEachCore = 12, + ConfigureThreadAddressFilterRange, + QueryThreadAddressFilterRange, + QueryThreadTraceStopRangeEntered, +} + +#[derive(Debug)] +#[repr(u32)] +pub enum Ioctl { + Request = 0x220004, + ReadTrace = 0x220006, +} + +#[bitfield(u64)] +#[derive(Debug)] +pub struct Options { + #[bits(0..=3, rw)] + option_version: u4, + #[bits(4..=7, rw)] + timing_settings: u4, + /// As in `IA32_RTIT_CTL` + #[bits(8..=11, rw)] + mtc_frequency: u4, + #[bits(12..=15, rw)] + cyc_threshold: u4, + #[bits(16..=19, rw)] + topa_pages_pow2: u4, + /// Not relevant when tracing by process handle + #[bits(20..=22, rw)] + match_settings: u3, + #[bits(23..=23, rw)] + inherit: bool, + #[bits(24..=27, rw)] + mode_settings: u4, + #[bits(28..=63)] + reserved: u36, +} + +impl Options { + const VERSION: u4 = u4::new(1); +} + +impl Default for Options { + fn default() -> Self { + Self::builder() + .with_option_version(Self::VERSION) + .with_topa_pages_pow2(u4::new(4)) // 64 kB + .with_inherit(false) + .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg + .value + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct OutGetTraceSize { + trace_version: u16, + _padding: [u8; 6], + pub trace_size: u64, +} + +#[repr(C)] +pub union OutputBuffer { + // get_trace_version: OutGetTraceVersion, + pub get_trace_size: OutGetTraceSize, + // query_filter: OutQueryThreadFilter, + // pause_trace: OutPauseResumeTrace, + // resume_trace: OutPauseResumeTrace, + _pad: [u8; 24], +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct PauseResumeThreadTrace { + thread_handle: HANDLE, +} + +impl PauseResumeThreadTrace { + const fn new(thread_handle: HANDLE) -> Self { + PauseResumeThreadTrace { thread_handle } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct StartProcessTrace { + process_handle: u64, + options: Options, +} + +#[repr(C)] +#[derive(Debug)] +pub struct TraceData { + pub trace_version: u16, + pub valid_trace: u16, + pub trace_size: u32, +} + +#[repr(C, packed(4))] +#[derive(Debug)] +pub struct TraceHeader { + pub thread_id: u64, + timing_settings: u32, + mtc_frequency: u32, + frequency_to_tsc_ratio: u32, + pub ring_buffer_offset: u32, + pub trace_size: u32, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ipt_input_buffer_size() { + assert_eq!(size_of::(), 0x30); + } +} diff --git a/crates/libafl_intelpt/src/windows.rs b/crates/libafl_intelpt/src/windows/mod.rs similarity index 58% rename from crates/libafl_intelpt/src/windows.rs rename to crates/libafl_intelpt/src/windows/mod.rs index b5f93314605..0423df24eca 100644 --- a/crates/libafl_intelpt/src/windows.rs +++ b/crates/libafl_intelpt/src/windows/mod.rs @@ -1,10 +1,10 @@ +mod ipt; + use alloc::{string::String, vec::Vec}; use core::{fmt::Debug, mem::MaybeUninit, ops::RangeInclusive, ptr::slice_from_raw_parts_mut}; #[cfg(feature = "export_raw")] use std::string::ToString; -use arbitrary_int::{u3, u4}; -use bitbybit::bitfield; use hashbrown::HashMap; use libafl_bolts::Error; use ptcov::PtCoverageDecoderBuilder; @@ -29,227 +29,6 @@ use windows::{ use crate::utils::current_cpu; -#[derive(Debug)] -#[repr(u32)] -enum IptIoctl { - Request = 0x220004, - ReadTrace = 0x220006, -} - -#[derive(Debug, Clone, Copy)] -#[repr(u32)] -#[expect(dead_code, reason = "Not all the commands are used")] -enum IptInputType { - GetTraceVersion = 0, - GetProcessTraceSize, - GetProcessTrace, - StartCoreTracing, - RegisterExtendedImageForTracing, - StartProcessTrace, - StopProcessTrace, - PauseThreadTrace, - ResumeThreadTrace, - QueryProcessTrace, - QueryCoreTrace, - StopTraceOnEachCore = 12, - ConfigureThreadAddressFilterRange, - QueryThreadAddressFilterRange, - QueryThreadTraceStopRangeEntered, -} - -#[repr(u32)] -#[derive(Debug, Clone, Copy)] -enum IptFilterRangeSettings { - // Disable = 0, - Ip = 1, - // TraceStop = 2, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct ConfigureThreadAddressFilterRange { - thread_handle: HANDLE, - range_index: u32, - range_config: IptFilterRangeSettings, - start_address: u64, - end_address: u64, -} - -/// Layout (from winipt): -/// - `OptionVersion`: bits 0-3 (4 bits) - Must be set to 1 -/// - `TimingSettings`: bits 4-7 (4 bits) - `IPT_TIMING_SETTINGS` -/// - `MtcFrequency`: bits 8-11 (4 bits) - Bits 14:17 in `IA32_RTIT_CTL` -/// - `CycThreshold`: bits 12-15 (4 bits) - Bits 19:22 in `IA32_RTIT_CTL` -/// - `TopaPagesPow2`: bits 16-19 (4 bits) - Size of buffer as 4KB powers of 2 (4KB->128MB) -/// - `MatchSettings`: bits 20-22 (3 bits) - `IPT_MATCH_SETTINGS` -/// - Inherit: bit 23 (1 bit) - Children will be automatically traced -/// - `ModeSettings`: bits 24-27 (4 bits) - `IPT_MODE_SETTINGS` -/// - Reserved: bits 28-63 (36 bits) -#[bitfield(u64)] -#[derive(Debug)] -struct IptOptions { - #[bits(0..=3, rw)] - option_version: u4, - #[bits(4..=7, rw)] - timing_settings: u4, - #[bits(8..=11, rw)] - mtc_frequency: u4, - #[bits(12..=15, rw)] - cyc_threshold: u4, - #[bits(16..=19, rw)] - topa_pages_pow2: u4, - /// Not relevant when tracing by process handle - #[bits(20..=22, rw)] - match_settings: u3, - #[bits(23..=23, rw)] - inherit: bool, - #[bits(24..=27, rw)] - mode_settings: u4, - #[bits(28..=63)] - reserved: u36, -} - -impl Default for IptOptions { - fn default() -> Self { - Self::builder() - .with_option_version(Self::VERSION) - .with_topa_pages_pow2(u4::new(4)) // 64 kB - .with_inherit(false) - .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg - .value - } -} - -impl IptOptions { - const VERSION: u4 = u4::new(1); -} - -const IPT_TRACE_VERSION: u16 = 1; - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct StartProcessTrace { - process_handle: u64, - options: IptOptions, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct GetProcessTrace { - trace_version: u16, - _padding: [u8; 6], - process_handle: u64, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct PauseResumeThreadTrace { - thread_handle: HANDLE, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct IptBufferVersion { - major: u32, - minor: u32, -} -const IPT_BUFFER_VERSION: IptBufferVersion = IptBufferVersion { major: 1, minor: 0 }; - -#[repr(C)] -union IptInputPayload { - get_trace: GetProcessTrace, - start_trace: StartProcessTrace, - // stop_trace: StopProcessTrace, - configure_thread_address_filter_range: ConfigureThreadAddressFilterRange, - // query_filter: QueryThreadFilter, - pause_resume_thread: PauseResumeThreadTrace, - _pad: [u8; 32], -} - -impl From for IptInputPayload { - fn from(value: ConfigureThreadAddressFilterRange) -> Self { - Self { - configure_thread_address_filter_range: value, - } - } -} - -impl From for IptInputPayload { - fn from(value: StartProcessTrace) -> Self { - Self { start_trace: value } - } -} - -impl From for IptInputPayload { - fn from(value: GetProcessTrace) -> Self { - Self { get_trace: value } - } -} - -impl From for IptInputPayload { - fn from(value: PauseResumeThreadTrace) -> Self { - Self { - pause_resume_thread: value, - } - } -} - -#[repr(C)] -struct IptInputBuffer { - version: IptBufferVersion, - input_type: IptInputType, - _padding: u32, - payload: IptInputPayload, -} - -impl IptInputBuffer { - fn new(input_type: IptInputType, payload: IptInputPayload) -> Self { - IptInputBuffer { - version: IPT_BUFFER_VERSION, - input_type, - _padding: 0, - payload, - } - } -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct OutGetTraceSize { - trace_version: u16, - _padding: [u8; 6], - trace_size: u64, -} - -#[repr(C)] -union IptOutputBuffer { - // get_trace_version: OutGetTraceVersion, - get_trace_size: OutGetTraceSize, - // query_filter: OutQueryThreadFilter, - // pause_trace: OutPauseResumeTrace, - // resume_trace: OutPauseResumeTrace, - _pad: [u8; 24], -} - -#[repr(C)] -#[derive(Debug)] -struct IptTraceData { - trace_version: u16, - valid_trace: u16, - trace_size: u32, -} - -#[repr(C, packed(4))] -#[derive(Debug)] -struct IptTraceHeader { - thread_id: u64, - timing_settings: u32, - mtc_frequency: u32, - frequency_to_tsc_ratio: u32, - ring_buffer_offset: u32, - trace_size: u32, -} - #[derive(Debug)] struct ThreadCoverageDecoder<'a> { decoder: PtCoverageDecoder<'a>, @@ -295,17 +74,8 @@ impl<'a> IntelPT<'a> { }; for (i, filter) in filters.iter().enumerate() { - let ipt_payload = ConfigureThreadAddressFilterRange { - thread_handle: *thread_handle, - range_index: i.try_into()?, - range_config: IptFilterRangeSettings::Ip, - start_address: *filter.start(), - end_address: *filter.end(), - }; - let input = IptInputBuffer::new( - IptInputType::ConfigureThreadAddressFilterRange, - ipt_payload.into(), - ); + let input = + ipt::InputBuffer::set_thread_ip_filter(*thread_handle, i.try_into()?, filter); let (_, out_size) = self.send_device_io_request(&input)?; debug_assert_eq!(out_size, 0); } @@ -313,30 +83,30 @@ impl<'a> IntelPT<'a> { } pub fn enable_tracing(&mut self) -> windows::core::Result<()> { - self.toggle_tracing(IptInputType::ResumeThreadTrace) + self.toggle_tracing(true) } pub fn disable_tracing(&mut self) -> windows::core::Result<()> { - self.toggle_tracing(IptInputType::PauseThreadTrace) + self.toggle_tracing(false) } // If the target thread_id is not set, this function will be a best effort based on the threads // seen in the last decoding. Enumerating the threads for every iteration kills performances. // If a new thread is spawn it is traced by default. If a thread ends, the reativation will fail // with a log message but without returning the error. - fn toggle_tracing(&mut self, input_type: IptInputType) -> windows::core::Result<()> { + fn toggle_tracing(&mut self, enable: bool) -> windows::core::Result<()> { if let Some(thread_id) = self.thread_id { let mut thread_handle = unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) }; - self.toggle_thread_tracing(input_type, &mut thread_handle)?; + self.toggle_thread_tracing(&mut thread_handle, enable)?; } else { for thread_id in &self.last_decode_threads { let mut thread_handle = unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, *thread_id)?) }; let _ = self - .toggle_thread_tracing(input_type, &mut thread_handle) + .toggle_thread_tracing(&mut thread_handle, enable) .inspect_err(|e| { - log::info!("Failed to toggle tracing for thread {thread_id}: {}", e); + log::info!("Failed to toggle tracing for thread {thread_id}: {e}"); }); } } @@ -346,19 +116,14 @@ impl<'a> IntelPT<'a> { fn toggle_thread_tracing( &self, - input_type: IptInputType, thread_handle: &mut HANDLE, + enable: bool, ) -> windows::core::Result<()> { - let ipt_payload = PauseResumeThreadTrace { - thread_handle: *thread_handle, + let input = if enable { + ipt::InputBuffer::resume_thread_trace(*thread_handle) + } else { + ipt::InputBuffer::pause_thread_trace(*thread_handle) }; - let input = IptInputBuffer::new( - input_type, - IptInputPayload { - pause_resume_thread: ipt_payload, - }, - ); - let (_, out_size) = self.send_device_io_request(&input)?; debug_assert_eq!(out_size, 24); @@ -387,25 +152,15 @@ impl<'a> IntelPT<'a> { let trace_size = self.get_trace_size()?; let mut trace_buffer: Vec = Vec::with_capacity(trace_size as usize); - let ipt_payload = GetProcessTrace { - trace_version: IPT_TRACE_VERSION, - _padding: [0u8; 6], - process_handle: self.target_process_handle.0 as u64, - }; - let input = IptInputBuffer::new( - IptInputType::GetProcessTrace, - IptInputPayload { - get_trace: ipt_payload, - }, - ); + let input = ipt::InputBuffer::get_process_trace(*self.target_process_handle); let mut out_size = 0; unsafe { DeviceIoControl( *self.ipt_handle, - IptIoctl::ReadTrace as u32, + ipt::Ioctl::ReadTrace as u32, Some(&raw const input as *const core::ffi::c_void), - size_of::() as u32, + size_of::() as u32, Some(trace_buffer.as_mut_ptr() as *mut core::ffi::c_void), trace_buffer.capacity() as u32, Some(&raw mut out_size), @@ -419,26 +174,26 @@ impl<'a> IntelPT<'a> { trace_buffer.set_len(out_size as usize); }; - debug_assert!(trace_buffer.as_ptr().cast::().is_aligned()); + debug_assert!(trace_buffer.as_ptr().cast::().is_aligned()); let trace_data = unsafe { trace_buffer .as_ptr() - .cast::() + .cast::() .as_ref_unchecked() }; - debug_assert_eq!(trace_data.trace_version, IPT_TRACE_VERSION); + debug_assert_eq!(trace_data.trace_version, ipt::TRACE_VERSION); if trace_data.valid_trace == 0 { return Err(Error::runtime( "Intel PT: failed to get a valid trace from ipt.sys", )); } - let mut slice = &trace_buffer[size_of::()..]; - while slice.len() >= size_of::() { - debug_assert!(slice.as_ptr().cast::().is_aligned()); + let mut slice = &trace_buffer[size_of::()..]; + while slice.len() >= size_of::() { + debug_assert!(slice.as_ptr().cast::().is_aligned()); let inner_header = - unsafe { slice.as_ptr().cast::().as_ref_unchecked() }; - slice = &slice[size_of::()..]; + unsafe { slice.as_ptr().cast::().as_ref_unchecked() }; + slice = &slice[size_of::()..]; self.last_decode_threads.push(inner_header.thread_id as u32); if self @@ -488,34 +243,29 @@ impl<'a> IntelPT<'a> { } fn get_trace_size(&self) -> windows::core::Result { - let ipt_payload = GetProcessTrace { - trace_version: IPT_TRACE_VERSION, - _padding: [0; 6], - process_handle: self.target_process_handle.0 as u64, - }; - let input = IptInputBuffer::new(IptInputType::GetProcessTraceSize, ipt_payload.into()); + let input = ipt::InputBuffer::get_process_trace_size(*self.target_process_handle); let (out, out_size) = self.send_device_io_request(&input)?; - debug_assert_eq!(out_size, size_of::() as u32); + debug_assert_eq!(out_size, size_of::() as u32); Ok(unsafe { out.get_trace_size.trace_size }) } fn send_device_io_request( &self, - input: &IptInputBuffer, - ) -> windows::core::Result<(IptOutputBuffer, u32)> { - let mut out = MaybeUninit::::uninit(); + input: &ipt::InputBuffer, + ) -> windows::core::Result<(ipt::OutputBuffer, u32)> { + let mut out = MaybeUninit::::uninit(); let mut out_size = 0; unsafe { DeviceIoControl( *self.ipt_handle, - IptIoctl::Request as u32, + ipt::Ioctl::Request as u32, Some((&raw const *input).cast()), - size_of::() as u32, + size_of::() as u32, Some(out.as_mut_ptr().cast()), - size_of::() as u32, + size_of::() as u32, Some(&raw mut out_size), None, ) @@ -581,13 +331,9 @@ impl<'a> IntelPTBuilder<'a> { last_decode_trace: Vec::new(), }; - let options = IptOptions::default(); + let options = ipt::Options::default(); - let ipt_payload = StartProcessTrace { - process_handle: intel_pt.target_process_handle.0 as u64, - options, - }; - let input = IptInputBuffer::new(IptInputType::StartProcessTrace, ipt_payload.into()); + let input = ipt::InputBuffer::start_process_trace(*intel_pt.target_process_handle, options); let (_, out_size) = intel_pt.send_device_io_request(&input)?; debug_assert_eq!(out_size, 0); @@ -661,13 +407,3 @@ fn open_ipt_handle() -> windows::core::Result> { } .map(|h| unsafe { Owned::new(h) }) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn ipt_input_buffer_size() { - assert_eq!(size_of::(), 0x30); - } -} From d213287cf3e74df3dd21f45f5cd25c96a18b78ed Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 12 Jun 2026 10:17:34 +0200 Subject: [PATCH 21/28] Simpler integration test, lints --- crates/libafl_intelpt/src/windows/mod.rs | 10 ++- .../tests/integration_tests_windows.rs | 65 +++++++------------ 2 files changed, 31 insertions(+), 44 deletions(-) diff --git a/crates/libafl_intelpt/src/windows/mod.rs b/crates/libafl_intelpt/src/windows/mod.rs index 0423df24eca..5469c2183f9 100644 --- a/crates/libafl_intelpt/src/windows/mod.rs +++ b/crates/libafl_intelpt/src/windows/mod.rs @@ -102,7 +102,14 @@ impl<'a> IntelPT<'a> { } else { for thread_id in &self.last_decode_threads { let mut thread_handle = - unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, *thread_id)?) }; + match unsafe { OpenThread(THREAD_GET_CONTEXT, false, *thread_id) } { + Ok(handle) => unsafe { Owned::new(handle) }, + Err(e) => { + log::info!("Failed to toggle tracing for thread {thread_id}: {e}"); + continue; + } + }; + let _ = self .toggle_thread_tracing(&mut thread_handle, enable) .inspect_err(|e| { @@ -234,6 +241,7 @@ impl<'a> IntelPT<'a> { if let Err(e) = ptcov_decoder.decoder.coverage(trace, coverage) { log::warn!("PT trace decoding to coverage failed: {e:?}"); + coverage.fill(0.into()); } } slice = &slice[inner_header.trace_size as usize..]; diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs index 37198d93585..f40d4877b11 100644 --- a/crates/libafl_intelpt/tests/integration_tests_windows.rs +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -1,15 +1,10 @@ #![cfg(feature = "std")] #![cfg(target_os = "windows")] -use std::{ - arch::asm, - process, slice, - sync::{ - Arc, Barrier, - mpsc::{Sender, channel}, - }, - thread, -}; +extern crate alloc; + +use std::{arch::asm, process}; +use alloc::slice; use libafl_intelpt::{IntelPT, availability}; use log::LevelFilter; @@ -17,30 +12,8 @@ use proc_maps::get_process_maps; use ptcov::PtImage; use windows::Win32::System::Threading::GetCurrentThreadId; -fn worker_main(thread_id_sender: Sender, barrier: Arc) -> u32 { - let tid = unsafe { GetCurrentThreadId() }; - thread_id_sender.send(tid).unwrap(); - - barrier.wait(); - - let mut count = 0; - unsafe { - asm!( - "2:", - "add {0:r}, 1", - "cmp {0:r}, 255", - "jle 2b", - inout(reg) count, - options(nostack) - ); - } - - barrier.wait(); - count -} - #[test] -fn intel_pt_trace_thread() { +fn intel_pt_trace_loop() { let _ = env_logger::builder() .is_test(true) .filter_level(LevelFilter::Trace) @@ -68,15 +41,24 @@ fn intel_pt_trace_thread() { .images(&images) .build() .expect("Failed to create IntelPT for worker thread"); - - let (thread_id_sender, thread_id_receiver) = channel(); - let barrier = Arc::new(Barrier::new(2)); - let worker_barrier = barrier.clone(); - let worker = thread::spawn(|| worker_main(thread_id_sender, worker_barrier)); - let worker_tid = thread_id_receiver.recv().unwrap(); - pt.set_thread_id(Some(worker_tid)); + let tid = unsafe { GetCurrentThreadId() }; + pt.set_thread_id(Some(tid)); pt.enable_tracing().unwrap(); - barrier.wait(); + + let mut count = 0; + unsafe { + asm!( + "2:", + "add {0:r}, 1", + "cmp {0:r}, 255", + "jle 2b", + inout(reg) count, + options(nostack) + ); + } + let _ = count; + + pt.disable_tracing().unwrap(); let mut map = vec![0u16; 0x10_00]; pt.decode_traces_into_map(map.as_mut_ptr(), map.len()) @@ -87,7 +69,4 @@ fn intel_pt_trace_thread() { assembly_jump_id.is_some(), "Assembly jumps not found in traces" ); - - barrier.wait(); - worker.join().expect("Worker thread panicked"); } From 6d7ff31e69fb257e392c1dde6ae0f1cecdfb88d9 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Fri, 12 Jun 2026 17:24:34 +0200 Subject: [PATCH 22/28] Do not set filters from builder --- crates/libafl_intelpt/src/windows/mod.rs | 42 +++++++------------ .../tests/integration_tests_windows.rs | 2 +- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/crates/libafl_intelpt/src/windows/mod.rs b/crates/libafl_intelpt/src/windows/mod.rs index 5469c2183f9..c8586568b66 100644 --- a/crates/libafl_intelpt/src/windows/mod.rs +++ b/crates/libafl_intelpt/src/windows/mod.rs @@ -1,3 +1,7 @@ +/// Intel Processor Trace code using the `ipt.sys` Windows driver. +/// +/// This code is heavily inspired by `winipt` by Alex Ionescu and its other authors. +/// Credits also go to Frederic Kah and Justin Avril, who drafted the initial implementation. mod ipt; use alloc::{string::String, vec::Vec}; @@ -11,7 +15,7 @@ use ptcov::PtCoverageDecoderBuilder; pub use ptcov::{CoverageEntry, PtCoverageDecoder, PtImage}; use windows::{ Win32::{ - Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + Foundation::HANDLE, Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, @@ -65,12 +69,12 @@ impl<'a> IntelPT<'a> { /// /// Only instructions in `filters` ranges will be traced. pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> windows::core::Result<()> { - let thread_handle = unsafe { - Owned::new(if let Some(thread_id) = self.thread_id { - OpenThread(THREAD_GET_CONTEXT, false, thread_id)? - } else { - INVALID_HANDLE_VALUE // TODO apply to all threads? - }) + let thread_handle = if let Some(thread_id) = self.thread_id { + unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) } + } else { + todo!( + "Return a libafl error here? in general return libafl errors instead of windows err" + ) }; for (i, filter) in filters.iter().enumerate() { @@ -300,7 +304,6 @@ impl<'a> IntelPT<'a> { #[derive(Debug)] pub struct IntelPTBuilder<'a> { - ip_filters: Vec>, images: &'a [PtImage<'a>], pid: u32, } @@ -308,27 +311,21 @@ pub struct IntelPTBuilder<'a> { impl Default for IntelPTBuilder<'_> { fn default() -> Self { let pid = unsafe { GetCurrentProcessId() }; - Self { - ip_filters: Vec::new(), - images: &[], - pid, - } + Self { images: &[], pid } } } impl<'a> IntelPTBuilder<'a> { pub fn build(self) -> Result, Error> { - // todo: is there a way to start this all set to trace but "paused" as in linux? // todo: better error suggesting to start the kernel driver let ipt_handle = open_ipt_handle()?; let target_process_handle = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, self.pid) .map(|h| Owned::new(h)) - } - .map_err(|e| Error::os_error(e.into(), "Failed to get target process handle"))?; + }?; - let mut intel_pt = IntelPT { + let intel_pt = IntelPT { ipt_handle, target_process_handle, thread_id: None, @@ -345,10 +342,6 @@ impl<'a> IntelPTBuilder<'a> { let (_, out_size) = intel_pt.send_device_io_request(&input)?; debug_assert_eq!(out_size, 0); - // todo: review this, since thread_id could be set later, filter setup can fail or be useless here - intel_pt - .set_ip_filters(&self.ip_filters) - .map_err(|e| Error::os_error(e.into(), "Failed to set IntelPT ip filters"))?; Ok(intel_pt) } @@ -358,13 +351,6 @@ impl<'a> IntelPTBuilder<'a> { self } - #[must_use] - /// Set filters based on Instruction Pointer (IP) - pub fn ip_filters(mut self, filters: Vec>) -> Self { - self.ip_filters = filters; - self - } - #[must_use] pub fn images(mut self, images: &'a [PtImage<'_>]) -> Self { self.images = images; diff --git a/crates/libafl_intelpt/tests/integration_tests_windows.rs b/crates/libafl_intelpt/tests/integration_tests_windows.rs index f40d4877b11..023be8831ce 100644 --- a/crates/libafl_intelpt/tests/integration_tests_windows.rs +++ b/crates/libafl_intelpt/tests/integration_tests_windows.rs @@ -3,8 +3,8 @@ extern crate alloc; -use std::{arch::asm, process}; use alloc::slice; +use std::{arch::asm, process}; use libafl_intelpt::{IntelPT, availability}; use log::LevelFilter; From c339e422aebb9df4e04a804cf5de11279062f2f0 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Mon, 15 Jun 2026 10:25:45 +0200 Subject: [PATCH 23/28] Taplo --- crates/libafl_intelpt/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index 0fd7446abff..1149ea6c4ac 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -41,7 +41,7 @@ caps = { version = "0.5.5" } perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] -hashbrown = {workspace = true} +hashbrown = { workspace = true } ptcov = { version = "0.1.1", features = ["retc"] } windows = { workspace = true, features = [ "Win32_System_IO", From aad71604447c5b545225b3b31b1cddbe771e63aa Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Mon, 15 Jun 2026 10:37:44 +0200 Subject: [PATCH 24/28] fmt --- crates/libafl/src/executors/hooks/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/libafl/src/executors/hooks/mod.rs b/crates/libafl/src/executors/hooks/mod.rs index 0ce8d952ba9..9430516f478 100644 --- a/crates/libafl/src/executors/hooks/mod.rs +++ b/crates/libafl/src/executors/hooks/mod.rs @@ -21,7 +21,11 @@ pub mod inprocess; pub mod timer; /// Intel Processor Trace (PT) -#[cfg(all(feature = "intel_pt", any(windows, target_os = "linux"), target_arch = "x86_64"))] +#[cfg(all( + feature = "intel_pt", + any(windows, target_os = "linux"), + target_arch = "x86_64" +))] pub mod intel_pt; /// The hook that runs before and after the executor runs the target From a6f21bb7179445e354f5435f38ac08c1970afb5d Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Mon, 15 Jun 2026 18:03:10 +0200 Subject: [PATCH 25/28] Fix export_raw --- crates/libafl_intelpt/src/windows/ipt.rs | 8 +++++++- crates/libafl_intelpt/src/windows/mod.rs | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/libafl_intelpt/src/windows/ipt.rs b/crates/libafl_intelpt/src/windows/ipt.rs index 79d9cb23424..c35d119f4c5 100644 --- a/crates/libafl_intelpt/src/windows/ipt.rs +++ b/crates/libafl_intelpt/src/windows/ipt.rs @@ -141,7 +141,7 @@ impl InputBuffer { union InputPayload { get_trace: GetProcessTrace, start_trace: StartProcessTrace, - // stop_trace: StopProcessTrace, + stop_trace: StopProcessTrace, configure_thread_address_filter_range: ConfigureThreadAddressFilterRange, // query_filter: QueryThreadFilter, pause_resume_thread: PauseResumeThreadTrace, @@ -281,6 +281,12 @@ struct StartProcessTrace { options: Options, } +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct StopProcessTrace { + process_handle: u64, +} + #[repr(C)] #[derive(Debug)] pub struct TraceData { diff --git a/crates/libafl_intelpt/src/windows/mod.rs b/crates/libafl_intelpt/src/windows/mod.rs index c8586568b66..6cb9b3f37e2 100644 --- a/crates/libafl_intelpt/src/windows/mod.rs +++ b/crates/libafl_intelpt/src/windows/mod.rs @@ -136,7 +136,7 @@ impl<'a> IntelPT<'a> { ipt::InputBuffer::pause_thread_trace(*thread_handle) }; let (_, out_size) = self.send_device_io_request(&input)?; - debug_assert_eq!(out_size, 24); + debug_assert_eq!(out_size as usize, size_of::()); Ok(()) } @@ -238,7 +238,7 @@ impl<'a> IntelPT<'a> { #[cfg(feature = "export_raw")] { - self.last_decode_trace.extend(slice); + self.last_decode_trace.extend(trace); } let coverage = unsafe { &mut *slice_from_raw_parts_mut(map_ptr, map_len) }; @@ -315,6 +315,7 @@ impl Default for IntelPTBuilder<'_> { } } +// todo Consider removing the builder entirely?? impl<'a> IntelPTBuilder<'a> { pub fn build(self) -> Result, Error> { // todo: better error suggesting to start the kernel driver From fc4b50ca845cb436326efa0a0f1236d6d795404c Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Tue, 16 Jun 2026 17:10:31 +0200 Subject: [PATCH 26/28] Lints --- crates/libafl_intelpt/src/windows/mod.rs | 63 ++++++++++--------- .../intel_pt_baby_fuzzer/src/main.rs | 2 +- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/crates/libafl_intelpt/src/windows/mod.rs b/crates/libafl_intelpt/src/windows/mod.rs index 6cb9b3f37e2..c1d904392a9 100644 --- a/crates/libafl_intelpt/src/windows/mod.rs +++ b/crates/libafl_intelpt/src/windows/mod.rs @@ -13,6 +13,7 @@ use hashbrown::HashMap; use libafl_bolts::Error; use ptcov::PtCoverageDecoderBuilder; pub use ptcov::{CoverageEntry, PtCoverageDecoder, PtImage}; +use raw_cpuid::CpuId; use windows::{ Win32::{ Foundation::HANDLE, @@ -68,29 +69,36 @@ impl<'a> IntelPT<'a> { /// Set filters based on Instruction Pointer (IP) /// /// Only instructions in `filters` ranges will be traced. - pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> windows::core::Result<()> { + /// `thread_id` must be set in order to set the filters, otherwise this function will return an + /// error. + pub fn set_ip_filters(&mut self, filters: &[RangeInclusive]) -> Result<(), Error> { let thread_handle = if let Some(thread_id) = self.thread_id { unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) } } else { - todo!( - "Return a libafl error here? in general return libafl errors instead of windows err" - ) + return Err(Error::unsupported( + "IP filtering requires the `thread_id` to be set!", + )); }; for (i, filter) in filters.iter().enumerate() { let input = ipt::InputBuffer::set_thread_ip_filter(*thread_handle, i.try_into()?, filter); - let (_, out_size) = self.send_device_io_request(&input)?; + let (_, out_size) = self.send_device_io_request(&input).map_err(|e| { + Error::unsupported(format!( + "Failed to set IP filters: {e}. Number of supported filters: {}", + nr_addr_filters().unwrap_or_default() + )) + })?; debug_assert_eq!(out_size, 0); } Ok(()) } - pub fn enable_tracing(&mut self) -> windows::core::Result<()> { + pub fn enable_tracing(&mut self) -> Result<(), Error> { self.toggle_tracing(true) } - pub fn disable_tracing(&mut self) -> windows::core::Result<()> { + pub fn disable_tracing(&mut self) -> Result<(), Error> { self.toggle_tracing(false) } @@ -98,7 +106,7 @@ impl<'a> IntelPT<'a> { // seen in the last decoding. Enumerating the threads for every iteration kills performances. // If a new thread is spawn it is traced by default. If a thread ends, the reativation will fail // with a log message but without returning the error. - fn toggle_tracing(&mut self, enable: bool) -> windows::core::Result<()> { + fn toggle_tracing(&mut self, enable: bool) -> Result<(), Error> { if let Some(thread_id) = self.thread_id { let mut thread_handle = unsafe { Owned::new(OpenThread(THREAD_GET_CONTEXT, false, thread_id)?) }; @@ -125,11 +133,7 @@ impl<'a> IntelPT<'a> { Ok(()) } - fn toggle_thread_tracing( - &self, - thread_handle: &mut HANDLE, - enable: bool, - ) -> windows::core::Result<()> { + fn toggle_thread_tracing(&self, thread_handle: &mut HANDLE, enable: bool) -> Result<(), Error> { let input = if enable { ipt::InputBuffer::resume_thread_trace(*thread_handle) } else { @@ -318,8 +322,7 @@ impl Default for IntelPTBuilder<'_> { // todo Consider removing the builder entirely?? impl<'a> IntelPTBuilder<'a> { pub fn build(self) -> Result, Error> { - // todo: better error suggesting to start the kernel driver - let ipt_handle = open_ipt_handle()?; + let ipt_handle = open_ipt_handle().map_err(Error::unsupported)?; let target_process_handle = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, self.pid) @@ -363,11 +366,7 @@ pub(crate) fn availability_in_windows() -> Result<(), String> { let mut reasons = Vec::new(); if let Err(e) = open_ipt_handle() { - let err = format!( - "Failed to open IPT device: {e}; \n\ - Make sure the ipt service is running with `sc start ipt` from an admin shell." - ); - reasons.push(err); + reasons.push(e); } if reasons.is_empty() { @@ -377,16 +376,16 @@ pub(crate) fn availability_in_windows() -> Result<(), String> { } } -// /// Number of address filters available on the running CPU -// fn nr_addr_filters() -> Result { -// let cpuid = CpuId::new(); -// cpuid -// .get_processor_trace_info() -// .ok_or("Failed to read CPU Processor Trace Info") -// .map(|pti| pti.configurable_address_ranges()) -// } +/// Number of address filters available on the running CPU +fn nr_addr_filters() -> Result { + let cpuid = CpuId::new(); + cpuid + .get_processor_trace_info() + .ok_or("Failed to read CPU Processor Trace Info") + .map(|pti| pti.configurable_address_ranges()) +} -fn open_ipt_handle() -> windows::core::Result> { +fn open_ipt_handle() -> Result, String> { let ipt_path = w!("\\??\\IPT\0"); unsafe { @@ -401,4 +400,10 @@ fn open_ipt_handle() -> windows::core::Result> { ) } .map(|h| unsafe { Owned::new(h) }) + .map_err(|e| { + format!( + "Failed to open IPT device: {e}; \n\ + Make sure the ipt service is running with `sc start ipt` from an admin shell." + ) + }) } diff --git a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs index eb0df648100..50917744ef3 100644 --- a/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs +++ b/fuzzers/binary_only/intel_pt_baby_fuzzer/src/main.rs @@ -1,4 +1,4 @@ -use std::{hint::black_box, num::NonZero, panic, path::PathBuf, process, slice, time::Duration}; +use std::{hint::black_box, num::NonZero, path::PathBuf, process, slice, time::Duration}; use libafl::{ corpus::{InMemoryCorpus, OnDiskCorpus}, From 437df0b54162a162861b504f36ded5bb81bb5173 Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Mon, 6 Jul 2026 14:32:29 +0200 Subject: [PATCH 27/28] Bump ptcov to 0.1.2 --- crates/libafl_intelpt/Cargo.toml | 4 ++-- crates/libafl_intelpt/src/windows/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index 1149ea6c4ac..144cee6bbd5 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -33,7 +33,7 @@ libc = { workspace = true } log = { workspace = true } num_enum = { workspace = true, default-features = false } num-traits = { workspace = true, default-features = false } -ptcov = { version = "0.1.1" } +ptcov = { version = "0.1.2" } raw-cpuid = { version = "11.1.0" } [target.'cfg(target_os = "linux" )'.dependencies] @@ -42,7 +42,7 @@ perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] hashbrown = { workspace = true } -ptcov = { version = "0.1.1", features = ["retc"] } +ptcov = { version = "0.1.2", features = ["retc"] } windows = { workspace = true, features = [ "Win32_System_IO", "Win32_Storage_FileSystem", diff --git a/crates/libafl_intelpt/src/windows/mod.rs b/crates/libafl_intelpt/src/windows/mod.rs index c1d904392a9..b4d380ef799 100644 --- a/crates/libafl_intelpt/src/windows/mod.rs +++ b/crates/libafl_intelpt/src/windows/mod.rs @@ -248,7 +248,7 @@ impl<'a> IntelPT<'a> { let coverage = unsafe { &mut *slice_from_raw_parts_mut(map_ptr, map_len) }; if let Err(e) = ptcov_decoder.decoder.coverage(trace, coverage) { - log::warn!("PT trace decoding to coverage failed: {e:?}"); + log::warn!("PT trace decoding to coverage failed: {e:x?}"); coverage.fill(0.into()); } } From d1c2cea3b631d14d12baacd583eea9fd2fa0b7cc Mon Sep 17 00:00:00 2001 From: Marcondiro Date: Thu, 9 Jul 2026 11:05:32 +0200 Subject: [PATCH 28/28] Fix export raw, increase default buffer size --- crates/libafl_intelpt/Cargo.toml | 6 ++++-- crates/libafl_intelpt/src/windows/ipt.rs | 2 +- crates/libafl_intelpt/src/windows/mod.rs | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/libafl_intelpt/Cargo.toml b/crates/libafl_intelpt/Cargo.toml index 144cee6bbd5..497dfa92f45 100644 --- a/crates/libafl_intelpt/Cargo.toml +++ b/crates/libafl_intelpt/Cargo.toml @@ -33,7 +33,7 @@ libc = { workspace = true } log = { workspace = true } num_enum = { workspace = true, default-features = false } num-traits = { workspace = true, default-features = false } -ptcov = { version = "0.1.2" } +ptcov = { git = "https://github.com/Marcondiro/ptcov", branch = "fix_pgd" } raw-cpuid = { version = "11.1.0" } [target.'cfg(target_os = "linux" )'.dependencies] @@ -42,7 +42,9 @@ perf-event-open-sys = { version = "5.0.0" } [target.'cfg(target_os = "windows" )'.dependencies] hashbrown = { workspace = true } -ptcov = { version = "0.1.2", features = ["retc"] } +ptcov = { features = [ + "retc", +], git = "https://github.com/Marcondiro/ptcov", branch = "fix_pgd" } windows = { workspace = true, features = [ "Win32_System_IO", "Win32_Storage_FileSystem", diff --git a/crates/libafl_intelpt/src/windows/ipt.rs b/crates/libafl_intelpt/src/windows/ipt.rs index c35d119f4c5..66d86f5d6ef 100644 --- a/crates/libafl_intelpt/src/windows/ipt.rs +++ b/crates/libafl_intelpt/src/windows/ipt.rs @@ -237,7 +237,7 @@ impl Default for Options { fn default() -> Self { Self::builder() .with_option_version(Self::VERSION) - .with_topa_pages_pow2(u4::new(4)) // 64 kB + .with_topa_pages_pow2(u4::new(5)) // 128 kB .with_inherit(false) .with_mode_settings(u4::new(0)) // todo: better understand IPT_MODE_SETTINGS difference between Ctl and Reg .value diff --git a/crates/libafl_intelpt/src/windows/mod.rs b/crates/libafl_intelpt/src/windows/mod.rs index b4d380ef799..b3ed65baf70 100644 --- a/crates/libafl_intelpt/src/windows/mod.rs +++ b/crates/libafl_intelpt/src/windows/mod.rs @@ -162,6 +162,10 @@ impl<'a> IntelPT<'a> { T: CoverageEntry, { self.last_decode_threads.clear(); + #[cfg(feature = "export_raw")] + { + self.last_decode_trace.clear(); + } // get trace let trace_size = self.get_trace_size()?;