From fc9caa8314eb7e316bc6d31cbe94dbcaad5f49aa Mon Sep 17 00:00:00 2001 From: Mrmaxmeier <3913977+Mrmaxmeier@users.noreply.github.com> Date: Sat, 29 Jul 2023 13:44:54 +0200 Subject: [PATCH] Fix UB in frida fuzzers (#1385) * WIP: fix ub issue in frida fuzzers * refactor frida helper: remove unused fields * revert frida-gum bump. Current frida-gum doesn't build on iOS :/ * libafl_frida: silence must_use_candidate lint this lint is very noisy, and adding #[must_use] to _all_ (even pure )functions seems very excessive to me * fix clippy --- libafl_frida/src/asan/asan_rt.rs | 2 +- libafl_frida/src/helper.rs | 331 ++++++++++++++++--------------- libafl_frida/src/lib.rs | 3 +- 3 files changed, 170 insertions(+), 166 deletions(-) diff --git a/libafl_frida/src/asan/asan_rt.rs b/libafl_frida/src/asan/asan_rt.rs index 7f5b88f75a..1df06264aa 100644 --- a/libafl_frida/src/asan/asan_rt.rs +++ b/libafl_frida/src/asan/asan_rt.rs @@ -2365,7 +2365,7 @@ impl AsanRuntime { | addr | rip | | Rcx | Rax | | Rsi | Rdx | - Old Rsp - (redsone_size) -> | flags | Rdi | + Old Rsp - (redzone_size) -> | flags | Rdi | | | | Old Rsp -> | | | */ diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index 108f2de611..d7b8532f7e 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -1,11 +1,10 @@ use core::fmt::{self, Debug, Formatter}; - -#[cfg(target_arch = "aarch64")] -use capstone::{ - arch::{self, BuildsCapstone}, - Capstone, +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, }; -#[cfg(all(target_arch = "x86_64", unix))] + +#[cfg(any(target_arch = "aarch64", all(target_arch = "x86_64", unix)))] use capstone::{ arch::{self, BuildsCapstone}, Capstone, @@ -114,14 +113,11 @@ where } /// An helper that feeds `FridaInProcessExecutor` with edge-coverage instrumentation -pub struct FridaInstrumentationHelper<'a, RT> { - #[cfg(unix)] - capstone: Capstone, - ranges: RangeMap, - module_map: ModuleMap, +pub struct FridaInstrumentationHelper<'a, RT: 'a> { options: &'a FuzzerOptions, - transformer: Option>, - runtimes: RT, + transformer: Transformer<'a>, + ranges: Rc>>, + runtimes: Rc>, } impl Debug for FridaInstrumentationHelper<'_, RT> { @@ -166,7 +162,7 @@ where /// Constructor function to create a new [`FridaInstrumentationHelper`], given a `module_name`. #[allow(clippy::too_many_lines)] #[must_use] - pub fn new(gum: &'a Gum, options: &'a FuzzerOptions, runtimes: RT) -> Self { + pub fn new(gum: &'a Gum, options: &'a FuzzerOptions, mut runtimes: RT) -> Self { // workaround frida's frida-gum-allocate-near bug: #[cfg(unix)] unsafe { @@ -202,182 +198,185 @@ where let modules_to_instrument: Vec<&str> = modules_to_instrument.iter().map(AsRef::as_ref).collect(); - let mut helper = Self { - #[cfg(target_arch = "aarch64")] - capstone: Capstone::new() - .arm64() - .mode(arch::arm64::ArchMode::Arm) - .detail(true) - .build() - .expect("Failed to create Capstone object"), - #[cfg(all(target_arch = "x86_64", unix))] - capstone: Capstone::new() - .x86() - .mode(arch::x86::ArchMode::Mode64) - .detail(true) - .build() - .expect("Failed to create Capstone object"), - ranges: RangeMap::new(), - module_map: ModuleMap::new_from_names(gum, &modules_to_instrument), - options, - runtimes, - transformer: None, - }; + let module_map = ModuleMap::new_from_names(gum, &modules_to_instrument); + let mut ranges = RangeMap::new(); if options.cmplog || options.asan || !options.disable_coverage { - for (i, module) in helper.module_map.values().iter().enumerate() { + for (i, module) in module_map.values().iter().enumerate() { let range = module.range(); let start = range.base_address().0 as usize; // log::trace!("start: {:x}", start); - helper - .ranges - .insert(start..(start + range.size()), (i as u16, module.path())); + ranges.insert(start..(start + range.size()), (i as u16, module.path())); } if !options.dont_instrument.is_empty() { for (module_name, offset) in options.dont_instrument.clone() { let module_details = ModuleDetails::with_name(module_name).unwrap(); let lib_start = module_details.range().base_address().0 as usize; // log::info!("removing address: {:#x}", lib_start + offset); - helper - .ranges - .remove((lib_start + offset)..(lib_start + offset + 4)); + ranges.remove((lib_start + offset)..(lib_start + offset + 4)); } } // make sure we aren't in the instrumented list, as it would cause recursions assert!( - !helper.ranges.contains_key(&(Self::new as usize)), + !ranges.contains_key(&(Self::new as usize)), "instrumented libraries must not include the fuzzer" ); - helper - .runtimes - .init_all(gum, &helper.ranges, &modules_to_instrument); + runtimes.init_all(gum, &ranges, &modules_to_instrument); } - let transformer = Transformer::from_callback(gum, |basic_block, output| { - let mut first = true; - for instruction in basic_block { - let instr = instruction.instr(); - #[cfg(unix)] - let instr_size = instr.bytes().len(); - let address = instr.address(); - //log::trace!("block @ {:x} transformed to {:x}", address, output.writer().pc()); + #[cfg(target_arch = "aarch64")] + let capstone = Capstone::new() + .arm64() + .mode(arch::arm64::ArchMode::Arm) + .detail(true) + .build() + .expect("Failed to create Capstone object"); + #[cfg(all(target_arch = "x86_64", unix))] + let capstone = Capstone::new() + .x86() + .mode(arch::x86::ArchMode::Mode64) + .detail(true) + .build() + .expect("Failed to create Capstone object"); - //log::trace!( - //"address: {:x} contains: {:?}", - //address, - //self.ranges().contains_key(&(address as usize)) - //); + // Wrap ranges and runtimes in reference-counted refcells in order to move + // these references both into the struct that we return and the transformer callback + // that we pass to frida-gum. + let ranges = Rc::new(RefCell::new(ranges)); + let runtimes = Rc::new(RefCell::new(runtimes)); - //log::info!("Ranges: {:#?}", self.ranges()); - if helper.ranges().contains_key(&(address as usize)) { - if first { - first = false; - // log::info!( - // "block @ {:x} transformed to {:x}", - // address, - // output.writer().pc() - // ); - if let Some(rt) = helper.runtime_mut::() { - rt.emit_coverage_mapping(address, &output); + let transformer = { + let ranges = Rc::clone(&ranges); + let runtimes = Rc::clone(&runtimes); + Transformer::from_callback(gum, move |basic_block, output| { + let mut first = true; + for instruction in basic_block { + let instr = instruction.instr(); + #[cfg(unix)] + let instr_size = instr.bytes().len(); + let address = instr.address(); + //log::trace!("block @ {:x} transformed to {:x}", address, output.writer().pc()); + + if ranges.borrow().contains_key(&(address as usize)) { + let mut runtimes = (*runtimes).borrow_mut(); + if first { + first = false; + // log::info!( + // "block @ {:x} transformed to {:x}", + // address, + // output.writer().pc() + // ); + if let Some(rt) = runtimes.match_first_type_mut::() { + rt.emit_coverage_mapping(address, &output); + } + + #[cfg(unix)] + if let Some(rt) = runtimes.match_first_type_mut::() { + instruction.put_callout(|context| { + let real_address = rt.real_address_for_stalked(pc(&context)); + //let (range, (id, name)) = helper.ranges.get_key_value(&real_address).unwrap(); + //log::trace!("{}:0x{:016x}", name, real_address - range.start); + rt.drcov_basic_blocks.push(DrCovBasicBlock::new( + real_address, + real_address + instr_size, + )); + }); + } } #[cfg(unix)] - if let Some(rt) = helper.runtime_mut::() { - instruction.put_callout(|context| { - let real_address = rt.real_address_for_stalked(pc(&context)); - //let (range, (id, name)) = helper.ranges.get_key_value(&real_address).unwrap(); - //log::trace!("{}:0x{:016x}", name, real_address - range.start); - rt.drcov_basic_blocks.push(DrCovBasicBlock::new( - real_address, - real_address + instr_size, - )); - }); - } - } - - #[cfg(unix)] - let res = if let Some(_rt) = helper.runtime::() { - AsanRuntime::asan_is_interesting_instruction( - &helper.capstone, - address, - instr, - ) - } else { - None - }; - - #[cfg(all(target_arch = "x86_64", unix))] - if let Some((segment, width, basereg, indexreg, scale, disp)) = res { - if let Some(rt) = helper.runtime_mut::() { - rt.emit_shadow_check( - address, &output, segment, width, basereg, indexreg, scale, disp, - ); - } - } - - #[cfg(target_arch = "aarch64")] - if let Some((basereg, indexreg, displacement, width, shift, extender)) = res { - if let Some(rt) = helper.runtime_mut::() { - rt.emit_shadow_check( - address, - &output, - basereg, - indexreg, - displacement, - width, - shift, - extender, - ); - } - } - - #[cfg(all(feature = "cmplog", target_arch = "aarch64"))] - if let Some(rt) = helper.runtime::() { - if let Some((op1, op2, special_case)) = - CmpLogRuntime::cmplog_is_interesting_instruction( - &helper.capstone, - address, - instr, - ) + let res = if let Some(_rt) = runtimes.match_first_type_mut::() { - //emit code that saves the relevant data in runtime(passes it to x0, x1) - rt.emit_comparison_handling(address, &output, &op1, &op2, special_case); + AsanRuntime::asan_is_interesting_instruction(&capstone, address, instr) + } else { + None + }; + + #[cfg(all(target_arch = "x86_64", unix))] + if let Some((segment, width, basereg, indexreg, scale, disp)) = res { + if let Some(rt) = runtimes.match_first_type_mut::() { + rt.emit_shadow_check( + address, &output, segment, width, basereg, indexreg, scale, + disp, + ); + } + } + + #[cfg(target_arch = "aarch64")] + if let Some((basereg, indexreg, displacement, width, shift, extender)) = res + { + if let Some(rt) = runtimes.match_first_type_mut::() { + rt.emit_shadow_check( + address, + &output, + basereg, + indexreg, + displacement, + width, + shift, + extender, + ); + } + } + + #[cfg(all(feature = "cmplog", target_arch = "aarch64"))] + if let Some(rt) = runtimes.match_first_type_mut::() { + if let Some((op1, op2, special_case)) = + CmpLogRuntime::cmplog_is_interesting_instruction( + &helper.capstone, + address, + instr, + ) + { + //emit code that saves the relevant data in runtime(passes it to x0, x1) + rt.emit_comparison_handling( + address, + &output, + &op1, + &op2, + special_case, + ); + } + } + + #[cfg(unix)] + if let Some(rt) = runtimes.match_first_type_mut::() { + rt.add_stalked_address( + output.writer().pc() as usize - instr_size, + address as usize, + ); + } + + #[cfg(unix)] + if let Some(rt) = runtimes.match_first_type_mut::() { + rt.add_stalked_address( + output.writer().pc() as usize - instr_size, + address as usize, + ); } } - - #[cfg(unix)] - if let Some(rt) = helper.runtime_mut::() { - rt.add_stalked_address( - output.writer().pc() as usize - instr_size, - address as usize, - ); - } - - #[cfg(unix)] - if let Some(rt) = helper.runtime_mut::() { - rt.add_stalked_address( - output.writer().pc() as usize - instr_size, - address as usize, - ); - } + instruction.keep(); } - instruction.keep(); - } - }); + }) + }; - helper.transformer = Some(transformer); - - helper + Self { + options, + transformer, + ranges, + runtimes, + } } + /* /// Return the runtime pub fn runtime(&self) -> Option<&R> where R: FridaRuntime, { - self.runtimes.match_first_type::() + self.runtimes.borrow().match_first_type::() } /// Return the mutable runtime @@ -385,13 +384,13 @@ where where R: FridaRuntime, { - self.runtimes.match_first_type_mut::() + (*self.runtimes).borrow_mut().match_first_type_mut::() } + */ /// Returns ref to the Transformer - pub fn transformer(&mut self) -> &Transformer<'a> { - // the Transformer is always initialized on `new`. We can safely unwrap. - self.transformer.as_ref().unwrap() + pub fn transformer(&self) -> &Transformer<'a> { + &self.transformer } /// Initialize all @@ -401,17 +400,19 @@ where ranges: &RangeMap, modules_to_instrument: &'a [&str], ) { - self.runtimes.init_all(gum, ranges, modules_to_instrument); + (*self.runtimes) + .borrow_mut() + .init_all(gum, ranges, modules_to_instrument); } /// Method called before execution pub fn pre_exec(&mut self, input: &I) -> Result<(), Error> { - self.runtimes.pre_exec_all(input) + (*self.runtimes).borrow_mut().pre_exec_all(input) } /// Method called after execution pub fn post_exec(&mut self, input: &I) -> Result<(), Error> { - self.runtimes.post_exec_all(input) + (*self.runtimes).borrow_mut().post_exec_all(input) } /// If stalker is enabled @@ -421,18 +422,20 @@ where /// Pointer to coverage map pub fn map_mut_ptr(&mut self) -> Option<*mut u8> { - self.runtime_mut::() + (*self.runtimes) + .borrow_mut() + .match_first_type_mut::() .map(CoverageRuntime::map_mut_ptr) } /// Ranges - pub fn ranges(&self) -> &RangeMap { - &self.ranges + pub fn ranges(&self) -> Ref> { + self.ranges.borrow() } /// Mutable ranges - pub fn ranges_mut(&mut self) -> &mut RangeMap { - &mut self.ranges + pub fn ranges_mut(&mut self) -> RefMut> { + (*self.ranges).borrow_mut() } /// Return the ref to options diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index 46b277a5c6..9e3a3d0c24 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -19,7 +19,8 @@ Additional documentation is available in [the `LibAFL` book](https://aflplus.plu clippy::missing_docs_in_private_items, clippy::module_name_repetitions, clippy::unreadable_literal, - clippy::ptr_cast_constness + clippy::ptr_cast_constness, + clippy::must_use_candidate )] #![cfg_attr(not(test), warn( missing_debug_implementations,