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
This commit is contained in:
Mrmaxmeier 2023-07-29 13:44:54 +02:00 committed by GitHub
parent 37bfead4e5
commit fc9caa8314
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 166 deletions

View File

@ -2365,7 +2365,7 @@ impl AsanRuntime {
| addr | rip | | addr | rip |
| Rcx | Rax | | Rcx | Rax |
| Rsi | Rdx | | Rsi | Rdx |
Old Rsp - (redsone_size) -> | flags | Rdi | Old Rsp - (redzone_size) -> | flags | Rdi |
| | | | | |
Old Rsp -> | | | Old Rsp -> | | |
*/ */

View File

@ -1,11 +1,10 @@
use core::fmt::{self, Debug, Formatter}; use core::fmt::{self, Debug, Formatter};
use std::{
#[cfg(target_arch = "aarch64")] cell::{Ref, RefCell, RefMut},
use capstone::{ rc::Rc,
arch::{self, BuildsCapstone},
Capstone,
}; };
#[cfg(all(target_arch = "x86_64", unix))]
#[cfg(any(target_arch = "aarch64", all(target_arch = "x86_64", unix)))]
use capstone::{ use capstone::{
arch::{self, BuildsCapstone}, arch::{self, BuildsCapstone},
Capstone, Capstone,
@ -114,14 +113,11 @@ where
} }
/// An helper that feeds `FridaInProcessExecutor` with edge-coverage instrumentation /// An helper that feeds `FridaInProcessExecutor` with edge-coverage instrumentation
pub struct FridaInstrumentationHelper<'a, RT> { pub struct FridaInstrumentationHelper<'a, RT: 'a> {
#[cfg(unix)]
capstone: Capstone,
ranges: RangeMap<usize, (u16, String)>,
module_map: ModuleMap,
options: &'a FuzzerOptions, options: &'a FuzzerOptions,
transformer: Option<Transformer<'a>>, transformer: Transformer<'a>,
runtimes: RT, ranges: Rc<RefCell<RangeMap<usize, (u16, String)>>>,
runtimes: Rc<RefCell<RT>>,
} }
impl<RT> Debug for FridaInstrumentationHelper<'_, RT> { impl<RT> Debug for FridaInstrumentationHelper<'_, RT> {
@ -166,7 +162,7 @@ where
/// Constructor function to create a new [`FridaInstrumentationHelper`], given a `module_name`. /// Constructor function to create a new [`FridaInstrumentationHelper`], given a `module_name`.
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
#[must_use] #[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: // workaround frida's frida-gum-allocate-near bug:
#[cfg(unix)] #[cfg(unix)]
unsafe { unsafe {
@ -202,182 +198,185 @@ where
let modules_to_instrument: Vec<&str> = let modules_to_instrument: Vec<&str> =
modules_to_instrument.iter().map(AsRef::as_ref).collect(); modules_to_instrument.iter().map(AsRef::as_ref).collect();
let mut helper = Self { let module_map = ModuleMap::new_from_names(gum, &modules_to_instrument);
#[cfg(target_arch = "aarch64")] let mut ranges = RangeMap::new();
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,
};
if options.cmplog || options.asan || !options.disable_coverage { 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 range = module.range();
let start = range.base_address().0 as usize; let start = range.base_address().0 as usize;
// log::trace!("start: {:x}", start); // 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() { if !options.dont_instrument.is_empty() {
for (module_name, offset) in options.dont_instrument.clone() { for (module_name, offset) in options.dont_instrument.clone() {
let module_details = ModuleDetails::with_name(module_name).unwrap(); let module_details = ModuleDetails::with_name(module_name).unwrap();
let lib_start = module_details.range().base_address().0 as usize; let lib_start = module_details.range().base_address().0 as usize;
// log::info!("removing address: {:#x}", lib_start + offset); // 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 // make sure we aren't in the instrumented list, as it would cause recursions
assert!( assert!(
!helper.ranges.contains_key(&(Self::new as usize)), !ranges.contains_key(&(Self::new as usize)),
"instrumented libraries must not include the fuzzer" "instrumented libraries must not include the fuzzer"
); );
helper runtimes.init_all(gum, &ranges, &modules_to_instrument);
.runtimes
.init_all(gum, &helper.ranges, &modules_to_instrument);
} }
let transformer = Transformer::from_callback(gum, |basic_block, output| { #[cfg(target_arch = "aarch64")]
let mut first = true; let capstone = Capstone::new()
for instruction in basic_block { .arm64()
let instr = instruction.instr(); .mode(arch::arm64::ArchMode::Arm)
#[cfg(unix)] .detail(true)
let instr_size = instr.bytes().len(); .build()
let address = instr.address(); .expect("Failed to create Capstone object");
//log::trace!("block @ {:x} transformed to {:x}", address, output.writer().pc()); #[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!( // Wrap ranges and runtimes in reference-counted refcells in order to move
//"address: {:x} contains: {:?}", // these references both into the struct that we return and the transformer callback
//address, // that we pass to frida-gum.
//self.ranges().contains_key(&(address as usize)) let ranges = Rc::new(RefCell::new(ranges));
//); let runtimes = Rc::new(RefCell::new(runtimes));
//log::info!("Ranges: {:#?}", self.ranges()); let transformer = {
if helper.ranges().contains_key(&(address as usize)) { let ranges = Rc::clone(&ranges);
if first { let runtimes = Rc::clone(&runtimes);
first = false; Transformer::from_callback(gum, move |basic_block, output| {
// log::info!( let mut first = true;
// "block @ {:x} transformed to {:x}", for instruction in basic_block {
// address, let instr = instruction.instr();
// output.writer().pc() #[cfg(unix)]
// ); let instr_size = instr.bytes().len();
if let Some(rt) = helper.runtime_mut::<CoverageRuntime>() { let address = instr.address();
rt.emit_coverage_mapping(address, &output); //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::<CoverageRuntime>() {
rt.emit_coverage_mapping(address, &output);
}
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
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)] #[cfg(unix)]
if let Some(rt) = helper.runtime_mut::<DrCovRuntime>() { let res = if let Some(_rt) = runtimes.match_first_type_mut::<AsanRuntime>()
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>() {
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::<AsanRuntime>() {
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::<AsanRuntime>() {
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::<CmpLogRuntime>() {
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) AsanRuntime::asan_is_interesting_instruction(&capstone, address, instr)
rt.emit_comparison_handling(address, &output, &op1, &op2, special_case); } 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::<AsanRuntime>() {
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::<AsanRuntime>() {
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::<CmpLogRuntime>() {
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::<AsanRuntime>() {
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::<DrCovRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
} }
} }
instruction.keep();
#[cfg(unix)]
if let Some(rt) = helper.runtime_mut::<AsanRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
#[cfg(unix)]
if let Some(rt) = helper.runtime_mut::<DrCovRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
} }
instruction.keep(); })
} };
});
helper.transformer = Some(transformer); Self {
options,
helper transformer,
ranges,
runtimes,
}
} }
/*
/// Return the runtime /// Return the runtime
pub fn runtime<R>(&self) -> Option<&R> pub fn runtime<R>(&self) -> Option<&R>
where where
R: FridaRuntime, R: FridaRuntime,
{ {
self.runtimes.match_first_type::<R>() self.runtimes.borrow().match_first_type::<R>()
} }
/// Return the mutable runtime /// Return the mutable runtime
@ -385,13 +384,13 @@ where
where where
R: FridaRuntime, R: FridaRuntime,
{ {
self.runtimes.match_first_type_mut::<R>() (*self.runtimes).borrow_mut().match_first_type_mut::<R>()
} }
*/
/// Returns ref to the Transformer /// Returns ref to the Transformer
pub fn transformer(&mut self) -> &Transformer<'a> { pub fn transformer(&self) -> &Transformer<'a> {
// the Transformer is always initialized on `new`. We can safely unwrap. &self.transformer
self.transformer.as_ref().unwrap()
} }
/// Initialize all /// Initialize all
@ -401,17 +400,19 @@ where
ranges: &RangeMap<usize, (u16, String)>, ranges: &RangeMap<usize, (u16, String)>,
modules_to_instrument: &'a [&str], 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 /// Method called before execution
pub fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I) -> Result<(), Error> { pub fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I) -> Result<(), Error> {
self.runtimes.pre_exec_all(input) (*self.runtimes).borrow_mut().pre_exec_all(input)
} }
/// Method called after execution /// Method called after execution
pub fn post_exec<I: Input + HasTargetBytes>(&mut self, input: &I) -> Result<(), Error> { pub fn post_exec<I: Input + HasTargetBytes>(&mut self, input: &I) -> Result<(), Error> {
self.runtimes.post_exec_all(input) (*self.runtimes).borrow_mut().post_exec_all(input)
} }
/// If stalker is enabled /// If stalker is enabled
@ -421,18 +422,20 @@ where
/// Pointer to coverage map /// Pointer to coverage map
pub fn map_mut_ptr(&mut self) -> Option<*mut u8> { pub fn map_mut_ptr(&mut self) -> Option<*mut u8> {
self.runtime_mut::<CoverageRuntime>() (*self.runtimes)
.borrow_mut()
.match_first_type_mut::<CoverageRuntime>()
.map(CoverageRuntime::map_mut_ptr) .map(CoverageRuntime::map_mut_ptr)
} }
/// Ranges /// Ranges
pub fn ranges(&self) -> &RangeMap<usize, (u16, String)> { pub fn ranges(&self) -> Ref<RangeMap<usize, (u16, String)>> {
&self.ranges self.ranges.borrow()
} }
/// Mutable ranges /// Mutable ranges
pub fn ranges_mut(&mut self) -> &mut RangeMap<usize, (u16, String)> { pub fn ranges_mut(&mut self) -> RefMut<RangeMap<usize, (u16, String)>> {
&mut self.ranges (*self.ranges).borrow_mut()
} }
/// Return the ref to options /// Return the ref to options

View File

@ -19,7 +19,8 @@ Additional documentation is available in [the `LibAFL` book](https://aflplus.plu
clippy::missing_docs_in_private_items, clippy::missing_docs_in_private_items,
clippy::module_name_repetitions, clippy::module_name_repetitions,
clippy::unreadable_literal, clippy::unreadable_literal,
clippy::ptr_cast_constness clippy::ptr_cast_constness,
clippy::must_use_candidate
)] )]
#![cfg_attr(not(test), warn( #![cfg_attr(not(test), warn(
missing_debug_implementations, missing_debug_implementations,