Skip to content

Commit

Permalink
Fixing Frida ASAN tests on Windows (AFLplusplus#2299)
Browse files Browse the repository at this point in the history
* libafl_frida unit tests passing with ASAN

* Clippy+fmt

* Clippy

* Setup VS environment before building
  • Loading branch information
mkravchik authored Jun 11, 2024
1 parent df40db5 commit 03d8d2e
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 28 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ jobs:
profile: minimal
toolchain: stable
- uses: actions/checkout@v3
- uses: ./.github/workflows/windows-tester-prepare
- uses: Swatinem/rust-cache@v2
- name: Run real clippy, not the fake one
shell: pwsh
Expand Down
4 changes: 3 additions & 1 deletion libafl_frida/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ backtrace = { version = "0.3", default-features = false, features = [
"serde",
] }
num-traits = "0.2"
ahash = "0.8"
ahash = "^0.8" # fetch the latest
paste = "1.0"
log = "0.4.20"
mmap-rs = "0.6.0"
Expand All @@ -104,3 +104,5 @@ winsafe = {version = "0.0.21", features = ["kernel"]}
serial_test = { version = "3", default-features = false, features = ["logging"] }
clap = {version = "4.5", features = ["derive"]}
libloading = "0.8"
mimalloc = { version = "*", default-features = false }
dlmalloc ={version = "0.2.6", features = ["global"]}
18 changes: 16 additions & 2 deletions libafl_frida/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fn main() {
}

let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();

// Force linking against libc++
if target_family == "unix" {
println!("cargo:rustc-link-lib=dylib=c++");
Expand All @@ -23,7 +24,7 @@ fn main() {
if target_family == "windows" {
let compiler = cc::Build::new()
.cpp(true)
.file("test_harness.a")
.file("test_harness.cpp")
.get_compiler();
let mut cmd = std::process::Command::new(compiler.path());
let cmd = cmd
Expand All @@ -46,7 +47,20 @@ fn main() {
std::env::var("HOME").unwrap()
));
cmd.arg("/dll").arg("/OUT:test_harness.dll");
cmd.status().expect("Failed to link test_harness.dll");
let output = cmd.output().expect("Failed to link test_harness.dll");
let output_str = format!(
"{:?}\nstatus: {}\nstdout: {}\nstderr: {}",
cmd,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);

// std::fs::write("compiler_output.txt", output_str.clone()).expect("Unable to write file");
assert!(output.status.success(),
"Failed to link test_harness.dll\n {:?}",
output_str.as_str()
);
} else {
let compiler = cc::Build::new()
.cpp(true)
Expand Down
27 changes: 22 additions & 5 deletions libafl_frida/src/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ impl Allocator {
#[must_use]
#[allow(clippy::missing_safety_doc)]
pub unsafe fn alloc(&mut self, size: usize, _alignment: usize) -> *mut c_void {
log::trace!("alloc");
let mut is_malloc_zero = false;
let size = if size == 0 {
is_malloc_zero = true;
Expand Down Expand Up @@ -242,6 +243,7 @@ impl Allocator {
/// Releases the allocation at the given address.
#[allow(clippy::missing_safety_doc)]
pub unsafe fn release(&mut self, ptr: *mut c_void) {
log::trace!("release {:?}", ptr);
let Some(metadata) = self.allocations.get_mut(&(ptr as usize)) else {
if !ptr.is_null() {
AsanErrors::get_mut_blocking()
Expand Down Expand Up @@ -375,9 +377,14 @@ impl Allocator {
unpoison: bool,
) -> (usize, usize) {
let shadow_mapping_start = map_to_shadow!(self, start);

log::trace!("map_shadow_for_region: {:x}, {:x}", start, end);
let shadow_start = self.round_down_to_page(shadow_mapping_start);
let shadow_end = self.round_up_to_page((end - start) / 8 + self.page_size + shadow_start);
log::trace!(
"map_shadow_for_region: shadow_start {:x}, shadow_end {:x}",
shadow_start,
shadow_end
);
if self.using_pre_allocated_shadow_mapping {
let mut newly_committed_regions = Vec::new();
for gap in self.shadow_pages.gaps(&(shadow_start..shadow_end)) {
Expand Down Expand Up @@ -406,6 +413,11 @@ impl Allocator {
}
}
for newly_committed_region in newly_committed_regions {
log::trace!(
"committed shadow pages: start {:x}, end {:x}",
newly_committed_region.start(),
newly_committed_region.end()
);
self.shadow_pages
.insert(newly_committed_region.start()..newly_committed_region.end());
self.mappings
Expand Down Expand Up @@ -549,21 +561,20 @@ impl Allocator {
}
}

/// Unpoison all the memory that is currently mapped with read/write permissions.
/// Unpoison all the memory that is currently mapped with read permissions.
pub fn unpoison_all_existing_memory(&mut self) {
RangeDetails::enumerate_with_prot(
PageProtection::Read,
&mut |range: &RangeDetails| -> bool {
let start = range.memory_range().base_address().0 as usize;
let end = start + range.memory_range().size();
//the shadow region should be the highest valid userspace region, so don't continue after
if self.is_managed(start as *mut c_void) {
false
log::trace!("Not unpoisoning: {:#x}-{:#x}, is_managed", start, end);
} else {
log::trace!("Unpoisoning: {:#x}-{:#x}", start, end);
self.map_shadow_for_region(start, end, true);
true
}
true
},
);
}
Expand Down Expand Up @@ -672,6 +683,11 @@ impl Allocator {
shadow_bit = (try_shadow_bit).try_into().unwrap();

log::warn!("shadow_bit {shadow_bit:} is suitable");
log::trace!(
"shadow area from {:x} to {:x} pre-allocated",
addr,
addr + (1 << (try_shadow_bit + 1))
);
self.pre_allocated_shadow_mappings.push(mapping);
self.using_pre_allocated_shadow_mapping = true;
break;
Expand Down Expand Up @@ -734,6 +750,7 @@ impl Default for Allocator {
}

#[test]
#[cfg(not(windows))] // not working yet
fn check_shadow() {
let mut allocator = Allocator::default();
allocator.init();
Expand Down
29 changes: 18 additions & 11 deletions libafl_frida/src/asan/asan_rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1722,11 +1722,11 @@ impl AsanRuntime {
; .arch x64
// ; int3
; mov rdx, 1
; shl rdx, shadow_bit as i8 //rcx now contains the mask
; mov rcx, rdi //copy address into rdx
; and rcx, 7 //rsi now contains the offset for unaligned accesses
; shr rdi, 3 //rdi now contains the shadow byte offset
; add rdi, rdx //add rdx and rdi to get the address of the shadow byte. rdi now contains the shadow address
; shl rdx, shadow_bit as i8 //rdx = shadow_base
; mov rcx, rdi //copy address into rcx
; and rcx, 7 //remainder
; shr rdi, 3 //start >> 3
; add rdi, rdx //shadow_base + (start >> 3)
; mov edx, [rdi] //load 4 shadow bytes. We load 4 just in case of an unaligned access
; bswap edx //bswap to get it into an acceptable form
; shl edx, cl //this shifts by the unaligned access offset. why does x86 require cl...
Expand Down Expand Up @@ -2259,7 +2259,7 @@ impl AsanRuntime {
#[allow(clippy::result_unit_err)]
pub fn asan_is_interesting_instruction(
decoder: InstDecoder,
_address: u64,
address: u64,
instr: &Insn,
) -> Option<(u8, X86Register, X86Register, u8, i32)> {
let result = frida_to_cs(decoder, instr);
Expand Down Expand Up @@ -2291,19 +2291,26 @@ impl AsanRuntime {
return None;
}

log::trace!("{:#x} {:#?} {:#?}", address, cs_instr, cs_instr.to_string());

for operand in operands {
if operand.is_memory() {
// log::trace!("{:#?}", operand);
// if we reach this point
// because in x64 there's no mem to mem inst, just return the first memory operand

if let Some((basereg, indexreg, scale, disp)) = operand_details(&operand) {
let memsz = cs_instr.mem_size().unwrap().bytes_size().unwrap(); // this won't fail if it is mem access inst
// if the base register is rip, then it is a pc-relative access
// and does not deal with dynamically allocated memory
if basereg != X86Register::Rip {
let memsz = cs_instr.mem_size().unwrap().bytes_size().unwrap(); // this won't fail if it is mem access inst

// println!("{:#?} {:#?} {:#?}", cs_instr, cs_instr.to_string(), operand);
// println!("{:#?}", (memsz, basereg, indexreg, scale, disp));

return Some((memsz, basereg, indexreg, scale, disp));
// println!("{:#?} {:#?} {:#?}", cs_instr, cs_instr.to_string(), operand);
// println!("{:#?}", (memsz, basereg, indexreg, scale, disp));
log::trace!("ASAN Interesting operand {:#?}", operand);
log::trace!("{:#?}", (memsz, basereg, indexreg, scale, disp));
return Some((memsz, basereg, indexreg, scale, disp));
}
} // else {} // perhaps avx instructions?
}
}
Expand Down
4 changes: 3 additions & 1 deletion libafl_frida/src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use core::fmt::{self, Debug, Formatter};
#[cfg(all(windows, not(test)))]
use std::process::abort;
use std::{ffi::c_void, marker::PhantomData};

use frida_gum::{
Expand Down Expand Up @@ -110,7 +112,7 @@ where
log::error!("Crashing target as it had ASan errors");
libc::raise(libc::SIGABRT);
#[cfg(windows)]
std::process::abort();
abort();
}
}
self.helper.post_exec(input)?;
Expand Down
29 changes: 22 additions & 7 deletions libafl_frida/src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl FridaInstrumentationHelperBuilder {
/// # Example
/// Instrument all modules in `/usr/lib` as well as `libfoo.so`:
/// ```
///# use libafl_frida::helper::FridaInstrumentationHelperBuilder;
///# use libafl_frida::helper::FridaInstrumentationHelper;
/// let builder = FridaInstrumentationHelper::builder()
/// .instrument_module_if(|module| module.name() == "libfoo.so")
/// .instrument_module_if(|module| module.path().starts_with("/usr/lib"));
Expand Down Expand Up @@ -212,7 +212,7 @@ impl FridaInstrumentationHelperBuilder {
/// Instrument all modules in `/usr/lib`, but exclude `libfoo.so`.
///
/// ```
///# use libafl_frida::helper::FridaInstrumentationHelperBuilder;
///# use libafl_frida::helper::FridaInstrumentationHelper;
/// let builder = FridaInstrumentationHelper::builder()
/// .instrument_module_if(|module| module.path().starts_with("/usr/lib"))
/// .skip_module_if(|module| module.name() == "libfoo.so");
Expand Down Expand Up @@ -464,6 +464,7 @@ where
})
}

#[allow(clippy::too_many_lines)]
fn transform(
basic_block: StalkerIterator,
output: &StalkerOutput,
Expand All @@ -484,13 +485,20 @@ where
let mut runtimes = (*runtimes_unborrowed).borrow_mut();
if first {
first = false;
// log::info!(
// "block @ {:x} transformed to {:x}",
// address,
// output.writer().pc()
// );
log::trace!(
"block @ {:x} transformed to {:x}",
address,
output.writer().pc()
);
if let Some(rt) = runtimes.match_first_type_mut::<CoverageRuntime>() {
let start = output.writer().pc();
rt.emit_coverage_mapping(address, output);
log::trace!(
"emitted coverage info mapping for {:x} at {:x}-{:x}",
address,
start,
output.writer().pc()
);
}
if let Some(_rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
basic_block_start = address;
Expand All @@ -506,6 +514,7 @@ where
#[cfg(target_arch = "x86_64")]
if let Some(details) = res {
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
let start = output.writer().pc();
rt.emit_shadow_check(
address,
output,
Expand All @@ -516,6 +525,12 @@ where
details.3,
details.4,
);
log::trace!(
"emitted shadow_check for {:x} at {:x}-{:x}",
address,
start,
output.writer().pc()
);
}
}

Expand Down
10 changes: 10 additions & 0 deletions libafl_frida/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ mod tests {
use libafl_bolts::{
cli::FuzzerOptions, rands::StdRand, tuples::tuple_list, AsSlice, SimpleStdoutLogger,
};
#[cfg(unix)]
use mimalloc::MiMalloc;

use crate::{
asan::{
Expand All @@ -375,6 +377,14 @@ mod tests {
executor::FridaInProcessExecutor,
helper::FridaInstrumentationHelper,
};
#[cfg(unix)]
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
#[cfg(windows)]
use dlmalloc::GlobalDlmalloc;
#[cfg(windows)]
#[global_allocator]
static GLOBAL: GlobalDlmalloc = GlobalDlmalloc;

static GUM: OnceLock<Gum> = OnceLock::new();

Expand Down
Loading

0 comments on commit 03d8d2e

Please sign in to comment.