From b3fe744e57dcc907a9f8a0602c49c0b56507416f Mon Sep 17 00:00:00 2001 From: mkravchik Date: Fri, 14 Feb 2025 14:45:38 +0200 Subject: [PATCH] Pr/fasan multithreading fixes upstream (#2955) * Fixing the test_harness library name * Fasan works, but testing of all features is pending * Tests pass, before fixing clippy and fmt * CLippy+fmt * CLippy+fmt+tests running on linux * Clippy * Not stalkering the fuzzer. In the correct way * Removing the instrumentation upon crash. Proper hooking of UnmapViewOfFile * Fixes after the merge from the upstream (before 0.15.0). Still need to add the observer, clippy, fmt, and at least linux compilation * Adding the helper observer and using it in the test * Removing the observer from the wrong location * Adapting to the new helper ownership model * Adding an observer to shut down instrumentation upon crash * Clippy + fmt * Using mimalloc everywhere * Deactivating before activating with the harness. Otherwise, gets stuck on Linux. * Fixing imports for windows * Using the new way of passing the handler * Using frida_helper_shutdown_observer * Clippy+fmt * no-std, clippy * Fmt * Stable thread_id * Clippy 18 * More clippy * Formatting toml * Fixing apples * Fixing apples 2 * Fixing apples 3 * Upping to 0.16.7 (necessary for Windows) * Clippy+fmt * Enabling the allocator test after the fix and clarifying the importantce of the static runtime linking. * Moving has_tls to bolts * Proper handling of no-std, hopefully * Another attempt to fix win no-std * Not mine clippy complaint... * Not mine clippy complaint #2... * Dlmalloc not used, removing from dependencies * Restoring target in config.toml (otherwise fails CI on Linux) * lots of digging around, pray for us * fixup? * Revert "lots of digging around, pray for us" This reverts commit 706c27201918e906e3401cd0d9e76546f889d1f5. * Revert "fixup?" This reverts commit 1d7c5d4fb5b1bd31f5e0c07492aa8ed64c6822f3. * Revert artifact * Revert fixups * Removing unused * Reverting to upstream/main --------- Co-authored-by: Addison Crump Co-authored-by: Dongjia "toka" Zhang --- .gitignore | 2 + .../frida_executable_libpng/Cargo.toml | 2 +- .../frida_executable_libpng/src/fuzzer.rs | 73 +++- fuzzers/binary_only/frida_libpng/Cargo.toml | 2 +- .../binary_only/frida_libpng/src/fuzzer.rs | 23 +- .../frida_windows_gdiplus/.cargo/config.toml | 2 + .../frida_windows_gdiplus/Cargo.toml | 2 +- .../frida_windows_gdiplus/README.md | 2 + .../frida_windows_gdiplus/cargo/.config | 2 - .../frida_windows_gdiplus/src/fuzzer.rs | 63 ++- .../nautilus_sync/src/bin/libafl_cc.rs | 3 +- libafl/Cargo.toml | 8 +- libafl/src/executors/hooks/windows.rs | 30 +- libafl_bolts/Cargo.toml | 9 + libafl_bolts/src/lib.rs | 180 +++++++- libafl_frida/Cargo.toml | 14 +- libafl_frida/build.rs | 2 +- libafl_frida/src/alloc.rs | 56 ++- libafl_frida/src/asan/asan_rt.rs | 404 ++++++++++++------ libafl_frida/src/asan/hook_funcs.rs | 263 ++++++++++-- libafl_frida/src/executor.rs | 95 ++-- .../src/frida_helper_shutdown_observer.rs | 97 +++++ libafl_frida/src/helper.rs | 29 +- libafl_frida/src/lib.rs | 51 ++- libafl_frida/test_harness.cpp | 60 ++- 25 files changed, 1162 insertions(+), 312 deletions(-) create mode 100644 fuzzers/binary_only/frida_windows_gdiplus/.cargo/config.toml delete mode 100644 fuzzers/binary_only/frida_windows_gdiplus/cargo/.config create mode 100644 libafl_frida/src/frida_helper_shutdown_observer.rs diff --git a/.gitignore b/.gitignore index ea92a630b0..de5478a871 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ harness program fuzzer_libpng* forkserver_simple + +*.patch diff --git a/fuzzers/binary_only/frida_executable_libpng/Cargo.toml b/fuzzers/binary_only/frida_executable_libpng/Cargo.toml index 5ba7048f4b..4f12aba4f5 100644 --- a/fuzzers/binary_only/frida_executable_libpng/Cargo.toml +++ b/fuzzers/binary_only/frida_executable_libpng/Cargo.toml @@ -25,7 +25,7 @@ libafl = { path = "../../../libafl", features = [ "frida_cli", ] } #, "llmp_small_maps", "llmp_debug"]} libafl_bolts = { path = "../../../libafl_bolts" } -frida-gum = { version = "0.16.5", features = [ +frida-gum = { version = "0.16.7", features = [ "auto-download", "event-sink", "invocation-listener", diff --git a/fuzzers/binary_only/frida_executable_libpng/src/fuzzer.rs b/fuzzers/binary_only/frida_executable_libpng/src/fuzzer.rs index d4988a97b0..44d28b3b42 100644 --- a/fuzzers/binary_only/frida_executable_libpng/src/fuzzer.rs +++ b/fuzzers/binary_only/frida_executable_libpng/src/fuzzer.rs @@ -1,6 +1,6 @@ //! A libfuzzer-like fuzzer with llmp-multithreading support and restarts //! The example harness is built for libpng. -use std::{path::PathBuf, ptr::null}; +use std::{cell::RefCell, path::PathBuf, ptr::null, rc::Rc}; use frida_gum::Gum; use libafl::{ @@ -43,6 +43,7 @@ use libafl_frida::{ cmplog_rt::CmpLogRuntime, coverage_rt::{CoverageRuntime, MAP_SIZE}, executor::FridaInProcessExecutor, + frida_helper_shutdown_observer::FridaHelperObserver, helper::FridaInstrumentationHelper, }; use libafl_targets::cmplog::CmpLogObserver; @@ -113,16 +114,22 @@ unsafe fn fuzz( let asan = AsanRuntime::new(options); #[cfg(unix)] - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan)); + let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage, asan), + ))); #[cfg(windows)] - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage)); + let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( + &gum, + &options, + tuple_list!(coverage), + ))); // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( "edges", - frida_helper.map_mut_ptr().unwrap(), + frida_helper.borrow_mut().map_mut_ptr().unwrap(), MAP_SIZE, )) .track_indices(); @@ -131,6 +138,7 @@ unsafe fn fuzz( let time_observer = TimeObserver::new("time"); #[cfg(unix)] let asan_observer = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR @@ -196,9 +204,14 @@ unsafe fn fuzz( let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); #[cfg(unix)] - let observers = tuple_list!(edges_observer, time_observer, asan_observer); + let observers = tuple_list!( + frida_helper_observer, + edges_observer, + time_observer, + asan_observer + ); #[cfg(windows)] - let observers = tuple_list!(edges_observer, time_observer); + let observers = tuple_list!(frida_helper_observer, edges_observer, time_observer); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -210,7 +223,7 @@ unsafe fn fuzz( &mut state, &mut mgr, )?, - &mut frida_helper, + Rc::clone(&frida_helper), ); // In case the corpus is empty (on first run), reset @@ -238,13 +251,16 @@ unsafe fn fuzz( let coverage = CoverageRuntime::new(); let cmplog = CmpLogRuntime::new(); - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog)); + let mut frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage, cmplog), + ))); // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( "edges", - frida_helper.map_mut_ptr().unwrap(), + frida_helper.borrow_mut().map_mut_ptr().unwrap(), MAP_SIZE, )) .track_indices(); @@ -253,6 +269,7 @@ unsafe fn fuzz( let time_observer = TimeObserver::new("time"); #[cfg(unix)] let asan_observer = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR @@ -316,9 +333,14 @@ unsafe fn fuzz( let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); #[cfg(unix)] - let observers = tuple_list!(edges_observer, time_observer, asan_observer); + let observers = tuple_list!( + frida_helper_observer, + edges_observer, + time_observer, + asan_observer + ); #[cfg(windows)] - let observers = tuple_list!(edges_observer, time_observer); + let observers = tuple_list!(frida_helper_observer, edges_observer, time_observer); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -330,7 +352,7 @@ unsafe fn fuzz( &mut state, &mut mgr, )?, - &mut frida_helper, + Rc::clone(&frida_helper), ); // In case the corpus is empty (on first run), reset @@ -373,13 +395,16 @@ unsafe fn fuzz( let coverage = CoverageRuntime::new(); - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage)); + let mut frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage), + ))); // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( "edges", - frida_helper.map_mut_ptr().unwrap(), + frida_helper.borrow_mut().map_mut_ptr().unwrap(), MAP_SIZE, )) .track_indices(); @@ -388,6 +413,7 @@ unsafe fn fuzz( let time_observer = TimeObserver::new("time"); #[cfg(unix)] let asan_observer = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR @@ -451,9 +477,14 @@ unsafe fn fuzz( let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); #[cfg(unix)] - let observers = tuple_list!(edges_observer, time_observer, asan_observer); + let observers = tuple_list!( + frida_helper_observer, + edges_observer, + time_observer, + asan_observer + ); #[cfg(windows)] - let observers = tuple_list!(edges_observer, time_observer); + let observers = tuple_list!(frida_helper_observer, edges_observer, time_observer); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -465,7 +496,7 @@ unsafe fn fuzz( &mut state, &mut mgr, )?, - &mut frida_helper, + Rc::clone(&frida_helper), ); // In case the corpus is empty (on first run), reset diff --git a/fuzzers/binary_only/frida_libpng/Cargo.toml b/fuzzers/binary_only/frida_libpng/Cargo.toml index da44fd646b..430e7f5f6a 100644 --- a/fuzzers/binary_only/frida_libpng/Cargo.toml +++ b/fuzzers/binary_only/frida_libpng/Cargo.toml @@ -27,7 +27,7 @@ libafl = { path = "../../../libafl", features = [ "errors_backtrace", ] } #, "llmp_small_maps", "llmp_debug"]} libafl_bolts = { path = "../../../libafl_bolts" } -frida-gum = { version = "0.16.5", features = [ +frida-gum = { version = "0.16.7", features = [ "auto-download", "event-sink", "invocation-listener", diff --git a/fuzzers/binary_only/frida_libpng/src/fuzzer.rs b/fuzzers/binary_only/frida_libpng/src/fuzzer.rs index 261296ffc8..6ea6f9cd33 100644 --- a/fuzzers/binary_only/frida_libpng/src/fuzzer.rs +++ b/fuzzers/binary_only/frida_libpng/src/fuzzer.rs @@ -1,6 +1,6 @@ //! A libfuzzer-like fuzzer with llmp-multithreading support and restarts //! The example harness is built for libpng. -use std::path::PathBuf; +use std::{cell::RefCell, path::PathBuf, rc::Rc}; use frida_gum::Gum; use libafl::{ @@ -40,6 +40,7 @@ use libafl_frida::{ cmplog_rt::CmpLogRuntime, coverage_rt::{CoverageRuntime, MAP_SIZE}, executor::FridaInProcessExecutor, + frida_helper_shutdown_observer::FridaHelperObserver, helper::{FridaInstrumentationHelper, IfElseRuntime}, }; use libafl_targets::cmplog::CmpLogObserver; @@ -104,7 +105,7 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let options_clone = options.clone(); let client_description_clone2 = client_description.clone(); let options_clone2 = options.clone(); - let mut frida_helper = FridaInstrumentationHelper::new( + let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( &gum, options, tuple_list!( @@ -120,17 +121,22 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { ), coverage ), - ); + ))); // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(unsafe { - StdMapObserver::from_mut_ptr("edges", frida_helper.map_mut_ptr().unwrap(), MAP_SIZE) + StdMapObserver::from_mut_ptr( + "edges", + frida_helper.borrow_mut().map_mut_ptr().unwrap(), + MAP_SIZE, + ) }) .track_indices(); // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); let asan_observer = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR @@ -187,7 +193,12 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let observers = tuple_list!(edges_observer, time_observer, asan_observer); + let observers = tuple_list!( + frida_helper_observer, + edges_observer, + time_observer, + asan_observer + ); // Create the executor for an in-process function with just one observer for edge coverage let executor = FridaInProcessExecutor::new( @@ -200,7 +211,7 @@ fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { &mut mgr, options.timeout, )?, - &mut frida_helper, + Rc::clone(&frida_helper), ); // Create an observation channel using cmplog map let cmplog_observer = CmpLogObserver::new("cmplog", true); diff --git a/fuzzers/binary_only/frida_windows_gdiplus/.cargo/config.toml b/fuzzers/binary_only/frida_windows_gdiplus/.cargo/config.toml new file mode 100644 index 0000000000..fac678a9b3 --- /dev/null +++ b/fuzzers/binary_only/frida_windows_gdiplus/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml b/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml index 960736f802..c89af01c56 100644 --- a/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml +++ b/fuzzers/binary_only/frida_windows_gdiplus/Cargo.toml @@ -23,7 +23,7 @@ libafl = { path = "../../../libafl", features = [ "errors_backtrace", ] } #, "llmp_small_maps", "llmp_debug"]} libafl_bolts = { path = "../../../libafl_bolts" } -frida-gum = { version = "0.16.5", features = [ +frida-gum = { version = "0.16.7", features = [ "auto-download", "event-sink", "invocation-listener", diff --git a/fuzzers/binary_only/frida_windows_gdiplus/README.md b/fuzzers/binary_only/frida_windows_gdiplus/README.md index e432d5ac43..ae8e4606fc 100644 --- a/fuzzers/binary_only/frida_windows_gdiplus/README.md +++ b/fuzzers/binary_only/frida_windows_gdiplus/README.md @@ -9,6 +9,8 @@ To build this example, run `cargo build --release` in this folder. Then compile the harness `cl.exe /LD harness.cc /link /dll gdiplus.lib ole32.lib` +Note: this fuzzer is **statically linked** with C runtime. This is achieved by specifying `rustflags = ["-C", "target-feature=+crt-static"]` in `.cargo/config.toml`. The static linking is necessary to avoid Asan function hooks to hook the calls from the fuzzer itself, as such self-hooking can eventually lead to deadlocks in internal Frida mechanisms. + ## Run To run the example `target\release\frida_windows_gdiplus.exe -H harness.dll -i corpus -o output --libs-to-instrument gdi32.dll --libs-to-instrument gdi32full.dll --libs-to-instrument gdiplus.dll --libs-to-instrument WindowsCodecs.dll --disable-excludes` diff --git a/fuzzers/binary_only/frida_windows_gdiplus/cargo/.config b/fuzzers/binary_only/frida_windows_gdiplus/cargo/.config deleted file mode 100644 index 33806ae7fb..0000000000 --- a/fuzzers/binary_only/frida_windows_gdiplus/cargo/.config +++ /dev/null @@ -1,2 +0,0 @@ -[build] -target = "x86_64-pc-windows-msvc" diff --git a/fuzzers/binary_only/frida_windows_gdiplus/src/fuzzer.rs b/fuzzers/binary_only/frida_windows_gdiplus/src/fuzzer.rs index 5e09527f1a..4739b88827 100644 --- a/fuzzers/binary_only/frida_windows_gdiplus/src/fuzzer.rs +++ b/fuzzers/binary_only/frida_windows_gdiplus/src/fuzzer.rs @@ -10,7 +10,7 @@ use mimalloc::MiMalloc; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; -use std::path::PathBuf; +use std::{cell::RefCell, path::PathBuf, rc::Rc}; use frida_gum::Gum; use libafl::{ @@ -50,6 +50,7 @@ use libafl_frida::{ cmplog_rt::CmpLogRuntime, coverage_rt::{CoverageRuntime, MAP_SIZE}, executor::FridaInProcessExecutor, + frida_helper_shutdown_observer::FridaHelperObserver, helper::FridaInstrumentationHelper, }; use libafl_targets::cmplog::CmpLogObserver; @@ -105,13 +106,16 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let coverage = CoverageRuntime::new(); let asan = AsanRuntime::new(options); - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan)); + let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage, asan), + ))); // // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( "edges", - frida_helper.map_mut_ptr().unwrap(), + frida_helper.borrow_mut().map_mut_ptr().unwrap(), MAP_SIZE, )) .track_indices(); @@ -121,6 +125,8 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let asan_observer = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); + // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR let mut feedback = feedback_or!( @@ -180,7 +186,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let observers = tuple_list!(edges_observer, time_observer, asan_observer,); + let observers = tuple_list!( + frida_helper_observer, + edges_observer, + time_observer, + asan_observer, + ); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -193,7 +204,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { &mut mgr, options.timeout, )?, - &mut frida_helper, + Rc::clone(&frida_helper), ); // In case the corpus is empty (on first run), reset @@ -220,14 +231,16 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let coverage = CoverageRuntime::new(); let cmplog = CmpLogRuntime::new(); - - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog)); + let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage, cmplog), + ))); // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( "edges", - frida_helper.map_mut_ptr().unwrap(), + frida_helper.borrow_mut().map_mut_ptr().unwrap(), MAP_SIZE, )) .track_indices(); @@ -235,6 +248,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); let asan_observer = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR @@ -294,7 +308,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let observers = tuple_list!(edges_observer, time_observer, asan_observer); + let observers = tuple_list!( + frida_helper_observer, + edges_observer, + time_observer, + asan_observer + ); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -306,7 +325,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { &mut state, &mut mgr, )?, - &mut frida_helper, + Rc::clone(&frida_helper), ); // In case the corpus is empty (on first run), reset @@ -350,13 +369,16 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let coverage = CoverageRuntime::new(); - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage)); + let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage), + ))); // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( "edges", - frida_helper.map_mut_ptr().unwrap(), + frida_helper.borrow_mut().map_mut_ptr().unwrap(), MAP_SIZE, )) .track_indices(); @@ -366,6 +388,8 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let asan_observer = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); + // Feedback to rate the interestingness of an input // This one is composed by two Feedbacks in OR let mut feedback = feedback_or!( @@ -424,7 +448,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let observers = tuple_list!(edges_observer, time_observer, asan_observer); + let observers = tuple_list!( + frida_helper_observer, + edges_observer, + time_observer, + asan_observer + ); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -437,7 +466,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { &mut mgr, options.timeout, )?, - &mut frida_helper, + Rc::clone(&frida_helper), ); // In case the corpus is empty (on first run), reset diff --git a/fuzzers/structure_aware/nautilus_sync/src/bin/libafl_cc.rs b/fuzzers/structure_aware/nautilus_sync/src/bin/libafl_cc.rs index 4f78a0875d..1954919d7c 100644 --- a/fuzzers/structure_aware/nautilus_sync/src/bin/libafl_cc.rs +++ b/fuzzers/structure_aware/nautilus_sync/src/bin/libafl_cc.rs @@ -18,7 +18,8 @@ pub fn main() { let mut cc = ClangWrapper::new(); if let Some(code) = cc - .cpp(is_cpp) + .cpp(true) // Link with C++ standard library (Frida links to it in order to hook C++ functions) + // .cpp(is_cpp) // silence the compiler wrapper output, needed for some configure scripts. .silence(true) .parse_args(&args) diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 728ef52edd..67b109e21a 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -327,6 +327,12 @@ windows = { workspace = true, features = [ "Win32_Security", "Win32_System_SystemInformation", ] } - +winapi = { version = "0.3", features = [ + "dbghelp", + "minwindef", + "winnt", + "errhandlingapi", + "processthreadsapi", +] } [target.'cfg(windows)'.build-dependencies] windows = { workspace = true } diff --git a/libafl/src/executors/hooks/windows.rs b/libafl/src/executors/hooks/windows.rs index d5e2e0c2de..77eccd4d92 100644 --- a/libafl/src/executors/hooks/windows.rs +++ b/libafl/src/executors/hooks/windows.rs @@ -369,7 +369,15 @@ pub mod windows_exception_handler { let exception_list = data.exceptions(); if exception_list.contains(&code) { - log::error!("Crashed with {code}"); + log::error!( + "Crashed with {code} at {:?} in thread {:?}", + exception_pointers + .ExceptionRecord + .as_mut() + .unwrap() + .ExceptionAddress, + winapi::um::processthreadsapi::GetCurrentThreadId() + ); } else { // log::trace!("Exception code received, but {code} is not in CRASH_EXCEPTIONS"); is_crash = false; @@ -422,7 +430,17 @@ pub mod windows_exception_handler { // Make sure we don't crash in the crash handler forever. if is_crash { - let input = data.take_current_input::(); + log::warn!("Running observers and exiting!"); + // // I want to disable the hooks before doing anything, especially before taking a stack dump + let input = data.take_current_input::(); // log::set_max_level(log::LevelFilter::Trace); + run_observers_and_save_state::( + executor, + state, + input, + fuzzer, + event_mgr, + ExitKind::Crash, + ); { let mut bsod = Vec::new(); { @@ -434,14 +452,6 @@ pub mod windows_exception_handler { } log::error!("{}", std::str::from_utf8(&bsod).unwrap()); } - run_observers_and_save_state::( - executor, - state, - input, - fuzzer, - event_mgr, - ExitKind::Crash, - ); } else { // This is not worth saving } diff --git a/libafl_bolts/Cargo.toml b/libafl_bolts/Cargo.toml index f52953b1ac..dfffb75edf 100644 --- a/libafl_bolts/Cargo.toml +++ b/libafl_bolts/Cargo.toml @@ -185,6 +185,15 @@ windows = { workspace = true, features = [ "Win32_System_SystemInformation", "Win32_System_Console", ] } +once_cell = "1.10.0" +winapi = { version = "0.3", features = [ + "fileapi", + "handleapi", + "processenv", + "processthreadsapi", + "winbase", + "winnt", +] } windows-result = "0.3.0" [target.'cfg(windows)'.build-dependencies] diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index 6345311ae4..ecef128cfd 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -970,6 +970,109 @@ impl SimpleStdoutLogger { } } +#[cfg(feature = "std")] +#[cfg(target_os = "windows")] +#[allow(clippy::cast_ptr_alignment)] +#[must_use] +/// Return thread ID without using TLS +pub fn get_thread_id() -> u64 { + use std::arch::asm; + #[cfg(target_arch = "x86_64")] + unsafe { + let teb: *const u8; + asm!("mov {}, gs:[0x30]", out(reg) teb); + let thread_id_ptr = teb.add(0x48) as *const u32; + u64::from(*thread_id_ptr) + } + + #[cfg(target_arch = "x86")] + unsafe { + let teb: *const u8; + asm!("mov {}, fs:[0x18]", out(reg) teb); + let thread_id_ptr = teb.add(0x24) as *const u32; + *thread_id_ptr as u64 + } +} + +#[cfg(target_os = "linux")] +#[must_use] +#[allow(clippy::cast_sign_loss)] +/// Return thread ID without using TLS +pub fn get_thread_id() -> u64 { + use libc::{syscall, SYS_gettid}; + + unsafe { syscall(SYS_gettid) as u64 } +} + +#[cfg(feature = "std")] +#[cfg(not(any(target_os = "windows", target_os = "linux")))] +#[must_use] +/// Return thread ID using Rust's `std::thread` +pub fn get_thread_id() -> u64 { + // Fallback for other platforms + let thread_id = std::thread::current().id(); + unsafe { mem::transmute::<_, u64>(thread_id) } +} + +#[cfg(feature = "std")] +#[cfg(target_os = "windows")] +mod windows_logging { + use std::ptr; + + use once_cell::sync::OnceCell; + use winapi::um::{ + fileapi::WriteFile, handleapi::INVALID_HANDLE_VALUE, processenv::GetStdHandle, + winbase::STD_OUTPUT_HANDLE, winnt::HANDLE, + }; + + // Safe wrapper around HANDLE + struct StdOutHandle(HANDLE); + + // Implement Send and Sync for StdOutHandle, assuming it's safe to share + unsafe impl Send for StdOutHandle {} + unsafe impl Sync for StdOutHandle {} + + static H_STDOUT: OnceCell = OnceCell::new(); + + fn get_stdout_handle() -> HANDLE { + H_STDOUT + .get_or_init(|| { + let handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) }; + StdOutHandle(handle) + }) + .0 + } + /// A function that writes directly to stdout using `WinAPI`. + /// Works much faster than println and does not need TLS + pub fn direct_log(message: &str) { + // Get the handle to standard output + let h_stdout: HANDLE = get_stdout_handle(); + + if h_stdout == INVALID_HANDLE_VALUE { + eprintln!("Failed to get standard output handle"); + return; + } + + let bytes = message.as_bytes(); + let mut bytes_written = 0; + + // Write the message to standard output + let result = unsafe { + WriteFile( + h_stdout, + bytes.as_ptr() as *const _, + bytes.len() as u32, + &mut bytes_written, + ptr::null_mut(), + ) + }; + + if result == 0 { + eprintln!("Failed to write to standard output"); + } + } +} + #[cfg(feature = "std")] impl log::Log for SimpleStdoutLogger { #[inline] @@ -977,16 +1080,32 @@ impl log::Log for SimpleStdoutLogger { true } + #[cfg(not(target_os = "windows"))] fn log(&self, record: &Record) { println!( - "[{:?}, {:?}] {}: {}", + "[{:?}, {:?}:{:?}] {}: {}", current_time(), std::process::id(), + get_thread_id(), record.level(), record.args() ); } + #[cfg(target_os = "windows")] + fn log(&self, record: &Record) { + // println is not safe in TLS-less environment + let msg = format!( + "[{:?}, {:?}:{:?}] {}: {}\n", + current_time(), + std::process::id(), + get_thread_id(), + record.level(), + record.args() + ); + windows_logging::direct_log(msg.as_str()); + } + fn flush(&self) {} } @@ -1143,6 +1262,65 @@ pub unsafe fn set_error_print_panic_hook(new_stderr: RawFd) { })); } +#[cfg(feature = "std")] +#[cfg(target_os = "windows")] +#[repr(C)] +#[allow(clippy::upper_case_acronyms)] +struct TEB { + reserved1: [u8; 0x58], + tls_pointer: *mut *mut u8, + reserved2: [u8; 0xC0], +} + +#[cfg(feature = "std")] +#[cfg(target_arch = "x86_64")] +#[inline(always)] +#[cfg(target_os = "windows")] +fn nt_current_teb() -> *mut TEB { + use std::arch::asm; + let teb: *mut TEB; + unsafe { + asm!("mov {}, gs:0x30", out(reg) teb); + } + teb +} + +/// Some of our hooks can be invoked from threads that do not have TLS yet. +/// Many Rust and Frida functions require TLS to be set up, so we need to check if we have TLS. +/// This was observed on Windows, so for now for other platforms we assume that we have TLS. +#[cfg(feature = "std")] +#[inline] +#[allow(unreachable_code)] +#[must_use] +pub fn has_tls() -> bool { + #[cfg(target_os = "windows")] + unsafe { + let teb = nt_current_teb(); + if teb.is_null() { + return false; + } + + let tls_array = (*teb).tls_pointer; + if tls_array.is_null() { + return false; + } + return true; + } + #[cfg(target_arch = "aarch64")] + unsafe { + let mut tid: u64; + std::arch::asm!( + "mrs {tid}, TPIDRRO_EL0", + tid = out(reg) tid, + ); + tid &= 0xffff_ffff_ffff_fff8; + let tlsptr = tid as *const u64; + return tlsptr.add(0x102).read() != 0u64; + } + // Default + true +} + /// Zero-cost way to construct [`core::num::NonZeroUsize`] at compile-time. #[macro_export] macro_rules! nonzero { diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 552edd9e4e..5d800b9442 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -62,17 +62,18 @@ nix = { workspace = true, default-features = true, features = ["mman"] } libc = { workspace = true } hashbrown = { workspace = true, default-features = true } rangemap = { workspace = true } -frida-gum-sys = { version = "0.16.5", features = [ +frida-gum-sys = { version = "0.16.7", features = [ "event-sink", "invocation-listener", ] } -frida-gum = { version = "0.16.5", features = [ +frida-gum = { version = "0.16.7", features = [ "event-sink", "invocation-listener", "module-names", "script", "backtrace", ] } +os-thread-local = "0.1.3" dynasmrt = "3.0.1" color-backtrace = { version = "0.7.0", features = ["resolve-modules"] } @@ -112,6 +113,15 @@ iced-x86 = { version = "1.21.0", features = ["code_asm"], optional = true } [target.'cfg(windows)'.dependencies] winsafe = { version = "0.0.22", features = ["kernel"] } +winapi = { version = "0.3", features = [ + "processthreadsapi", + "winnt", + "memoryapi", + "errhandlingapi", + "debugapi", +] } +[target.'cfg(unix)'.dependencies] +errno = "0.2" [target.'cfg(target_vendor="apple")'.dependencies] mach-sys = { version = "0.5.4" } diff --git a/libafl_frida/build.rs b/libafl_frida/build.rs index ca0a08657f..047b2814e1 100644 --- a/libafl_frida/build.rs +++ b/libafl_frida/build.rs @@ -57,7 +57,7 @@ fn main() { cmd.arg("/dll").arg(format!( "/OUT:{}", Path::new(&out_dir) - .join("test_harness.so") + .join("test_harness.dll") .to_str() .unwrap() )); diff --git a/libafl_frida/src/alloc.rs b/libafl_frida/src/alloc.rs index ece38bea1f..b0d945b8ea 100644 --- a/libafl_frida/src/alloc.rs +++ b/libafl_frida/src/alloc.rs @@ -200,7 +200,10 @@ impl Allocator { } metadata } else { - // log::trace!("{:x}, {:x}", self.current_mapping_addr, rounded_up_size); + // log::info!( + // "Mapping {:x}, size {rounded_up_size:x}", + // self.current_mapping_addr + // ); let mapping = match MmapOptions::new(rounded_up_size) .unwrap() .with_address(self.current_mapping_addr) @@ -247,18 +250,14 @@ impl Allocator { let address = (metadata.address + self.page_size) as *mut c_void; self.allocations.insert(address as usize, metadata); - log::info!( - "serving address: {:#x}, size: {:#x}", - address as usize, - size - ); + // log::info!("serving address: {address:?}, size: {size:x}"); address } /// Releases the allocation at the given address. #[expect(clippy::missing_safety_doc)] pub unsafe fn release(&mut self, ptr: *mut c_void) { - log::info!("release {:?}", ptr); + // log::info!("releasing {:?}", ptr); let Some(metadata) = self.allocations.get_mut(&(ptr as usize)) else { if !ptr.is_null() && AsanErrors::get_mut_blocking() @@ -405,14 +404,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); + // 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 - ); + // 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)) { @@ -441,11 +440,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() - ); + // 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 @@ -571,7 +570,21 @@ impl Allocator { map_to_shadow!(self, start) } - /// Checks if the current address is one of ours - is this address in the allocator region + /// Is this a valid and mapped shadow address? + #[must_use] + pub fn valid_shadow(&self, start: usize, size: usize) -> bool { + let range_to_check = start..(start + size); + let valid = self + .shadow_pages + .overlapping(&range_to_check) + .any(|r| r.start <= start && r.end >= start + size); + + if !valid { + log::error!("Not a valid shadow: {:#x}!", start); + } + valid + } + /// Checks if the currennt address is one of ours #[inline] pub fn is_managed(&self, ptr: *mut c_void) -> bool { //self.allocations.contains_key(&(ptr as usize)) @@ -664,7 +677,7 @@ impl Allocator { if self.shadow_offset <= start && end <= self.current_mapping_addr { log::trace!("Reached the shadow/allocator region - skipping"); } else { - log::trace!("Unpoisoning: {:#x}-{:#x}", start, end); + // log::trace!("Unpoisoning: {:#x}-{:#x}", start, end); self.map_shadow_for_region(start, end, true); } true @@ -843,8 +856,9 @@ impl Default for Allocator { } #[test] -#[cfg(not(windows))] // not working yet fn check_shadow() { + use frida_gum::Gum; + let _gum = Gum::obtain(); 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 3bb827e473..cf640e058e 100644 --- a/libafl_frida/src/asan/asan_rt.rs +++ b/libafl_frida/src/asan/asan_rt.rs @@ -12,7 +12,7 @@ use std::{ ffi::{c_char, c_void}, ptr::write_volatile, rc::Rc, - sync::MutexGuard, + sync::{Mutex, MutexGuard}, }; use backtrace::Backtrace; @@ -27,7 +27,7 @@ use frida_gum::{ }; use frida_gum_sys::Insn; use hashbrown::HashMap; -use libafl_bolts::cli::FuzzerOptions; +use libafl_bolts::{cli::FuzzerOptions, get_thread_id, has_tls}; use libc::wchar_t; use rangemap::RangeMap; #[cfg(target_arch = "aarch64")] @@ -62,6 +62,144 @@ extern "C" { fn tls_ptr() -> *const c_void; } +// Reentrancy guard for the hooks +// We don't want to hook any operation initiated by the code of our hook +// Otherwise, we get into infinite recursion or deadlock +thread_local! { + static ASAN_IN_HOOK: Cell = const { Cell::new(false) }; +} + +/// RAII guard to set and reset the `ASAN_IN_HOOK` properly +#[derive(Debug)] +pub struct AsanInHookGuard; + +impl AsanInHookGuard { + /// Constructor to save the current last error + #[must_use] + pub fn new() -> Self { + ASAN_IN_HOOK.set(true); + AsanInHookGuard + } +} +impl Drop for AsanInHookGuard { + fn drop(&mut self) { + ASAN_IN_HOOK.set(false); + } +} +impl Default for AsanInHookGuard { + fn default() -> Self { + Self::new() + } +} +/// The Lock below is a simple spinlock that uses the thread id as the lock value. +/// This is a simple way to prevent reentrancy in the hooks when we don't have TLS. +/// This is not a perfect solution, as it is global so it orders all threads without TLS. +/// However, this is a rare situation and should not affect performance significantly. +use std::sync::atomic::{AtomicU64, Ordering}; +use std::thread; +#[derive(Debug)] +struct Lock { + state: AtomicU64, +} +impl Lock { + const fn new() -> Self { + Lock { + state: AtomicU64::new(u64::MAX), + } + } + + fn lock(&self) -> LockResult { + let current_thread_id = get_thread_id(); + loop { + let current_lock = self.state.load(Ordering::Relaxed); + if current_lock == u64::MAX { + if self + .state + .compare_exchange( + u64::MAX, + current_thread_id, + Ordering::Acquire, + Ordering::Relaxed, + ) + .is_ok() + { + return LockResult::Acquired; // Lock acquired + } + } else if current_lock == current_thread_id { + return LockResult::AlreadyLocked; // Already locked by the same thread + } + thread::yield_now(); // Busy wait + } + } + + fn unlock(&self) -> UnlockResult { + let current_thread_id = get_thread_id(); + let current_lock = self.state.load(Ordering::Relaxed); + if current_lock == current_thread_id { + self.state.store(u64::MAX, Ordering::Release); + return UnlockResult::Success; // Lock released + } + UnlockResult::NotOwner // Lock not owned by the current thread + } +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +use errno::{errno, set_errno, Errno}; +#[cfg(target_os = "windows")] +use winapi::shared::minwindef::DWORD; +/// We need to save and restore the last error in the hooks +#[cfg(target_os = "windows")] +use winapi::um::errhandlingapi::{GetLastError, SetLastError}; + +struct LastErrorGuard { + #[cfg(target_os = "windows")] + last_error: DWORD, + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + last_error: Errno, +} + +impl LastErrorGuard { + // Constructor to save the current last error + fn new() -> Self { + #[cfg(target_os = "windows")] + let last_error = unsafe { GetLastError() }; + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + let last_error = errno(); + + LastErrorGuard { last_error } + } +} + +// Implement the Drop trait to restore the last error +impl Drop for LastErrorGuard { + fn drop(&mut self) { + #[cfg(target_os = "windows")] + unsafe { + SetLastError(self.last_error); + } + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + set_errno(self.last_error); + } +} + +#[derive(Debug, PartialEq)] +enum LockResult { + Acquired, + AlreadyLocked, +} + +#[derive(Debug, PartialEq)] +enum UnlockResult { + Success, + NotOwner, +} + +// For threads without TLS, we use a static lock to prevent hook reentrancy +// This is not as efficient as using TLS, because it prevent TLS-free threads +// from running in parallel, but such situations are very rare (Windows loaded thread pool) +// and should not affect performance significantly +static TLS_LESS_LOCK: Lock = Lock::new(); + /// The count of registers that need to be saved by the `ASan` runtime. /// /// Sixteen general purpose registers are put in this order, `rax`, `rbx`, `rcx`, `rdx`, `rbp`, `rsp`, `rsi`, `rdi`, `r8-r15`, plus instrumented `rip`, accessed memory addr and true `rip` @@ -92,29 +230,6 @@ pub const ASAN_SAVE_REGISTER_NAMES: [&str; ASAN_SAVE_REGISTER_COUNT] = [ "actual rip", ]; -thread_local! { - static ASAN_IN_HOOK: Cell = const { Cell::new(false) }; -} - -#[inline] -#[cfg(target_arch = "aarch64")] -unsafe fn thread_local_initted() -> bool { - let mut tid: u64; - std::arch::asm!( - "mrs {tid}, TPIDRRO_EL0", - tid = out(reg) tid, - ); - tid &= 0xffff_ffff_ffff_fff8; - let tlsptr = tid as *const u64; - tlsptr.add(0x102).read() != 0u64 -} - -#[inline] -#[cfg(not(target_arch = "aarch64"))] -unsafe fn thread_local_initted() -> bool { - true -} - /// The count of registers that need to be saved by the asan runtime #[cfg(target_arch = "aarch64")] pub const ASAN_SAVE_REGISTER_COUNT: usize = 32; @@ -135,7 +250,7 @@ const ASAN_EH_FRAME_FDE_ADDRESS_OFFSET: u32 = 28; pub struct AsanRuntime { check_for_leaks_enabled: bool, current_report_impl: u64, - allocator: Allocator, + allocator: Mutex, regs: [usize; ASAN_SAVE_REGISTER_COUNT], blob_report: Option>, blob_check_mem_byte: Option>, @@ -158,6 +273,7 @@ pub struct AsanRuntime { pc: Option, hooks: Vec, pub(crate) hooks_enabled: bool, + // thread_in_hook: ThreadLocal>, #[cfg(target_arch = "aarch64")] eh_frame: [u32; ASAN_EH_FRAME_DWORD_COUNT], } @@ -184,7 +300,7 @@ impl FridaRuntime for AsanRuntime { _ranges: &RangeMap, module_map: &Rc, ) { - self.allocator.init(); + self.allocator_mut().init(); AsanErrors::get_mut_blocking().set_continue_on_error(self.continue_on_error); @@ -247,7 +363,7 @@ impl AsanRuntime { let continue_on_error = options.continue_on_error; Self { check_for_leaks_enabled: options.detect_leaks, - allocator: Allocator::new(options), + allocator: Mutex::new(Allocator::new(options)), skip_ranges, continue_on_error, ..Self::default() @@ -256,23 +372,22 @@ impl AsanRuntime { /// Reset all allocations so that they can be reused for new allocation requests. pub fn reset_allocations(&mut self) { - self.allocator.reset(); + self.allocator_mut().reset(); } /// Gets the allocator - #[must_use] - pub fn allocator(&self) -> &Allocator { - &self.allocator + pub fn allocator(&self) -> MutexGuard { + self.allocator.lock().unwrap() } /// Gets the allocator (mutable) - pub fn allocator_mut(&mut self) -> &mut Allocator { - &mut self.allocator + pub fn allocator_mut(&mut self) -> MutexGuard { + self.allocator.lock().unwrap() } /// Check if the test leaked any memory and report it if so. pub fn check_for_leaks(&mut self) { - self.allocator.check_for_leaks(); + self.allocator_mut().check_for_leaks(); } /// Returns the `AsanErrors` from the recent run. @@ -283,7 +398,7 @@ impl AsanRuntime { /// Make sure the specified memory is unpoisoned pub fn unpoison(&mut self, address: usize, size: usize) { - self.allocator + self.allocator_mut() .map_shadow_for_region(address, address + size, true); } @@ -293,7 +408,10 @@ impl AsanRuntime { /// The address needs to be a valid address, the size needs to be correct. /// This will dereference at the address. pub unsafe fn poison(&mut self, address: usize, size: usize) { - Allocator::poison(self.allocator.map_to_shadow(address), size); + let start = self.allocator_mut().map_to_shadow(address); + if self.allocator_mut().valid_shadow(start, size) { + Allocator::poison(start, size); + } } /// Add a stalked address to real address mapping. @@ -313,16 +431,18 @@ impl AsanRuntime { /// Unpoison all the memory that is currently mapped with read/write permissions. pub fn unpoison_all_existing_memory(&mut self) { - self.allocator.unpoison_all_existing_memory(); + self.allocator_mut().unpoison_all_existing_memory(); } /// Enable all function hooks pub fn enable_hooks(&mut self) { + log::info!("Enabling hooks"); self.hooks_enabled = true; } /// Disable all function hooks pub fn disable_hooks(&mut self) { self.hooks_enabled = false; + log::info!("Disabling hooks"); } /// Register the current thread with the runtime, implementing shadow memory for its stack and @@ -331,14 +451,15 @@ impl AsanRuntime { pub fn register_thread(&mut self) { let (stack_start, stack_end) = Self::current_stack(); let (tls_start, tls_end) = Self::current_tls(); - log::info!( - "registering thread with stack {stack_start:x}:{stack_end:x} and tls {tls_start:x}:{tls_end:x}" + println!( + "registering thread {:?} with stack {stack_start:x}:{stack_end:x} and tls {tls_start:x}:{tls_end:x}", + get_thread_id() ); - self.allocator + self.allocator_mut() .map_shadow_for_region(stack_start, stack_end, true); #[cfg(unix)] - self.allocator + self.allocator_mut() .map_shadow_for_region(tls_start, tls_end, true); } @@ -346,7 +467,7 @@ impl AsanRuntime { #[cfg(target_vendor = "apple")] pub fn register_thread(&mut self) { let (stack_start, stack_end) = Self::current_stack(); - self.allocator + self.allocator_mut() .map_shadow_for_region(stack_start, stack_end, true); log::info!("registering thread with stack {stack_start:x}:{stack_end:x}"); @@ -489,27 +610,28 @@ impl AsanRuntime { #[allow(non_snake_case)] unsafe extern "C" fn []($($param: $param_type),*) -> $return_type { + let _last_error_guard = LastErrorGuard::new(); let mut invocation = Interceptor::current_invocation(); let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime); //is this necessary? The stalked return address will always be the real return address // let real_address = this.real_address_for_stalked(invocation.return_addr()); let original = [<$name:snake:upper _PTR>].get().unwrap(); if this.hooks_enabled { - if thread_local_initted() { - if !ASAN_IN_HOOK.get() { - ASAN_IN_HOOK.set(true); - let ret = this.[](*original, $($param),*); - ASAN_IN_HOOK.set(false); - ret - } else { - (original)($($param),*) - } - } else { - (original)($($param),*) + if has_tls() { + if !ASAN_IN_HOOK.get(){ + let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset + return this.[](*original, $($param),*); + } } - } else { - (original)($($param),*) + // else{ + // log::warn!("{} called without TLS", stringify!($name)); + // $( + // log::warn!("{}: {:?}", stringify!($param), $param); + // )* + + // } } + (original)($($param),*) } let self_ptr = core::ptr::from_ref(self) as usize; @@ -536,27 +658,21 @@ impl AsanRuntime { #[allow(non_snake_case)] unsafe extern "C" fn []($($param: $param_type),*) -> $return_type { + let _last_error_guard = LastErrorGuard::new(); let mut invocation = Interceptor::current_invocation(); let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime); //is this necessary? The stalked return address will always be the real return address // let real_address = this.real_address_for_stalked(invocation.return_addr()); let original = [<$lib_ident:snake:upper _ $name:snake:upper _PTR>].get().unwrap(); if this.hooks_enabled { - if thread_local_initted() { - if !ASAN_IN_HOOK.get() { - ASAN_IN_HOOK.set(true); - let ret = this.[](*original, $($param),*); - ASAN_IN_HOOK.set(false); - ret - } else { - (original)($($param),*) - } - } else { - (original)($($param),*) + if has_tls() { + if !ASAN_IN_HOOK.get(){ + let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset + return this.[](*original, $($param),*); + } } - } else { - (original)($($param),*) } + (original)($($param),*) } let self_ptr = core::ptr::from_ref(self) as usize; @@ -573,7 +689,8 @@ impl AsanRuntime { #[allow(unused_macro_rules)] macro_rules! hook_func_with_check { - ($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr) => { + //No library case + ($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr ) => { paste::paste! { let target_function = Module::find_global_export_by_name(stringify!($name)).expect("Failed to find function"); @@ -582,39 +699,43 @@ impl AsanRuntime { let _ = [<$name:snake:upper _PTR>].set(unsafe {std::mem::transmute::<*const c_void, extern "C" fn($($param: $param_type),*) -> $return_type>(target_function.0)}).unwrap_or_else(|e| println!("{:?}", e)); - #[allow(non_snake_case)] + #[allow(non_snake_case)] // depends on the values the macro is invoked with + #[allow(clippy::redundant_else)] unsafe extern "C" fn []($($param: $param_type),*) -> $return_type { + let _last_error_guard = LastErrorGuard::new(); let mut invocation = Interceptor::current_invocation(); let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime); let original = [<$name:snake:upper _PTR>].get().unwrap(); - //don't check if hooks are enabled as there are certain cases where we want to run the hook even if we are out of the program - //For example, sometimes libafl will allocate certain things during the run and free them after the run. This results in a bug where a buffer will come from libafl-frida alloc and be freed in the normal allocator. if $always_enabled || this.hooks_enabled { - if thread_local_initted() { - if !ASAN_IN_HOOK.get() { - ASAN_IN_HOOK.set(true); - let ret = if this.[]($($param),*) { - this.[](*original, $($param),*) - } else { - (original)($($param),*) - }; - ASAN_IN_HOOK.set(false); - ret - } else { - (original)($($param),*) - } - } else { - let ret = if $always_enabled && this.[]($($param),*) { - this.[](*original, $($param),*) - } else { - (original)($($param),*) - }; - ret + if has_tls() { + if !ASAN_IN_HOOK.get(){ + let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset + if this.[]($($param),*){ + return this.[](*original, $($param),*); + } + } } - } else { - (original)($($param),*) - } + else{ + // log::warn!("{} called without TLS", stringify!($name)); + // $( + // log::warn!("Params: {}: {:?}", stringify!($param), $param); + // )* + if $always_enabled { + if TLS_LESS_LOCK.lock() == LockResult::Acquired && this.[]($($param),*){ + // There is no TLS and we have grabbed the lock - call the hook + let ret = this.[](*original, $($param),*); + TLS_LESS_LOCK.unlock(); + return ret; + } + else { + TLS_LESS_LOCK.unlock(); // Return the original function + } + + } + } + } + (original)($($param),*) } let self_ptr = core::ptr::from_ref(self) as usize; @@ -627,50 +748,47 @@ impl AsanRuntime { } }; //Library specific macro rule. lib and lib_ident are both needed because we need to generate a unique static variable and only name is insufficient. In addition, the lib name could contain invalid characters (i.e., lib.so is an invalid name) - ($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr) => { + ($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty, $always_enabled:expr ) => { paste::paste! { let target_function = process.find_module_by_name($lib).expect("Failed to find module").find_export_by_name(stringify!($name)).expect("Failed to find function"); log::warn!("Hooking {}:{} = {:?}", $lib, stringify!($name), target_function.0); static [<$lib_ident:snake:upper _ $name:snake:upper _PTR>]: std::sync::OnceLock $return_type> = std::sync::OnceLock::new(); - - let _ = [<$lib_ident:snake:upper _ $name:snake:upper _PTR>].set(unsafe {std::mem::transmute::<*const c_void, extern "C" fn($($param: $param_type),*) -> $return_type>(target_function.0)}).unwrap_or_else(|e| println!("{:?}", e)); #[allow(non_snake_case)] + #[allow(clippy::redundant_else)] unsafe extern "C" fn []($($param: $param_type),*) -> $return_type { + let _last_error_guard = LastErrorGuard::new(); let mut invocation = Interceptor::current_invocation(); let this = &mut *(invocation.replacement_data().unwrap().0 as *mut AsanRuntime); let original = [<$lib_ident:snake:upper _ $name:snake:upper _PTR>].get().unwrap(); - //don't check if hooks are enabled as there are certain cases where we want to run the hook even if we are out of the program - //For example, sometimes libafl will allocate certain things during the run and free them after the run. This results in a bug where a buffer will come from libafl-frida alloc and be freed in the normal allocator. if $always_enabled || this.hooks_enabled { - if thread_local_initted() { - if !ASAN_IN_HOOK.get() { - ASAN_IN_HOOK.set(true); - let ret = if this.[]($($param),*) { - this.[](*original, $($param),*) - } else { - (original)($($param),*) - }; - ASAN_IN_HOOK.set(false); - ret - } else { - (original)($($param),*) - } - } else { - let ret = if $always_enabled && this.[]($($param),*) { - this.[](*original, $($param),*) - } else { - (original)($($param),*) - }; - ret + if has_tls() { + if !ASAN_IN_HOOK.get(){ + let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset + if this.[]($($param),*){ + return this.[](*original, $($param),*); + } + } } - } else { - (original)($($param),*) - } + else{ + if $always_enabled { + if TLS_LESS_LOCK.lock() == LockResult::Acquired && this.[]($($param),*){ + // There is no TLS and we have grabbed the lock - call the hook + let ret = this.[](*original, $($param),*); + TLS_LESS_LOCK.unlock(); + return ret; + } + else { + TLS_LESS_LOCK.unlock(); // Return the original function + } + } + } + } + (original)($($param),*) } let self_ptr = core::ptr::from_ref(self) as usize; @@ -681,7 +799,14 @@ impl AsanRuntime { ); self.hooks.push(target_function); } - } + }; + // Default case without check_enabled parameter + ($name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => { + hook_func_with_check!($name, ($($param: $param_type),*), $return_type, false); + }; + ($lib:literal, $lib_ident:ident, $name:ident, ($($param:ident : $param_type:ty),*), $return_type:ty) => { + hook_func_with_check!($lib, $lib_ident, $name, ($($param: $param_type),*), $return_type, false); + }; } // Hook the memory allocator functions @@ -779,6 +904,8 @@ impl AsanRuntime { "HeapFree" => { hook_func_with_check!($libname, $lib_ident, HeapFree, (handle: *mut c_void, flags: u32, mem: *mut c_void), bool, true); } + // NOTE: we call it with always_enabled, because on Windows, some COM memory deallocation occurs later in the process + // after we have completed the run "RtlFreeHeap" => { hook_func_with_check!($libname, $lib_ident, RtlFreeHeap, (handle: *mut c_void, flags: u32, mem: *mut c_void), usize, true); } @@ -926,6 +1053,14 @@ impl AsanRuntime { *const c_void ); } + "UnmapViewOfFile" => { + hook_func!( + $libname, $lib_ident, + UnmapViewOfFile, + (ptr: *const c_void), + bool + ); + } "LoadLibraryExW" => { hook_func!( $libname, $lib_ident, @@ -963,6 +1098,8 @@ impl AsanRuntime { "api-ms-win-core-heap-obsolete-l1-1-0", api_ms_heap2_obsolete ); + hook_heap_windows!("api-ms-win-core-memory-l1-1-0", api_ms_memory1); + hook_heap_windows!("VCRUNTIME140", VCRUNTIME140); } /* @@ -1360,6 +1497,8 @@ impl AsanRuntime { #[expect(clippy::cast_sign_loss)] #[expect(clippy::too_many_lines)] extern "system" fn handle_trap(&mut self) { + // log::error!("Attach the debugger to process {:#?}", std::process::id()); + // std::thread::sleep(std::time::Duration::from_secs(30)); self.disable_hooks(); self.dump_registers(); @@ -1454,6 +1593,8 @@ impl AsanRuntime { } else if base_value.is_some() { if let Some(metadata) = self .allocator + .lock() + .unwrap() .find_metadata(fault_address, base_value.unwrap()) { match access_type { @@ -1609,6 +1750,8 @@ impl AsanRuntime { } } else if let Some(metadata) = self .allocator + .lock() + .unwrap() .find_metadata(fault_address, self.regs[base_reg as usize]) { let asan_readwrite_error = AsanReadWriteError { @@ -1778,7 +1921,7 @@ impl AsanRuntime { */ #[cfg(target_arch = "x86_64")] fn generate_shadow_check_blob(&mut self, size: u32) -> Box<[u8]> { - let shadow_bit = self.allocator.shadow_bit(); + let shadow_bit = self.allocator_mut().shadow_bit(); // Rcx, Rax, Rdi, Rdx, Rsi, R8 are used, so we save them in emit_shadow_check //at this point RDI contains the let mask_shift = 32 - size; @@ -1826,7 +1969,7 @@ impl AsanRuntime { x0 and x1 are saved by the asan_check The maximum size this supports is up to 25 bytes. This is because we load 4 bytes of the shadow value. And, in the case that we have a misaligned address with an offset of 7 into the word. For example, if we load 25 bytes from 0x1007 - [0x1007,0x101f], then we require the shadow values from 0x1000, 0x1008, 0x1010, and 0x1018 */ - let shadow_bit = self.allocator.shadow_bit(); + let shadow_bit = self.allocator_mut().shadow_bit(); macro_rules! shadow_check { ($ops:ident, $width:expr) => {dynasm!($ops ; .arch aarch64 @@ -1872,7 +2015,7 @@ impl AsanRuntime { assert!(width <= 64, "width must be <= 64"); let shift = 64 - width; - let shadow_bit = self.allocator.shadow_bit(); + let shadow_bit = self.allocator_mut().shadow_bit(); macro_rules! shadow_check_exact { ($ops:ident, $shift:expr) => {dynasm!($ops ; .arch aarch64 @@ -2543,6 +2686,8 @@ impl AsanRuntime { // on amd64 jump can takes 10 bytes at most, so that's why I put 10 bytes. writer.put_nop(); } + } else { + log::trace!("Cannot check instructions for {:?} bytes.", width); } writer.put_pop_reg(X86Register::Rdi); @@ -2788,7 +2933,7 @@ impl Default for AsanRuntime { Self { check_for_leaks_enabled: false, current_report_impl: 0, - allocator: Allocator::default(), + allocator: Mutex::new(Allocator::default()), regs: [0; ASAN_SAVE_REGISTER_COUNT], blob_report: None, blob_check_mem_byte: None, @@ -2813,6 +2958,7 @@ impl Default for AsanRuntime { pc: None, hooks: Vec::new(), hooks_enabled: false, + // thread_in_hook: ThreadLocal::new(|| Cell::new(false)), } } } diff --git a/libafl_frida/src/asan/hook_funcs.rs b/libafl_frida/src/asan/hook_funcs.rs index f5ba2d1945..680da84ae4 100644 --- a/libafl_frida/src/asan/hook_funcs.rs +++ b/libafl_frida/src/asan/hook_funcs.rs @@ -20,6 +20,14 @@ extern "system" { extern "system" { fn memset(s: *mut c_void, c: i32, n: usize) -> *mut c_void; } + +use std::ptr; + +#[cfg(windows)] +use winapi::um::memoryapi::VirtualQuery; +#[cfg(windows)] +use winapi::um::winnt::MEMORY_BASIC_INFORMATION; + #[expect(clippy::not_unsafe_ptr_arg_deref)] impl AsanRuntime { #[inline] @@ -131,6 +139,7 @@ impl AsanRuntime { _context: usize, _entry_point: usize, ) -> usize { + log::trace!("LdrpCallInitRoutine"); // winsafe::OutputDebugString("LdrpCallInitRoutine"); // let result = unsafe { LdrLoadDll(path, file, flags,x )}; // self.allocator_mut().unpoison_all_existing_memory(); @@ -174,6 +183,7 @@ impl AsanRuntime { _lock: *const c_void, _parameters: *const c_void, ) -> *mut c_void { + log::trace!("RtlCreateHeap"); 0xc0debeef as *mut c_void } #[inline] @@ -184,7 +194,8 @@ impl AsanRuntime { _original: extern "C" fn(_handle: *const c_void) -> *mut c_void, _handle: *const c_void, ) -> *mut c_void { - std::ptr::null_mut() + log::trace!("RtlDestroyHeap"); + ptr::null_mut() } #[inline] @@ -197,7 +208,8 @@ impl AsanRuntime { flags: u32, size: usize, ) -> *mut c_void { - let allocator = self.allocator_mut(); + log::trace!("HeapAlloc"); + let mut allocator = self.allocator_mut(); let ret = unsafe { allocator.alloc(size, 8) }; if flags & 8 == 8 { @@ -210,6 +222,7 @@ impl AsanRuntime { } ret } + #[inline] #[expect(non_snake_case)] #[cfg(windows)] @@ -220,7 +233,9 @@ impl AsanRuntime { flags: u32, size: usize, ) -> *mut c_void { - let allocator = self.allocator_mut(); + log::trace!("hook_RtlAllocateHeap handle {_handle:#?} flags {flags:x} size {size}"); + + let mut allocator = self.allocator_mut(); let ret = unsafe { allocator.alloc(size, 8) }; if flags & 8 == 8 { @@ -249,7 +264,8 @@ impl AsanRuntime { ptr: *mut c_void, size: usize, ) -> *mut c_void { - let allocator = self.allocator_mut(); + log::trace!("hook_HeapReAlloc handle {handle:#?} flags {flags:x} ptr {ptr:#?} size {size}"); + let mut allocator = self.allocator_mut(); if !allocator.is_managed(ptr) { return original(handle, flags, ptr, size); } @@ -293,7 +309,7 @@ impl AsanRuntime { ptr: *mut c_void, size: usize, ) -> *mut c_void { - let allocator = self.allocator_mut(); + let mut allocator = self.allocator_mut(); log::trace!("RtlReAllocateHeap({ptr:?}, {size:x})"); if !allocator.is_managed(ptr) { return original(handle, flags, ptr, size); @@ -331,6 +347,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> bool { + log::trace!("hook_check_RtlFreeHeap ptr {ptr:#?}"); self.allocator_mut().is_managed(ptr) } #[inline] @@ -343,6 +360,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> usize { + log::trace!("hook_RtlFreeHeap address handle {_handle:#?} flags 0x{_flags:x} ptr {ptr:#?}"); unsafe { self.allocator_mut().release(ptr) }; 0 } @@ -355,6 +373,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> bool { + log::trace!("hook_check_HeapFree"); self.allocator_mut().is_managed(ptr) } #[inline] @@ -367,6 +386,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> bool { + log::trace!("hook_HeapFree"); unsafe { self.allocator_mut().release(ptr) }; true } @@ -379,6 +399,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> bool { + log::trace!("hook_check_HeapSize"); self.allocator_mut().is_managed(ptr) } @@ -391,6 +412,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> usize { + log::trace!("hook_HeapSize"); self.allocator().get_usable_size(ptr) } #[inline] @@ -402,6 +424,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> bool { + log::trace!("hook_check_RtlSizeHeap"); self.allocator_mut().is_managed(ptr) } @@ -414,6 +437,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> usize { + log::trace!("hook_RtlSizeHeap"); self.allocator().get_usable_size(ptr) } #[inline] @@ -425,6 +449,7 @@ impl AsanRuntime { _flags: u32, ptr: *mut c_void, ) -> bool { + log::trace!("hook_check_RtlValidateHeap"); self.allocator_mut().is_managed(ptr) } @@ -437,6 +462,7 @@ impl AsanRuntime { _flags: u32, _ptr: *mut c_void, ) -> bool { + log::trace!("hook_RtlValidateHeap"); true } @@ -448,6 +474,7 @@ impl AsanRuntime { flags: u32, size: usize, ) -> *mut c_void { + log::trace!("hook_LocalAlloc"); let ret = unsafe { self.allocator_mut().alloc(size, 8) }; if flags & 0x40 == 0x40 { @@ -466,6 +493,7 @@ impl AsanRuntime { size: usize, _flags: u32, ) -> *mut c_void { + log::trace!("hook_LocalReAlloc"); unsafe { let ret = self.allocator_mut().alloc(size, 0x8); if !mem.is_null() && !ret.is_null() { @@ -480,6 +508,7 @@ impl AsanRuntime { #[expect(non_snake_case)] #[cfg(windows)] pub fn hook_check_LocalFree(&mut self, mem: *mut c_void) -> bool { + log::trace!("hook_check_LocalFree"); let res = self.allocator_mut().is_managed(mem); res } @@ -491,6 +520,7 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> *mut c_void, mem: *mut c_void, ) -> *mut c_void { + log::trace!("hook_LocalFree"); unsafe { self.allocator_mut().release(mem) }; mem } @@ -498,6 +528,7 @@ impl AsanRuntime { #[expect(non_snake_case)] #[cfg(windows)] pub fn hook_check_LocalHandle(&mut self, mem: *mut c_void) -> bool { + log::trace!("hook_check_LocalHandle"); self.allocator_mut().is_managed(mem) } #[expect(non_snake_case)] @@ -507,11 +538,13 @@ impl AsanRuntime { _soriginal: extern "C" fn(mem: *mut c_void) -> *mut c_void, mem: *mut c_void, ) -> *mut c_void { + log::trace!("hook_LocalHandle"); mem } #[expect(non_snake_case)] #[cfg(windows)] pub fn hook_check_LocalLock(&mut self, mem: *mut c_void) -> bool { + log::trace!("hook_check_LocalLock"); self.allocator_mut().is_managed(mem) } @@ -522,11 +555,13 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> *mut c_void, mem: *mut c_void, ) -> *mut c_void { + log::trace!("hook_LocalLock"); mem } #[expect(non_snake_case)] #[cfg(windows)] pub fn hook_check_LocalUnlock(&mut self, mem: *mut c_void) -> bool { + log::trace!("hook_check_LocalUnlock"); self.allocator_mut().is_managed(mem) } #[expect(non_snake_case)] @@ -536,11 +571,13 @@ impl AsanRuntime { _original: extern "C" fn(_mem: *mut c_void) -> bool, _mem: *mut c_void, ) -> bool { + log::trace!("hook_LocalUnlock"); false } #[expect(non_snake_case)] #[cfg(windows)] pub fn hook_check_LocalSize(&mut self, mem: *mut c_void) -> bool { + log::trace!("hook_check_LocalSize"); self.allocator_mut().is_managed(mem) } #[expect(non_snake_case)] @@ -550,11 +587,13 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> usize, mem: *mut c_void, ) -> usize { + log::trace!("hook_LocalSize"); self.allocator_mut().get_usable_size(mem) } #[expect(non_snake_case)] #[cfg(windows)] pub fn hook_check_LocalFlags(&mut self, mem: *mut c_void) -> bool { + log::trace!("hook_check_LocalFlags"); self.allocator_mut().is_managed(mem) } #[expect(non_snake_case)] @@ -564,6 +603,7 @@ impl AsanRuntime { _original: extern "C" fn(_mem: *mut c_void) -> u32, _mem: *mut c_void, ) -> u32 { + log::trace!("hook_LocalFlags"); 0 } @@ -575,6 +615,7 @@ impl AsanRuntime { flags: u32, size: usize, ) -> *mut c_void { + log::trace!("hook_GlobalAlloc"); let ret = unsafe { self.allocator_mut().alloc(size, 8) }; if flags & 0x40 == 0x40 { @@ -596,6 +637,7 @@ impl AsanRuntime { _flags: u32, size: usize, ) -> *mut c_void { + log::trace!("hook_GlobalReAlloc"); unsafe { let ret = self.allocator_mut().alloc(size, 0x8); if !mem.is_null() && !ret.is_null() { @@ -610,6 +652,7 @@ impl AsanRuntime { #[expect(non_snake_case)] #[cfg(windows)] pub fn hook_check_GlobalFree(&mut self, mem: *mut c_void) -> bool { + log::trace!("hook_check_GlobalFree"); self.allocator_mut().is_managed(mem) } #[expect(non_snake_case)] @@ -619,6 +662,7 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> *mut c_void, mem: *mut c_void, ) -> *mut c_void { + log::trace!("hook_GlobalFree"); unsafe { self.allocator_mut().release(mem) }; mem } @@ -635,6 +679,7 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> *mut c_void, mem: *mut c_void, ) -> *mut c_void { + log::trace!("hook_GlobalHandle"); mem } #[expect(non_snake_case)] @@ -650,6 +695,7 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> *mut c_void, mem: *mut c_void, ) -> *mut c_void { + log::trace!("hook_GlobalLock"); mem } #[expect(non_snake_case)] @@ -664,6 +710,7 @@ impl AsanRuntime { _original: extern "C" fn(_mem: *mut c_void) -> bool, _mem: *mut c_void, ) -> bool { + log::trace!("hook_GlobalUnlock"); false } #[expect(non_snake_case)] @@ -678,6 +725,7 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> usize, mem: *mut c_void, ) -> usize { + log::trace!("hook_GlobalSize"); self.allocator_mut().get_usable_size(mem) } #[expect(non_snake_case)] @@ -692,6 +740,7 @@ impl AsanRuntime { _original: extern "C" fn(mem: *mut c_void) -> u32, _mem: *mut c_void, ) -> u32 { + log::trace!("hook_GlobalFlags"); 0 } @@ -702,7 +751,10 @@ impl AsanRuntime { _original: extern "C" fn(size: usize) -> *mut c_void, size: usize, ) -> *mut c_void { - unsafe { self.allocator_mut().alloc(size, 8) } + unsafe { + log::trace!("hook_malloc"); + self.allocator_mut().alloc(size, 8) + } } #[inline] @@ -711,7 +763,10 @@ impl AsanRuntime { _original: extern "C" fn(size: usize) -> *mut c_void, size: usize, ) -> *mut c_void { - unsafe { self.allocator_mut().alloc(size, 8) } + unsafe { + log::trace!("hook_malloc"); + self.allocator_mut().alloc(size, 8) + } } #[inline] @@ -720,7 +775,10 @@ impl AsanRuntime { _original: extern "C" fn(size: usize) -> *mut c_void, size: usize, ) -> *mut c_void { - unsafe { self.allocator_mut().alloc(size, 8) } + unsafe { + log::trace!("hook_o_malloc"); + self.allocator_mut().alloc(size, 8) + } } #[expect(non_snake_case)] @@ -730,7 +788,10 @@ impl AsanRuntime { _original: extern "C" fn(size: usize) -> *mut c_void, size: usize, ) -> *mut c_void { - unsafe { self.allocator_mut().alloc(size, 8) } + unsafe { + log::trace!("hook__Znam"); + self.allocator_mut().alloc(size, 8) + } } #[expect(non_snake_case)] @@ -741,6 +802,7 @@ impl AsanRuntime { size: usize, _nothrow: *const c_void, ) -> *mut c_void { + log::trace!("hook__ZnamRKSt9nothrow_t"); unsafe { self.allocator_mut().alloc(size, 8) } } @@ -752,6 +814,7 @@ impl AsanRuntime { size: usize, alignment: usize, ) -> *mut c_void { + log::trace!("hook__ZnamSt11align_val_t"); unsafe { self.allocator_mut().alloc(size, alignment) } } @@ -768,6 +831,7 @@ impl AsanRuntime { alignment: usize, _nothrow: *const c_void, ) -> *mut c_void { + log::trace!("hook__ZnamSt11align_val_tRKSt9nothrow_t"); unsafe { self.allocator_mut().alloc(size, alignment) } } @@ -780,6 +844,7 @@ impl AsanRuntime { _original: extern "C" fn(size: usize) -> *mut c_void, size: usize, ) -> *mut c_void { + log::trace!("hook__Znwm"); let result = unsafe { self.allocator_mut().alloc(size, 8) }; if result.is_null() { extern "system" { @@ -803,6 +868,7 @@ impl AsanRuntime { size: usize, _nothrow: *const c_void, ) -> *mut c_void { + log::trace!("hook__ZnwmRKSt9nothrow_t"); unsafe { self.allocator_mut().alloc(size, 8) } } @@ -816,6 +882,7 @@ impl AsanRuntime { size: usize, alignment: usize, ) -> *mut c_void { + log::trace!("hook__ZnwmSt11align_val_t"); let result = unsafe { self.allocator_mut().alloc(size, alignment) }; if result.is_null() { extern "system" { @@ -842,6 +909,7 @@ impl AsanRuntime { alignment: usize, _nothrow: *const c_void, ) -> *mut c_void { + log::trace!("hook__ZnwmSt11align_val_tRKSt9nothrow_t"); unsafe { self.allocator_mut().alloc(size, alignment) } } @@ -852,6 +920,7 @@ impl AsanRuntime { _original: extern "C" fn(size: usize) -> *mut c_void, size: usize, ) -> *mut c_void { + log::trace!("hook__o_malloc"); unsafe { self.allocator_mut().alloc(size, 8) } } #[inline] @@ -864,6 +933,7 @@ impl AsanRuntime { extern "system" { fn memset(s: *mut c_void, c: i32, n: usize) -> *mut c_void; } + log::trace!("hook_calloc"); let ret = unsafe { self.allocator_mut().alloc(size * nmemb, 8) }; // if size * nmemb == 0x10 { // log::error!("backtrace: {:0x?}", frida_gum::Backtracer::accurate()); @@ -887,6 +957,7 @@ impl AsanRuntime { extern "system" { fn memset(s: *mut c_void, c: i32, n: usize) -> *mut c_void; } + log::trace!("hook__o_calloc"); let ret = unsafe { self.allocator_mut().alloc(size * nmemb, 8) }; unsafe { memset(ret, 0, size * nmemb); @@ -907,16 +978,17 @@ impl AsanRuntime { ptr: *mut c_void, size: usize, ) -> *mut c_void { + log::trace!("hook_realloc"); unsafe { if size == 0 { self.allocator_mut().release(ptr); #[cfg(not(target_vendor = "apple"))] - return std::ptr::null_mut(); + return ptr::null_mut(); #[cfg(target_vendor = "apple")] return self.allocator_mut().alloc(0, 0x8); } let ret = self.allocator_mut().alloc(size, 0x8); - if ptr != std::ptr::null_mut() && ret != std::ptr::null_mut() { + if ptr != ptr::null_mut() && ret != ptr::null_mut() { let old_size = self.allocator_mut().get_usable_size(ptr); let copy_size = if size < old_size { size } else { old_size }; (ptr as *mut u8).copy_to(ret as *mut u8, copy_size); @@ -947,7 +1019,7 @@ impl AsanRuntime { return self.allocator_mut().alloc(0, 0x8); } let ret = self.allocator_mut().alloc(size, 0x8); - if ptr != std::ptr::null_mut() && ret != std::ptr::null_mut() { + if ptr != ptr::null_mut() && ret != ptr::null_mut() { let old_size = self.allocator_mut().get_usable_size(ptr); let copy_size = if size < old_size { size } else { old_size }; (ptr as *mut u8).copy_to(ret as *mut u8, copy_size); @@ -966,9 +1038,10 @@ impl AsanRuntime { ptr: *mut c_void, size: usize, ) -> *mut c_void { + log::trace!("hook__o_realloc"); unsafe { let ret = self.allocator_mut().alloc(size, 0x8); - if ptr != std::ptr::null_mut() && ret != std::ptr::null_mut() { + if ptr != ptr::null_mut() && ret != ptr::null_mut() { let old_size = self.allocator_mut().get_usable_size(ptr); let copy_size = if size < old_size { size } else { old_size }; (ptr as *mut u8).copy_to(ret as *mut u8, copy_size); @@ -981,6 +1054,7 @@ impl AsanRuntime { #[expect(non_snake_case)] #[inline] pub fn hook_check__o_free(&mut self, ptr: *mut c_void) -> bool { + log::trace!("hook_check__o_free"); self.allocator_mut().is_managed(ptr) } @@ -992,7 +1066,8 @@ impl AsanRuntime { _original: extern "C" fn(ptr: *mut c_void) -> usize, ptr: *mut c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("hook__o_free"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1009,7 +1084,7 @@ impl AsanRuntime { _original: extern "C" fn(ptr: *mut c_void) -> usize, ptr: *mut c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1023,6 +1098,7 @@ impl AsanRuntime { alignment: usize, size: usize, ) -> *mut c_void { + log::trace!("hook_memalign"); unsafe { self.allocator_mut().alloc(size, alignment) } } @@ -1034,6 +1110,7 @@ impl AsanRuntime { alignment: usize, size: usize, ) -> i32 { + log::trace!("hook_posix_memalign"); unsafe { *pptr = self.allocator_mut().alloc(size, alignment); } @@ -1047,6 +1124,7 @@ impl AsanRuntime { _original: extern "C" fn(ptr: *mut c_void) -> usize, ptr: *mut c_void, ) -> usize { + log::trace!("hook_malloc_usable_size"); self.allocator_mut().get_usable_size(ptr) } #[inline] @@ -1219,6 +1297,7 @@ impl AsanRuntime { _file_offset_low: u32, size: usize, ) -> *const c_void { + log::trace!("hook_MapViewOfFile size {:?}", size); let ret = original( _handle, _desired_access, @@ -1226,7 +1305,86 @@ impl AsanRuntime { _file_offset_low, size, ); + + let mut size = size; + if size == 0 { + // The entire file is mapped starting from the offset + // We need to get the real size before unpoisoning it + // Use VirtualQuery to get the size of the mapped memory + let mut mem_info = MEMORY_BASIC_INFORMATION { + BaseAddress: ptr::null_mut(), + AllocationBase: ptr::null_mut(), + AllocationProtect: 0, + RegionSize: 0, + State: 0, + Protect: 0, + Type: 0, + }; + + let result = unsafe { + VirtualQuery( + ret as *const winapi::ctypes::c_void, + &mut mem_info, + size_of::(), + ) + }; + + if result == 0 { + log::error!("Failed to query virtual memory"); + } else { + size = mem_info.RegionSize; + } + } + self.unpoison(ret as usize, size); + log::trace!("hook_MapViewOfFile returns {:p}", ret); + ret + } + + #[inline] + #[expect(non_snake_case)] + #[cfg(windows)] + pub fn hook_UnmapViewOfFile( + &mut self, + original: extern "C" fn(ptr: *const c_void) -> bool, + ptr: *const c_void, + ) -> bool { + log::info!("hook_UnmapViewOfFile {:p}", ptr); + + let mut size = 0; + // We need to get the mapping size before poisoning it + // Use VirtualQuery to get the size of the mapped memory + let mut mem_info = MEMORY_BASIC_INFORMATION { + BaseAddress: ptr::null_mut(), + AllocationBase: ptr::null_mut(), + AllocationProtect: 0, + RegionSize: 0, + State: 0, + Protect: 0, + Type: 0, + }; + + let result = unsafe { + VirtualQuery( + ptr as *const winapi::ctypes::c_void, + &mut mem_info, + size_of::(), + ) + }; + + if result == 0 { + log::error!("Failed to query virtual memory for poisoning"); + } else { + size = mem_info.RegionSize; + log::info!("Size of mapped memory: {} bytes", size); + } + + let ret = original(ptr); + + if size > 0 { + unsafe { self.poison(ptr as usize, size) }; + } + ret } @@ -1238,7 +1396,8 @@ impl AsanRuntime { _original: extern "C" fn(ptr: *mut c_void) -> usize, ptr: *mut c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete[]"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1253,7 +1412,8 @@ impl AsanRuntime { ptr: *mut c_void, _ulong: u64, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete[]"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1269,7 +1429,8 @@ impl AsanRuntime { _ulong: u64, _alignment: usize, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete[](void*, std::size_t)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1284,7 +1445,8 @@ impl AsanRuntime { ptr: *mut c_void, _nothrow: *const c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete[](void*, std::size_t, std::align_val_t)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1304,7 +1466,8 @@ impl AsanRuntime { _alignment: usize, _nothrow: *const c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete[](void*, std::nothrow_t const&)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1319,7 +1482,8 @@ impl AsanRuntime { ptr: *mut c_void, _alignment: usize, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete[](void*, std::align_val_t)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1333,7 +1497,7 @@ impl AsanRuntime { _original: extern "C" fn(ptr: *mut c_void) -> usize, ptr: *mut c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1348,7 +1512,8 @@ impl AsanRuntime { ptr: *mut c_void, _ulong: u64, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete(void*)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1364,7 +1529,8 @@ impl AsanRuntime { _ulong: u64, _alignment: usize, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete(void*)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1379,7 +1545,8 @@ impl AsanRuntime { ptr: *mut c_void, _nothrow: *const c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete(void*)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1399,7 +1566,8 @@ impl AsanRuntime { _alignment: usize, _nothrow: *const c_void, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete(void*)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1414,7 +1582,8 @@ impl AsanRuntime { ptr: *mut c_void, _alignment: usize, ) -> usize { - if ptr != std::ptr::null_mut() { + log::trace!("delete(void*)"); + if ptr != ptr::null_mut() { unsafe { self.allocator_mut().release(ptr) } } 0 @@ -1439,6 +1608,7 @@ impl AsanRuntime { fd: i32, offset: usize, ) -> *mut c_void { + log::trace!("hook_mmap"); let res = original(addr, length, prot, flags, fd, offset); if res != (-1_isize as *mut c_void) { self.allocator_mut() @@ -1456,6 +1626,7 @@ impl AsanRuntime { addr: *const c_void, length: usize, ) -> i32 { + log::trace!("hook_munmap"); let res = original(addr, length); if res != -1 { Allocator::poison(self.allocator_mut().map_to_shadow(addr as usize), length); @@ -1472,6 +1643,7 @@ impl AsanRuntime { buf: *const c_void, count: usize, ) -> usize { + log::trace!("hook__write"); self.hook_write(original, fd, buf, count) } #[inline] @@ -1482,6 +1654,7 @@ impl AsanRuntime { buf: *const c_void, count: usize, ) -> usize { + log::trace!("hook_write"); if !self.allocator_mut().check_shadow(buf, count) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "write".to_string(), @@ -1505,6 +1678,7 @@ impl AsanRuntime { buf: *mut c_void, count: usize, ) -> usize { + log::trace!("hook__read"); self.hook_read(original, fd, buf, count) } #[inline] @@ -1515,6 +1689,7 @@ impl AsanRuntime { buf: *mut c_void, count: usize, ) -> usize { + log::trace!("hook_read"); if !self.allocator_mut().check_shadow(buf, count) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "read".to_string(), @@ -1537,6 +1712,7 @@ impl AsanRuntime { size: u32, stream: *mut c_void, ) -> *mut c_void { + log::trace!("hook_fgets"); if !self.allocator_mut().check_shadow(s, size as usize) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "fgets".to_string(), @@ -1559,6 +1735,7 @@ impl AsanRuntime { s2: *const c_void, n: usize, ) -> i32 { + log::trace!("hook_memcmp"); if !self.allocator_mut().check_shadow(s1, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memcmp".to_string(), @@ -1592,6 +1769,7 @@ impl AsanRuntime { src: *const c_void, n: usize, ) -> *mut c_void { + log::trace!("hook_memcpy dest {dest:#?} src {src:#?} size {n}"); if !self.allocator_mut().check_shadow(dest, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memcpy".to_string(), @@ -1626,6 +1804,7 @@ impl AsanRuntime { src: *const c_void, n: usize, ) -> *mut c_void { + log::trace!("hook_mempcpy"); if !self.allocator_mut().check_shadow(dest, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "mempcpy".to_string(), @@ -1659,6 +1838,7 @@ impl AsanRuntime { src: *const c_void, n: usize, ) -> *mut c_void { + log::trace!("hook_memmove"); if !self.allocator_mut().check_shadow(dest, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memmove".to_string(), @@ -1693,6 +1873,7 @@ impl AsanRuntime { c: i32, n: usize, ) -> *mut c_void { + log::trace!("hook_memset"); if !self.allocator_mut().check_shadow(dest, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset".to_string(), @@ -1715,6 +1896,7 @@ impl AsanRuntime { c: i32, n: usize, ) -> *mut c_void { + log::trace!("hook_memchr"); if !self.allocator_mut().check_shadow(s, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memchr".to_string(), @@ -1738,6 +1920,7 @@ impl AsanRuntime { c: i32, n: usize, ) -> *mut c_void { + log::trace!("hook_memrchr"); if !self.allocator_mut().check_shadow(s, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memrchr".to_string(), @@ -1766,6 +1949,7 @@ impl AsanRuntime { needle: *const c_void, needlelen: usize, ) -> *mut c_void { + log::trace!("hook_memmem"); if !self.allocator_mut().check_shadow(haystack, haystacklen) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memmem".to_string(), @@ -1799,6 +1983,7 @@ impl AsanRuntime { s: *mut c_void, n: usize, ) -> usize { + log::trace!("hook_bzero"); if !self.allocator_mut().check_shadow(s, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "bzero".to_string(), @@ -1821,6 +2006,7 @@ impl AsanRuntime { s: *mut c_void, n: usize, ) -> usize { + log::trace!("hook_explicit_bzero"); if !self.allocator_mut().check_shadow(s, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "explicit_bzero".to_string(), @@ -1844,6 +2030,7 @@ impl AsanRuntime { s2: *const c_void, n: usize, ) -> i32 { + log::trace!("hook_bcmp"); if !self.allocator_mut().check_shadow(s1, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "bcmp".to_string(), @@ -1880,6 +2067,7 @@ impl AsanRuntime { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strchr"); if !self .allocator_mut() .check_shadow(s as *const c_void, unsafe { strlen(s) }) @@ -1906,6 +2094,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strrchr"); if !self .allocator_mut() .check_shadow(s as *const c_void, unsafe { strlen(s) }) @@ -1932,6 +2121,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strcasecmp"); if !self .allocator_mut() .check_shadow(s1 as *const c_void, unsafe { strlen(s1) }) @@ -1969,6 +2159,7 @@ impl AsanRuntime { s2: *const c_char, n: usize, ) -> i32 { + log::trace!("hook_strncasecmp"); if !self.allocator_mut().check_shadow(s1 as *const c_void, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strncasecmp".to_string(), @@ -2004,6 +2195,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strcat"); if !self .allocator_mut() .check_shadow(s1 as *const c_void, unsafe { strlen(s1) }) @@ -2043,6 +2235,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strcmp"); if !self .allocator_mut() .check_shadow(s1 as *const c_void, unsafe { strlen(s1) }) @@ -2083,6 +2276,7 @@ impl AsanRuntime { extern "system" { fn strnlen(s: *const c_char, n: usize) -> usize; } + log::trace!("hook_strncmp"); if !self .allocator_mut() .check_shadow(s1 as *const c_void, unsafe { strnlen(s1, n) }) @@ -2122,6 +2316,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strcpy"); if !self .allocator_mut() .check_shadow(dest as *const c_void, unsafe { strlen(src) }) @@ -2162,6 +2357,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strncpy"); if !self.allocator_mut().check_shadow(dest as *const c_void, n) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "strncpy".to_string(), @@ -2198,6 +2394,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_stpcpy"); if !self .allocator_mut() .check_shadow(dest as *const c_void, unsafe { strlen(src) }) @@ -2234,6 +2431,7 @@ impl AsanRuntime { original: extern "C" fn(s: *const c_char) -> *mut c_char, s: *const c_char, ) -> *mut c_char { + log::trace!("hook__strdup"); self.hook_strdup(original, s) } #[inline] @@ -2246,6 +2444,7 @@ impl AsanRuntime { fn strlen(s: *const c_char) -> usize; fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char; } + log::trace!("hook_strdup"); let size = unsafe { strlen(s) + 1 }; if !self.allocator_mut().check_shadow(s as *const c_void, size) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -2272,6 +2471,7 @@ impl AsanRuntime { original: extern "C" fn(s: *const c_char) -> usize, s: *const c_char, ) -> usize { + log::trace!("hook_strlen"); let size = original(s); if !self.allocator_mut().check_shadow(s as *const c_void, size) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -2294,6 +2494,7 @@ impl AsanRuntime { s: *const c_char, n: usize, ) -> usize { + log::trace!("hook_strnlen"); let size = original(s, n); if !self.allocator_mut().check_shadow(s as *const c_void, size) && AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -2319,6 +2520,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strstr"); if !self .allocator_mut() .check_shadow(haystack as *const c_void, unsafe { strlen(haystack) }) @@ -2358,6 +2560,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_strcasestr"); if !self .allocator_mut() .check_shadow(haystack as *const c_void, unsafe { strlen(haystack) }) @@ -2396,6 +2599,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_atoi"); if !self .allocator_mut() .check_shadow(s as *const c_void, unsafe { strlen(s) }) @@ -2422,6 +2626,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_atol"); if !self .allocator_mut() .check_shadow(s as *const c_void, unsafe { strlen(s) }) @@ -2448,6 +2653,7 @@ impl AsanRuntime { extern "system" { fn strlen(s: *const c_char) -> usize; } + log::trace!("hook_atoll"); if !self .allocator_mut() .check_shadow(s as *const c_void, unsafe { strlen(s) }) @@ -2471,6 +2677,7 @@ impl AsanRuntime { original: extern "C" fn(s: *const wchar_t) -> usize, s: *const wchar_t, ) -> usize { + log::trace!("hook_wcslen"); let size = original(s); if !self .allocator_mut() @@ -2499,6 +2706,7 @@ impl AsanRuntime { extern "system" { fn wcslen(s: *const wchar_t) -> usize; } + log::trace!("hook_wcscpy"); if !self .allocator_mut() .check_shadow(dest as *const c_void, unsafe { (wcslen(src) + 1) * 2 }) @@ -2539,6 +2747,7 @@ impl AsanRuntime { extern "system" { fn wcslen(s: *const wchar_t) -> usize; } + log::trace!("hook_wcscmp"); if !self .allocator_mut() .check_shadow(s1 as *const c_void, unsafe { (wcslen(s1) + 1) * 2 }) diff --git a/libafl_frida/src/executor.rs b/libafl_frida/src/executor.rs index 882968b9d7..83ba6dfeeb 100644 --- a/libafl_frida/src/executor.rs +++ b/libafl_frida/src/executor.rs @@ -1,23 +1,19 @@ use core::fmt::{self, Debug, Formatter}; #[cfg(all(windows, not(test)))] use std::process::abort; -use std::{ffi::c_void, marker::PhantomData}; +use std::{cell::RefCell, ffi::c_void, marker::PhantomData, rc::Rc}; use frida_gum::{ stalker::{NoneEventSink, Stalker}, Gum, MemoryRange, NativePointer, }; #[cfg(windows)] -use libafl::{ - executors::{hooks::inprocess::InProcessHooks, inprocess::HasInProcessHooks}, - inputs::Input, - state::{HasCurrentTestcase, HasSolutions}, -}; +use libafl::executors::{hooks::inprocess::InProcessHooks, inprocess::HasInProcessHooks}; use libafl::{ executors::{Executor, ExitKind, HasObservers, InProcessExecutor}, - inputs::{NopTargetBytesConverter, TargetBytesConverter}, + inputs::{Input, NopTargetBytesConverter, TargetBytesConverter}, observers::ObserversTuple, - state::HasExecutions, + state::{HasCurrentTestcase, HasExecutions, HasSolutions}, Error, }; use libafl_bolts::{tuples::RefIndexable, AsSlice}; @@ -29,38 +25,41 @@ use crate::helper::{FridaInstrumentationHelper, FridaRuntimeTuple}; use crate::windows_hooks::initialize; /// The [`FridaInProcessExecutor`] is an [`Executor`] that executes the target in the same process, usinig [`frida`](https://frida.re/) for binary-only instrumentation. -pub struct FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z> { +pub struct FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, TC, Z> { base: InProcessExecutor<'a, EM, H, I, OT, S, Z>, /// `thread_id` for the Stalker thread_id: Option, /// Frida's dynamic rewriting engine stalker: Stalker, /// User provided callback for instrumentation - helper: &'c mut FridaInstrumentationHelper<'b, RT>, + helper: Rc>>, target_bytes_converter: TC, followed: bool, _phantom: PhantomData<&'b u8>, } impl Debug - for FridaInProcessExecutor<'_, '_, '_, EM, H, I, OT, RT, S, TC, Z> + for FridaInProcessExecutor<'_, '_, EM, H, I, OT, RT, S, TC, Z> where OT: Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("FridaInProcessExecutor") .field("base", &self.base) - .field("helper", &self.helper) + .field("helper", &self.helper.borrow_mut()) .field("followed", &self.followed) .finish_non_exhaustive() } } impl Executor - for FridaInProcessExecutor<'_, '_, '_, EM, H, I, OT, RT, S, TC, Z> + for FridaInProcessExecutor<'_, '_, EM, H, I, OT, RT, S, TC, Z> where H: FnMut(&I) -> ExitKind, + I: Input, S: HasExecutions, + S: HasCurrentTestcase, + S: HasSolutions, TC: TargetBytesConverter, OT: ObserversTuple, RT: FridaRuntimeTuple, @@ -75,13 +74,12 @@ where input: &I, ) -> Result { let target_bytes = self.target_bytes_converter.to_target_bytes(input); - self.helper.pre_exec(target_bytes.as_slice())?; - if self.helper.stalker_enabled() { - if self.followed { - self.stalker.activate(NativePointer(core::ptr::null_mut())); - } else { + self.helper.borrow_mut().pre_exec(target_bytes.as_slice())?; + if self.helper.borrow_mut().stalker_enabled() { + if !(self.followed) { self.followed = true; - let transformer = self.helper.transformer(); + let helper_binding = self.helper.borrow_mut(); + let transformer = helper_binding.transformer(); if let Some(thread_id) = self.thread_id { self.stalker.follow::( thread_id.try_into().unwrap(), @@ -90,11 +88,19 @@ where ); } else { self.stalker.follow_me::(transformer, None); + self.stalker.deactivate(); } } + // We removed the fuzzer from the stalked ranges, + // but we need to pass the harness entry point + // so that Stalker knows to pick it despite the module being excluded + let harness_fn_ref: &H = self.base.harness(); + let ptr: *const H = harness_fn_ref as *const H; + log::info!("Activating Stalker for {:p}", ptr); + self.stalker.activate(NativePointer(ptr as *mut c_void)); } let res = self.base.run_target(fuzzer, state, mgr, input); - if self.helper.stalker_enabled() { + if self.helper.borrow_mut().stalker_enabled() { self.stalker.deactivate(); } @@ -107,13 +113,15 @@ where abort(); } } - self.helper.post_exec(target_bytes.as_slice())?; + self.helper + .borrow_mut() + .post_exec(target_bytes.as_slice())?; res } } impl HasObservers - for FridaInProcessExecutor<'_, '_, '_, EM, H, I, OT, RT, S, TC, Z> + for FridaInProcessExecutor<'_, '_, EM, H, I, OT, RT, S, TC, Z> { type Observers = OT; #[inline] @@ -127,8 +135,8 @@ impl HasObservers } } -impl<'a, 'b, 'c, EM, H, I, OT, RT, S, Z> - FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, NopTargetBytesConverter, Z> +impl<'a, 'b, EM, H, I, OT, RT, S, Z> + FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, NopTargetBytesConverter, Z> where RT: FridaRuntimeTuple, { @@ -136,7 +144,7 @@ where pub fn new( gum: &'a Gum, base: InProcessExecutor<'a, EM, H, I, OT, S, Z>, - helper: &'c mut FridaInstrumentationHelper<'b, RT>, + helper: Rc>>, ) -> Self { FridaInProcessExecutor::with_target_bytes_converter( gum, @@ -151,7 +159,7 @@ where pub fn on_thread( gum: &'a Gum, base: InProcessExecutor<'a, EM, H, I, OT, S, Z>, - helper: &'c mut FridaInstrumentationHelper<'b, RT>, + helper: Rc>>, thread_id: u32, ) -> Self { FridaInProcessExecutor::with_target_bytes_converter( @@ -164,8 +172,7 @@ where } } -impl<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z> - FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z> +impl<'a, 'b, EM, H, I, OT, RT, S, TC, Z> FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, TC, Z> where RT: FridaRuntimeTuple, { @@ -173,31 +180,39 @@ where pub fn with_target_bytes_converter( gum: &'a Gum, base: InProcessExecutor<'a, EM, H, I, OT, S, Z>, - helper: &'c mut FridaInstrumentationHelper<'b, RT>, + helper: Rc>>, thread_id: Option, target_bytes_converter: TC, ) -> Self { let mut stalker = Stalker::new(gum); - // Include the current module (the fuzzer) in stalked ranges. We clone the ranges so that - // we don't add it to the INSTRUMENTED ranges. - let mut ranges = helper.ranges().clone(); + let ranges = helper.borrow_mut().ranges().clone(); for module in frida_gum::Process::obtain(gum).enumerate_modules() { let range = module.range(); if (range.base_address().0 as usize) < Self::with_target_bytes_converter as usize && (Self::with_target_bytes_converter as usize as u64) < range.base_address().0 as u64 + range.size() as u64 { - ranges.insert( - range.base_address().0 as u64 - ..(range.base_address().0 as u64 + range.size() as u64), - (0xffff, "fuzzer".to_string()), + log::info!( + "Fuzzer range: {:x}-{:x}", + range.base_address().0 as u64, + range.base_address().0 as u64 + range.size() as u64 ); + // Exclude the fuzzer from the stalked ranges, it is really unnecessary and harmfull. + // Otherwise, Stalker starts messing with our hooks and their callbacks + // wrecking havoc and causing deadlocks + stalker.exclude(&MemoryRange::new( + NativePointer(range.base_address().0), + range.size(), + )); break; } } - log::info!("disable_excludes: {:}", helper.disable_excludes); - if !helper.disable_excludes { + log::info!( + "disable_excludes: {:}", + helper.borrow_mut().disable_excludes + ); + if !helper.borrow_mut().disable_excludes { for range in ranges.gaps(&(0..u64::MAX)) { log::info!("excluding range: {:x}-{:x}", range.start, range.end); stalker.exclude(&MemoryRange::new( @@ -225,8 +240,8 @@ where } #[cfg(windows)] -impl<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z> HasInProcessHooks - for FridaInProcessExecutor<'a, 'b, 'c, EM, H, I, OT, RT, S, TC, Z> +impl<'a, 'b, EM, H, I, OT, RT, S, TC, Z> HasInProcessHooks + for FridaInProcessExecutor<'a, 'b, EM, H, I, OT, RT, S, TC, Z> where H: FnMut(&I) -> ExitKind, S: HasSolutions + HasCurrentTestcase + HasExecutions, diff --git a/libafl_frida/src/frida_helper_shutdown_observer.rs b/libafl_frida/src/frida_helper_shutdown_observer.rs new file mode 100644 index 0000000000..7630cd12c1 --- /dev/null +++ b/libafl_frida/src/frida_helper_shutdown_observer.rs @@ -0,0 +1,97 @@ +use std::{borrow::Cow, cell::RefCell, fmt, rc::Rc}; + +use libafl::{executors::ExitKind, inputs::HasTargetBytes, observers::Observer}; +use libafl_bolts::{Error, Named}; +use serde::{ + de::{self, Deserialize, Deserializer, MapAccess, Visitor}, + Serialize, +}; + +use crate::helper::{FridaInstrumentationHelper, FridaRuntimeTuple}; + +#[allow(clippy::unsafe_derive_deserialize)] +#[derive(Serialize, Debug)] +/// An observer that shuts down the Frida helper upon crash +/// This is necessary as we don't want to keep the instrumentation around when processing the crash +pub struct FridaHelperObserver<'a, RT> { + #[serde(skip)] + // helper: &'a RefCell>, + helper: Rc>>, +} + +impl<'a, RT> FridaHelperObserver<'a, RT> +where + RT: FridaRuntimeTuple + 'a, +{ + /// Creates a new [`FridaHelperObserver`] with the given name. + #[must_use] + pub fn new( + // helper: &'a RefCell>, + helper: Rc>>, + ) -> Self { + Self { helper } + } +} + +impl<'a, I, S, RT> Observer for FridaHelperObserver<'a, RT> +where + // S: UsesInput, + // S::Input: HasTargetBytes, + RT: FridaRuntimeTuple + 'a, + I: HasTargetBytes, +{ + fn post_exec(&mut self, _state: &mut S, input: &I, exit_kind: &ExitKind) -> Result<(), Error> { + if *exit_kind == ExitKind::Crash { + // Custom implementation logic for `FridaInProcessExecutor` + log::error!("Custom post_exec called for FridaInProcessExecutorHelper"); + // Add any custom logic specific to FridaInProcessExecutor + return self.helper.borrow_mut().post_exec(&input.target_bytes()); + } + Ok(()) + } +} + +impl Named for FridaHelperObserver<'_, RT> { + fn name(&self) -> &Cow<'static, str> { + static NAME: Cow<'static, str> = Cow::Borrowed("FridaHelperObserver"); + &NAME + } +} + +impl<'de, RT> Deserialize<'de> for FridaHelperObserver<'_, RT> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FridaHelperObserverVisitor<'a, RT> { + // marker: std::marker::PhantomData<&'b mut FridaInstrumentationHelper<'a, RT>>, + marker: std::marker::PhantomData<&'a RT>, + } + + impl<'de, 'a, RT> Visitor<'de> for FridaHelperObserverVisitor<'a, RT> { + type Value = FridaHelperObserver<'a, RT>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a FridaHelperObserver struct") + } + + fn visit_map(self, _map: M) -> Result + where + M: MapAccess<'de>, + { + // Construct the struct without deserializing `helper` + Err(de::Error::custom( + "Cannot deserialize `FridaHelperObserver` with a mutable reference", + )) + } + } + + deserializer.deserialize_struct( + "FridaHelperObserver", + &[], // No fields to deserialize + FridaHelperObserverVisitor { + marker: std::marker::PhantomData, + }, + ) + } +} diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index b5a60728af..33781059c6 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -451,18 +451,22 @@ impl FridaInstrumentationHelperBuilder { start..(start + range.size() as u64), (i as u16, module.path()), ); - } - for skip in skip_ranges { - match skip { - SkipRange::Absolute(range) => ranges - .borrow_mut() - .remove(range.start as u64..range.end as u64), - SkipRange::ModuleRelative { name, range } => { - let module_details = Module::load(gum, &name); - let lib_start = module_details.range().base_address().0 as u64; - ranges.borrow_mut().remove( - (lib_start + range.start as u64)..(lib_start + range.end as u64), - ); + for skip in &skip_ranges { + match skip { + SkipRange::Absolute(range) => ranges + .borrow_mut() + .remove(range.start as u64..range.end as u64), + SkipRange::ModuleRelative { name, range } => { + if name.eq(&module.name()) { + log::trace!("Skipping {:?} {:?}", name, range); + let module_details = Module::load(gum, &name.to_string()); + let lib_start = module_details.range().base_address().0 as u64; + ranges.borrow_mut().remove( + (lib_start + range.start as u64) + ..(lib_start + range.end as u64), + ); + } + } } } } @@ -650,6 +654,7 @@ where let mut first = true; let mut basic_block_start = 0; let mut basic_block_size = 0; + // let _guard = AsanInHookGuard::new(); // Ensure ASAN_IN_HOOK is set and reset for instruction in basic_block { let instr = instruction.instr(); let instr_size = instr.bytes().len(); diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index aa4ad95744..7be0c8133b 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -61,7 +61,7 @@ pub mod pthread_hook; #[cfg(feature = "cmplog")] pub mod cmplog_rt; -/// The `LibAFL` firda helper +/// The `LibAFL` frida helper pub mod helper; pub mod drcov_rt; @@ -72,6 +72,9 @@ pub mod executor; /// Utilities pub mod utils; +/// The frida helper shutdown observer, needed to remove the instrumentation upon crashing +pub mod frida_helper_shutdown_observer; + // for parsing asan and cmplog cores use libafl_bolts::core_affinity::{get_core_ids, CoreId, Cores}; @@ -323,7 +326,7 @@ impl Default for FridaOptions { #[cfg(test)] mod tests { use core::num::NonZero; - use std::sync::OnceLock; + use std::{cell::RefCell, rc::Rc, sync::OnceLock}; use clap::Parser; use frida_gum::Gum; @@ -343,7 +346,6 @@ mod tests { use libafl_bolts::{ cli::FuzzerOptions, rands::StdRand, tuples::tuple_list, AsSlice, SimpleStdoutLogger, }; - #[cfg(unix)] use mimalloc::MiMalloc; use crate::{ @@ -353,16 +355,11 @@ mod tests { }, coverage_rt::CoverageRuntime, executor::FridaInProcessExecutor, + frida_helper_shutdown_observer::FridaHelperObserver, 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(); @@ -404,6 +401,14 @@ mod tests { ), ("malloc_heap_uaf_write", Some("heap use-after-free write")), ("malloc_heap_uaf_read", Some("heap use-after-free read")), + ( + "heap_oob_memcpy_read", + Some("function arg resulting in bad read"), + ), + ( + "heap_oob_memcpy_write", + Some("function arg resulting in bad write"), + ), ]; //NOTE: RTLD_NOW is required on linux as otherwise the hooks will NOT work @@ -420,11 +425,16 @@ mod tests { let coverage = CoverageRuntime::new(); let asan = AsanRuntime::new(options); - let mut frida_helper = FridaInstrumentationHelper::new( + // let mut frida_helper = FridaInstrumentationHelper::new( + // GUM.get().expect("Gum uninitialized"), + // options, + // tuple_list!(coverage, asan), + // ); + let frida_helper = Rc::new(RefCell::new(FridaInstrumentationHelper::new( GUM.get().expect("Gum uninitialized"), options, tuple_list!(coverage, asan), - ); + ))); // Run the tests for each function for test in tests { @@ -442,6 +452,7 @@ mod tests { let mut feedback = ConstFeedback::new(true); let asan_obs = AsanErrorsObserver::from_static_asan_errors(); + let frida_helper_observer = FridaHelperObserver::new(Rc::clone(&frida_helper)); // Feedbacks to recognize an input as solution let mut objective = feedback_or_fast!( @@ -466,6 +477,7 @@ mod tests { let mut fuzzer = StdFuzzer::new(StdScheduler::new(), feedback, objective); let observers = tuple_list!( + frida_helper_observer, asan_obs //, ); @@ -497,7 +509,8 @@ mod tests { &mut event_manager, ) .unwrap(), - &mut frida_helper, + // &mut frida_helper, + Rc::clone(&frida_helper), ); let mutator = StdScheduledMutator::new(tuple_list!(BitFlipMutator::new())); @@ -523,7 +536,9 @@ mod tests { } } - frida_helper.deinit(GUM.get().expect("Gum uninitialized")); + frida_helper + .borrow_mut() + .deinit(GUM.get().expect("Gum uninitialized")); } #[test] @@ -545,8 +560,14 @@ mod tests { SimpleStdoutLogger::set_logger().unwrap(); - let out_dir = std::env::var_os("OUT_DIR").unwrap(); - let out_dir = out_dir.to_string_lossy().to_string(); + if let Ok(out_dir) = std::env::var("OUT_DIR") { + println!("OUT_DIR is set to: {out_dir}"); + } else { + println!("OUT_DIR is not set!"); + return; + } + + let out_dir = std::env::var("OUT_DIR").unwrap(); // Check if the harness dynamic library is present, if not - skip the test #[cfg(unix)] let test_harness_name = "test_harness.so"; diff --git a/libafl_frida/test_harness.cpp b/libafl_frida/test_harness.cpp index 0344727942..0bfc07d176 100644 --- a/libafl_frida/test_harness.cpp +++ b/libafl_frida/test_harness.cpp @@ -1,11 +1,15 @@ #include #include #include +#ifndef _MSC_VER + #include +#endif #ifdef _MSC_VER #include #include #include + BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { (void)hModule; @@ -48,7 +52,7 @@ EXTERN int heap_oob_read(const uint8_t *_data, size_t _size) { // OutputDebugStringA("heap_oob_read\n"); int *array = new int[100]; - fprintf(stdout, "%d\n", array[100]); + fprintf(stdout, "heap_oob_read %d\n", array[100]); delete[] array; return 0; } @@ -66,7 +70,7 @@ EXTERN int malloc_heap_uaf_read(const uint8_t *_data, size_t _size) { (void)_size; int *array = static_cast(malloc(100 * sizeof(int))); free(array); - fprintf(stdout, "%d\n", array[5]); + fprintf(stdout, "malloc_heap_uaf_read %d\n", array[5]); return 0; } @@ -83,7 +87,7 @@ 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]); + fprintf(stdout, "malloc_heap_oob_read %d\n", array[100]); free(array); return 0; } @@ -163,6 +167,56 @@ EXTERN int malloc_heap_oob_write_0x17_int_at_0x13(const uint8_t *_data, return 0; } +EXTERN int heap_oob_memcpy_write(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; + + const size_t REAL_SIZE = 10; + const size_t LARGER_SIZE = REAL_SIZE + 1; + + char *dest = new char[REAL_SIZE]; + char *src = new char[LARGER_SIZE]; + memcpy(dest, src, LARGER_SIZE); + + delete[] dest; + delete[] src; + return 0; +} + +EXTERN int heap_oob_memcpy_read(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; + + const size_t REAL_SIZE = 10; + const size_t LARGER_SIZE = REAL_SIZE + 1; + + char *dest = new char[LARGER_SIZE]; + char *src = new char[REAL_SIZE]; + memcpy(dest, src, LARGER_SIZE); + + delete[] dest; + delete[] src; + return 0; +} + +EXTERN int heap_oob_memcpy_write_avx(const uint8_t *_data, size_t _size) { + (void)_data; + (void)_size; + + // Using 127 bytes to make sure to fall on the AVX instruction in the + // optimized implementation + const size_t REAL_SIZE = 127; + const size_t LARGER_SIZE = REAL_SIZE + 1; + + char *dest = new char[LARGER_SIZE]; + char *src = new char[REAL_SIZE]; + memcpy(dest, src, LARGER_SIZE); + + delete[] dest; + delete[] src; + return 0; +} + EXTERN int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // abort(); (void)data;