Fixing Frida ASAN tests on Windows (#2299)

* libafl_frida unit tests passing with ASAN

* Clippy+fmt

* Clippy

* Setup VS environment before building
This commit is contained in:
mkravchik 2024-06-11 04:22:46 -07:00 committed by GitHub
parent df40db5ae8
commit 03d8d2eb08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 140 additions and 28 deletions

View File

@ -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

View File

@ -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"]}

View File

@ -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)

View File

@ -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();

View File

@ -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?
}
}

View File

@ -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)?;

View File

@ -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::<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;
@ -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,
@ -516,6 +525,12 @@ where
details.3,
details.4,
);
log::trace!(
"emitted shadow_check for {:x} at {:x}-{:x}",
address,
start,
output.writer().pc()
);
}
}

View File

@ -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<Gum> = OnceLock::new();

View File

@ -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 <winnt.h>
#include <winternl.h>
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<int *>(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<int *>(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<int *>(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<int *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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;
}