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:
parent
df40db5ae8
commit
03d8d2eb08
1
.github/workflows/build_and_test.yml
vendored
1
.github/workflows/build_and_test.yml
vendored
@ -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
|
||||
|
@ -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"]}
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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?
|
||||
}
|
||||
}
|
||||
|
@ -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)?;
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user