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 |
| Rcx | Rax |
| Rsi | Rdx |
Old Rsp - (redsone_size) -> | flags | Rdi |
Old Rsp - (redzone_size) -> | flags | Rdi |
| | |
Old Rsp -> | | |
*/

View File

@ -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<usize, (u16, String)>,
module_map: ModuleMap,
pub struct FridaInstrumentationHelper<'a, RT: 'a> {
options: &'a FuzzerOptions,
transformer: Option<Transformer<'a>>,
runtimes: RT,
transformer: Transformer<'a>,
ranges: Rc<RefCell<RangeMap<usize, (u16, String)>>>,
runtimes: Rc<RefCell<RT>>,
}
impl<RT> 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::<CoverageRuntime>() {
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::<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)]
if let Some(rt) = helper.runtime_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)]
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,
)
let res = if let Some(_rt) = runtimes.match_first_type_mut::<AsanRuntime>()
{
//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::<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,
);
}
}
#[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();
}
instruction.keep();
}
});
})
};
helper.transformer = Some(transformer);
helper
Self {
options,
transformer,
ranges,
runtimes,
}
}
/*
/// Return the runtime
pub fn runtime<R>(&self) -> Option<&R>
where
R: FridaRuntime,
{
self.runtimes.match_first_type::<R>()
self.runtimes.borrow().match_first_type::<R>()
}
/// Return the mutable runtime
@ -385,13 +384,13 @@ where
where
R: FridaRuntime,
{
self.runtimes.match_first_type_mut::<R>()
(*self.runtimes).borrow_mut().match_first_type_mut::<R>()
}
*/
/// 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<usize, (u16, String)>,
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<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
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
@ -421,18 +422,20 @@ where
/// Pointer to coverage map
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)
}
/// Ranges
pub fn ranges(&self) -> &RangeMap<usize, (u16, String)> {
&self.ranges
pub fn ranges(&self) -> Ref<RangeMap<usize, (u16, String)>> {
self.ranges.borrow()
}
/// Mutable ranges
pub fn ranges_mut(&mut self) -> &mut RangeMap<usize, (u16, String)> {
&mut self.ranges
pub fn ranges_mut(&mut self) -> RefMut<RangeMap<usize, (u16, String)>> {
(*self.ranges).borrow_mut()
}
/// 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::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,