From 03d8d2eb08b5112d5476949a06b90b13849d738e Mon Sep 17 00:00:00 2001 From: mkravchik Date: Tue, 11 Jun 2024 04:22:46 -0700 Subject: [PATCH] Fixing Frida ASAN tests on Windows (#2299) * libafl_frida unit tests passing with ASAN * Clippy+fmt * Clippy * Setup VS environment before building --- .github/workflows/build_and_test.yml | 1 + libafl_frida/Cargo.toml | 4 ++- libafl_frida/build.rs | 18 +++++++++-- libafl_frida/src/alloc.rs | 27 +++++++++++++--- libafl_frida/src/asan/asan_rt.rs | 29 +++++++++++------- libafl_frida/src/executor.rs | 4 ++- libafl_frida/src/helper.rs | 29 +++++++++++++----- libafl_frida/src/lib.rs | 10 ++++++ libafl_frida/test_harness.cpp | 46 +++++++++++++++++++++++++++- 9 files changed, 140 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 20afa93a11..8cf9b3f9d8 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -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 diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 0c1c87ac2b..4240e9c61b 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -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" @@ -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"]} \ No newline at end of file diff --git a/libafl_frida/build.rs b/libafl_frida/build.rs index 3d54b87a5e..72487f079f 100644 --- a/libafl_frida/build.rs +++ b/libafl_frida/build.rs @@ -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++"); @@ -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 @@ -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) diff --git a/libafl_frida/src/alloc.rs b/libafl_frida/src/alloc.rs index 8fc13cc96e..e12bd4013a 100644 --- a/libafl_frida/src/alloc.rs +++ b/libafl_frida/src/alloc.rs @@ -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; @@ -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() @@ -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)) { @@ -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 @@ -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 }, ); } @@ -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; @@ -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(); diff --git a/libafl_frida/src/asan/asan_rt.rs b/libafl_frida/src/asan/asan_rt.rs index e2e745cc4a..aaaf4c5472 100644 --- a/libafl_frida/src/asan/asan_rt.rs +++ b/libafl_frida/src/asan/asan_rt.rs @@ -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... @@ -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); @@ -2291,6 +2291,8 @@ 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); @@ -2298,12 +2300,17 @@ impl AsanRuntime { // 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? } } diff --git a/libafl_frida/src/executor.rs b/libafl_frida/src/executor.rs index 0a4afb9370..57629fa060 100644 --- a/libafl_frida/src/executor.rs +++ b/libafl_frida/src/executor.rs @@ -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::{ @@ -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)?; diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index adcdf70c4a..6d82732745 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -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")); @@ -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"); @@ -464,6 +464,7 @@ where }) } + #[allow(clippy::too_many_lines)] fn transform( basic_block: StalkerIterator, output: &StalkerOutput, @@ -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::() { + 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::() { basic_block_start = address; @@ -506,6 +514,7 @@ where #[cfg(target_arch = "x86_64")] if let Some(details) = res { if let Some(rt) = runtimes.match_first_type_mut::() { + let start = output.writer().pc(); rt.emit_shadow_check( address, output, @@ -516,6 +525,12 @@ where details.3, details.4, ); + log::trace!( + "emitted shadow_check for {:x} at {:x}-{:x}", + address, + start, + output.writer().pc() + ); } } diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index f30cede48b..90888dcd5c 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -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::{ @@ -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 = OnceLock::new(); diff --git a/libafl_frida/test_harness.cpp b/libafl_frida/test_harness.cpp index 4ea1369470..fd85df219f 100644 --- a/libafl_frida/test_harness.cpp +++ b/libafl_frida/test_harness.cpp @@ -7,16 +7,21 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { + (void)hModule; + (void)lpReserved; + (void)ul_reason_for_call; return TRUE; } - #define EXTERN __declspec(dllexport) extern "C" + #define EXTERN extern "C" __declspec(dllexport) #else #define EXTERN extern "C" { #endif EXTERN int heap_uaf_read(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; int *array = new int[100]; delete[] array; fprintf(stdout, "%d\n", array[5]); @@ -24,13 +29,26 @@ EXTERN int heap_uaf_read(const uint8_t *_data, size_t _size) { } EXTERN int heap_uaf_write(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; int *array = new int[100]; delete[] array; array[5] = 1; return 0; } +#include +#include + +static volatile bool stop = false; + EXTERN int heap_oob_read(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; + + // while(!stop); + + // OutputDebugStringA("heap_oob_read\n"); int *array = new int[100]; fprintf(stdout, "%d\n", array[100]); delete[] array; @@ -38,12 +56,16 @@ EXTERN int heap_oob_read(const uint8_t *_data, size_t _size) { } EXTERN int heap_oob_write(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; int *array = new int[100]; array[100] = 1; delete[] array; return 0; } EXTERN int malloc_heap_uaf_read(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; int *array = static_cast(malloc(100 * sizeof(int))); free(array); fprintf(stdout, "%d\n", array[5]); @@ -51,6 +73,8 @@ EXTERN int malloc_heap_uaf_read(const uint8_t *_data, size_t _size) { } EXTERN int malloc_heap_uaf_write(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; int *array = static_cast(malloc(100 * sizeof(int))); free(array); array[5] = 1; @@ -58,6 +82,8 @@ EXTERN int malloc_heap_uaf_write(const uint8_t *_data, size_t _size) { } EXTERN int malloc_heap_oob_read(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; int *array = static_cast(malloc(100 * sizeof(int))); fprintf(stdout, "%d\n", array[100]); free(array); @@ -65,6 +91,8 @@ EXTERN int malloc_heap_oob_read(const uint8_t *_data, size_t _size) { } EXTERN int malloc_heap_oob_write(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; int *array = static_cast(malloc(100 * sizeof(int))); array[100] = 1; free(array); @@ -72,6 +100,8 @@ EXTERN int malloc_heap_oob_write(const uint8_t *_data, size_t _size) { } EXTERN int malloc_heap_oob_write_0x12(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; char *array = static_cast(malloc(0x12)); array[0x12] = 1; free(array); @@ -79,6 +109,8 @@ EXTERN int malloc_heap_oob_write_0x12(const uint8_t *_data, size_t _size) { } EXTERN int malloc_heap_oob_write_0x14(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; char *array = static_cast(malloc(0x14)); array[0x14] = 1; free(array); @@ -86,6 +118,8 @@ EXTERN int malloc_heap_oob_write_0x14(const uint8_t *_data, size_t _size) { } EXTERN int malloc_heap_oob_write_0x17(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; char *array = static_cast(malloc(0x17)); array[0x17] = 1; free(array); @@ -94,6 +128,8 @@ EXTERN int malloc_heap_oob_write_0x17(const uint8_t *_data, size_t _size) { EXTERN int malloc_heap_oob_write_0x17_int_at_0x16(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; char *array = static_cast(malloc(0x17)); *(int *)(&array[0x16]) = 1; free(array); @@ -102,6 +138,8 @@ EXTERN int malloc_heap_oob_write_0x17_int_at_0x16(const uint8_t *_data, EXTERN int malloc_heap_oob_write_0x17_int_at_0x15(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; char *array = static_cast(malloc(0x17)); *(int *)(&array[0x15]) = 1; free(array); @@ -109,6 +147,8 @@ EXTERN int malloc_heap_oob_write_0x17_int_at_0x15(const uint8_t *_data, } EXTERN int malloc_heap_oob_write_0x17_int_at_0x14(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; char *array = static_cast(malloc(0x17)); *(int *)(&array[0x14]) = 1; free(array); @@ -117,6 +157,8 @@ EXTERN int malloc_heap_oob_write_0x17_int_at_0x14(const uint8_t *_data, EXTERN int malloc_heap_oob_write_0x17_int_at_0x13(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; char *array = static_cast(malloc(0x17)); *(int *)(&array[0x13]) = 1; free(array); @@ -125,6 +167,8 @@ EXTERN int malloc_heap_oob_write_0x17_int_at_0x13(const uint8_t *_data, EXTERN int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // abort(); + (void)data; + (void)size; return 0; }