WIP: Implement ASAN instrumentation using frida (#45)
* frida_asan: Implemented initial asan runtime library * frida_asan: Switch to hashbrown * Implemented GOT-based hooking to isolate the hooking of the memory functions. Implemented initial ASAN instrumentation * WIP: Shadowing all used memory. Currently tracking pages using a BTreeSet. Slow AF! * Add SigTrap to unix_signals and inprocess * Working frida-asan, almost no speed degradation. Currently the shadow check is reversed, so it checks only that the shadow is not 0. We need to implement sub-8-byte checking. * Format * Cleanup and formatting * Sub-qword and 16-byte checks implemented; Fixed unaligned access to QWORD * Pass the ucontext_t to signal handlers. Initial regdump on crash * Fix typo * Make the context argument a mut ref * Add missing files; Implement initial reporting * Refactor out gothook; Move safety checkers to dynasm * Get rid of const assembly blobs no longer needed * Move to a handler function instead of using SIGTRAP. This bloats the transformed code, but doesn't seem to have a major impact on performance. Also, implemented pretty backtraces and assembly output. * Formatting * Get rid of all the pinning crap I wasted my day on, We don't need it * windows fixes * ashmem * ashmem_service: server side ready * ashmem_service: client side ready. Ready for integration * ashmem_service: changes to UnixShMem to make it 'threadable' * ashmem_service: format * ashmem_service: Undo changes to UnixShMem, make the thread own the AshmemService instead; Fix protocol bug * ashmem_service: working ashmem service. Fix merge issues * use the newly released capston e 0.8.0; Fix a nasty bug where the afl_area an pc_pointer were reversed. Changed Vectors to Boxed [u8] * Implement type detection for reporting; Implement double-free/unallocated free checking * fmt * Cleanup code a little * frida-asan: This is an omnibus commit. Should probably have been a bunch of small commits, but I don't have the time/patience. - Implemented DrCov support in order to debug a failing harness. This is actually generic and should be moved out of libafl_frida. - Implemented LIBAFL_FRIDA_OPTIONS env var to pass options to the frida helper, to dynamically enable/disable asan and drcov. - Implemented memory reuse - after each test case the used pages are recycled and can be reused in the next test case. - Implemented and tested vectorized instruction instrumentation. - Implemented not instrumenting atomic load/store instructions. The cost of trying to emulate their behaviour is too high at the moment. - Implemented probing of shadow bit to determine the best match for the current system. - Implemented shadow memory pre-mapping where it is available. We probe for this too. - Implemented ability to specify a list of modules to instrument on the command line. This allows fine-grained control of which modules are instrumented for coverage/asan/drcov. - Implemented unpoisoning of the Input target_bytes in a pre_exec hook. - Added support for zero-sized allocations. We return 0x10 bytes at the moment. - Added all known operator new/delete functions to hooks. - Added workaround for frida_gum_allocate_near bug. - Cleaned up reporting, added reporting for different error types. * frida-asan: Implement leak detection * Fix merge issues * Rebased on dev to get llmp/shmem changes; Clippy fixes * Add FridaOptions struct * Add the Custom ExitKind; Get rid of Clone/PartialEq on ExitKind * Make it possible to recover from an ASAN error * Add SIGTRAP to crashing signals * Add back (conditional) crashing on Asan errors. * Fix too-large immediates in add instruction * Implement RcShMemProvider, finally fix the EOP bug * Clear ASAN_ERRORS before each test * Fix warnings; Fix review issues * Cleanup prints * Add timeout to Frida mode * Make allocation-/free-site backtraces optional * CPU Context and backtrace (on android/aarch64 atm) on crash * Make stalker conditional * Add metadata to solution, and write metadata files * Add addresses to backtrace; Add reporting of ASAN stack errors; Fix ASAN reporting bugs * Remove meaningless backtrace on crash * Fix the x0, x1 load in report * use upstream color-backtrace * use __builtin_thread_pointer instead of custom asm * Don't unwrap ASAN_ERRORS if it isn't some * Fix bug where we weren't clearing the drcov basicblocks after each run * Fix bug where we were dropping an ashmem too soon * Fix OwnedPtr instead of CPtr * Fix gettls for all archs * cfg guards for target arch, disabling Frida-ASAN/-DrCov if not on aarch64 * Cargo fmt * Only panic in options when asan/drcov are turned on; Merge fixes * gothook only supported on unix * Fix gettls on msvc * Another attempt to fix MSVC gettls * Fix backtrace use * nostd fixes; warning fixes * formatting * Migrate FridaEdgeCoverageHelper into libafl_frida, and rename to FridaInstrumentationHelper * Clean up uses * Move DrCovWriter to libafl_targets * Refactor DrCovWriter to get a vec of DrCovBasicBlocks; formatting * Update to newer backtrace which supports android with gimli * windows fixes Co-authored-by: Dominik Maier <domenukk@gmail.com> Co-authored-by: andreafioraldi <andreafioraldi@gmail.com>
This commit is contained in:
parent
b8b01baf59
commit
5c856cccc8
@ -10,6 +10,7 @@ members = [
|
||||
"libafl_derive",
|
||||
"libafl_cc",
|
||||
"libafl_targets",
|
||||
"libafl_frida",
|
||||
]
|
||||
exclude = [
|
||||
"fuzzers/libfuzzer_libpng",
|
||||
|
@ -22,9 +22,14 @@ num_cpus = "1.0"
|
||||
which = "4.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
frida-gum = { version = "0.3.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] }
|
||||
frida-gum-sys = { version = "0.2.2", optional = true, features = ["auto-download", "event-sink", "invocation-listener"] }
|
||||
libafl = { path = "../../libafl/", features = [ "std" ] } #, "llmp_small_maps", "llmp_debug"]}
|
||||
capstone = "0.8.0"
|
||||
frida-gum = { version = "0.4", optional = true, features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
frida-gum-sys = { version = "0.2.4", optional = true, features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
libafl_frida = { path = "../../libafl_frida", version = "0.1.0" }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
libloading = "0.7.0"
|
||||
num-traits = "0.2.14"
|
||||
rangemap = "0.1.10"
|
||||
seahash = "4.1.0"
|
||||
|
@ -119,6 +119,8 @@ fn main() {
|
||||
//.arg("HAS_DUMMY_CRASH=1")
|
||||
.arg("-fPIC")
|
||||
.arg("-shared")
|
||||
.arg("-O3")
|
||||
//.arg("-fomit-frame-pointer")
|
||||
.arg(if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" {
|
||||
"-static-libstdc++"
|
||||
} else {
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
@ -83,6 +84,25 @@ extern "C" int afl_libfuzzer_init() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char * allocation = NULL;
|
||||
__attribute__((noinline))
|
||||
void func3( char * alloc) {
|
||||
printf("func3\n");
|
||||
if (random() % 5 == 0) {
|
||||
alloc[0xff] = 0xde;
|
||||
}
|
||||
}
|
||||
__attribute__((noinline))
|
||||
void func2() {
|
||||
allocation = (char*)malloc(0xff);
|
||||
printf("func2\n");
|
||||
func3(allocation);
|
||||
}
|
||||
__attribute__((noinline))
|
||||
void func1() {
|
||||
printf("func1\n");
|
||||
func2();
|
||||
}
|
||||
// Entry point for LibFuzzer.
|
||||
// Roughly follows the libpng book example:
|
||||
// http://www.libpng.org/pub/png/book/chapter13.html
|
||||
@ -91,6 +111,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
func1();
|
||||
|
||||
std::vector<unsigned char> v(data, data + size);
|
||||
if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) {
|
||||
// not a PNG.
|
||||
|
@ -4,12 +4,14 @@
|
||||
use libafl::{
|
||||
bolts::tuples::{tuple_list, Named},
|
||||
corpus::{
|
||||
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
|
||||
QueueCorpusScheduler,
|
||||
ondisk::OnDiskMetadataFormat, Corpus, InMemoryCorpus,
|
||||
IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler,
|
||||
},
|
||||
events::{setup_restarting_mgr_std, EventManager},
|
||||
executors::{inprocess::InProcessExecutor, Executor, ExitKind, HasObservers},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback},
|
||||
executors::{
|
||||
inprocess::InProcessExecutor, timeout::TimeoutExecutor, Executor, ExitKind, HasObservers,
|
||||
},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
inputs::{HasTargetBytes, Input},
|
||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||
@ -22,229 +24,38 @@ use libafl::{
|
||||
Error,
|
||||
};
|
||||
|
||||
use core::cell::RefCell;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use frida_gum::instruction_writer::X86Register;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use frida_gum::instruction_writer::{Aarch64Register, IndexMode};
|
||||
use frida_gum::{
|
||||
instruction_writer::InstructionWriter,
|
||||
stalker::{NoneEventSink, Stalker, Transformer},
|
||||
};
|
||||
use frida_gum::{Gum, MemoryRange, Module, NativePointer, PageProtection};
|
||||
use std::{env, ffi::c_void, path::PathBuf};
|
||||
|
||||
/// An helper that feeds FridaInProcessExecutor with user-supplied instrumentation
|
||||
pub trait FridaHelper<'a> {
|
||||
fn transformer(&self) -> &Transformer<'a>;
|
||||
}
|
||||
|
||||
const MAP_SIZE: usize = 64 * 1024;
|
||||
|
||||
/// An helper that feeds FridaInProcessExecutor with edge-coverage instrumentation
|
||||
struct FridaEdgeCoverageHelper<'a> {
|
||||
map: [u8; MAP_SIZE],
|
||||
previous_pc: RefCell<u64>,
|
||||
base_address: u64,
|
||||
size: usize,
|
||||
current_log_impl: u64,
|
||||
/// Transformer that has to be passed to FridaInProcessExecutor
|
||||
transformer: Option<Transformer<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FridaHelper<'a> for FridaEdgeCoverageHelper<'a> {
|
||||
fn transformer(&self) -> &Transformer<'a> {
|
||||
self.transformer.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to get the size of a module's CODE section from frida
|
||||
pub fn get_module_size(module_name: &str) -> usize {
|
||||
let mut code_size = 0;
|
||||
let code_size_ref = &mut code_size;
|
||||
Module::enumerate_ranges(module_name, PageProtection::ReadExecute, move |details| {
|
||||
*code_size_ref = details.memory_range().size() as usize;
|
||||
true
|
||||
});
|
||||
|
||||
code_size
|
||||
}
|
||||
|
||||
/// A minimal maybe_log implementation. We insert this into the transformed instruction stream
|
||||
/// every time we need a copy that is within a direct branch of the start of the transformed basic
|
||||
/// block.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const MAYBE_LOG_CODE: [u8; 47] = [
|
||||
0x9c, /* pushfq */
|
||||
0x50, /* push rax */
|
||||
0x51, /* push rcx */
|
||||
0x52, /* push rdx */
|
||||
0x48, 0x8d, 0x05, 0x24, 0x00, 0x00, 0x00, /* lea rax, sym._afl_area_ptr_ptr */
|
||||
0x48, 0x8b, 0x00, /* mov rax, qword [rax] */
|
||||
0x48, 0x8d, 0x0d, 0x22, 0x00, 0x00, 0x00, /* lea rcx, sym.previous_pc */
|
||||
0x48, 0x8b, 0x11, /* mov rdx, qword [rcx] */
|
||||
0x48, 0x8b, 0x12, /* mov rdx, qword [rdx] */
|
||||
0x48, 0x31, 0xfa, /* xor rdx, rdi */
|
||||
0xfe, 0x04, 0x10, /* inc byte [rax + rdx] */
|
||||
0x48, 0xd1, 0xef, /* shr rdi, 1 */
|
||||
0x48, 0x8b, 0x01, /* mov rax, qword [rcx] */
|
||||
0x48, 0x89, 0x38, /* mov qword [rax], rdi */
|
||||
0x5a, /* pop rdx */
|
||||
0x59, /* pop rcx */
|
||||
0x58, /* pop rax */
|
||||
0x9d, /* popfq */
|
||||
0xc3, /* ret */
|
||||
|
||||
/* Read-only data goes here: */
|
||||
/* uint8_t* afl_area_ptr */
|
||||
/* uint64_t* afl_prev_loc_ptr */
|
||||
];
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const MAYBE_LOG_CODE: [u8; 56] = [
|
||||
// __afl_area_ptr[current_pc ^ previous_pc]++;
|
||||
// previous_pc = current_pc >> 1;
|
||||
0xE1, 0x0B, 0xBF, 0xA9, // stp x1, x2, [sp, -0x10]!
|
||||
0xE3, 0x13, 0xBF, 0xA9, // stp x3, x4, [sp, -0x10]!
|
||||
// x0 = current_pc
|
||||
0x81, 0x01, 0x00, 0x58, // ldr x1, #0x30, =__afl_area_ptr
|
||||
0xa2, 0x01, 0x00, 0x58, // ldr x2, #0x38, =&previous_pc
|
||||
0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] (=previous_pc)
|
||||
// __afl_area_ptr[current_pc ^ previous_pc]++;
|
||||
0x84, 0x00, 0x00, 0xca, // eor x4, x4, x0
|
||||
0x23, 0x68, 0x64, 0xf8, // ldr x3, [x1, x4]
|
||||
0x63, 0x04, 0x00, 0x91, // add x3, x3, #1
|
||||
0x23, 0x68, 0x24, 0xf8, // str x3, [x1, x2]
|
||||
// previous_pc = current_pc >> 1;
|
||||
0xe0, 0x07, 0x40, 0x8b, // add x0, xzr, x0, LSR #1
|
||||
0x40, 0x00, 0x00, 0xf9, // str x0, [x2]
|
||||
0xE3, 0x13, 0xc1, 0xA8, // ldp x3, x4, [sp], #0x10
|
||||
0xE1, 0x0B, 0xc1, 0xA8, // ldp x1, x2, [sp], #0x10
|
||||
0xC0, 0x03, 0x5F, 0xD6, // ret
|
||||
|
||||
// &afl_area_ptr
|
||||
// &afl_prev_loc_ptr
|
||||
];
|
||||
|
||||
/// The implementation of the FridaEdgeCoverageHelper
|
||||
impl<'a> FridaEdgeCoverageHelper<'a> {
|
||||
/// Constructor function to create a new FridaEdgeCoverageHelper, given a module_name.
|
||||
pub fn new(gum: &'a Gum, module_name: &str) -> Self {
|
||||
let mut helper = Self {
|
||||
map: [0u8; MAP_SIZE],
|
||||
previous_pc: RefCell::new(0x0),
|
||||
base_address: Module::find_base_address(module_name).0 as u64,
|
||||
size: get_module_size(module_name),
|
||||
current_log_impl: 0,
|
||||
transformer: None,
|
||||
stalker::{NoneEventSink, Stalker},
|
||||
Gum, NativePointer,
|
||||
};
|
||||
|
||||
let transformer = Transformer::from_callback(gum, |basic_block, _output| {
|
||||
let mut first = true;
|
||||
for instruction in basic_block {
|
||||
if first {
|
||||
first = false;
|
||||
let address = unsafe { (*instruction.instr()).address };
|
||||
if address >= helper.base_address
|
||||
&& address <= helper.base_address + helper.size as u64
|
||||
{
|
||||
let writer = _output.writer();
|
||||
if helper.current_log_impl == 0
|
||||
|| !writer.can_branch_directly_to(helper.current_log_impl)
|
||||
|| !writer.can_branch_directly_between(
|
||||
writer.pc() + 128,
|
||||
helper.current_log_impl,
|
||||
)
|
||||
{
|
||||
let after_log_impl = writer.code_offset() + 1;
|
||||
use std::{env, ffi::c_void, marker::PhantomData, path::PathBuf, time::Duration};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
writer.put_jmp_near_label(after_log_impl);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
writer.put_b_label(after_log_impl);
|
||||
use libafl_frida::{
|
||||
asan_rt::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS},
|
||||
helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE},
|
||||
FridaOptions,
|
||||
};
|
||||
|
||||
helper.current_log_impl = writer.pc();
|
||||
writer.put_bytes(&MAYBE_LOG_CODE);
|
||||
let prev_loc_pointer = helper.previous_pc.as_ptr() as *mut _ as usize;
|
||||
let map_pointer = helper.map.as_ptr() as usize;
|
||||
|
||||
writer.put_bytes(&prev_loc_pointer.to_ne_bytes());
|
||||
writer.put_bytes(&map_pointer.to_ne_bytes());
|
||||
|
||||
writer.put_label(after_log_impl);
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
println!("here");
|
||||
writer.put_lea_reg_reg_offset(
|
||||
X86Register::Rsp,
|
||||
X86Register::Rsp,
|
||||
-(frida_gum_sys::GUM_RED_ZONE_SIZE as i32),
|
||||
);
|
||||
writer.put_push_reg(X86Register::Rdi);
|
||||
writer.put_mov_reg_address(
|
||||
X86Register::Rdi,
|
||||
((address >> 4) ^ (address << 8)) & (MAP_SIZE - 1) as u64,
|
||||
);
|
||||
writer.put_call_address(helper.current_log_impl);
|
||||
writer.put_pop_reg(X86Register::Rdi);
|
||||
writer.put_lea_reg_reg_offset(
|
||||
X86Register::Rsp,
|
||||
X86Register::Rsp,
|
||||
frida_gum_sys::GUM_RED_ZONE_SIZE as i32,
|
||||
);
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
writer.put_stp_reg_reg_reg_offset(
|
||||
Aarch64Register::Lr,
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::Sp,
|
||||
-(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64,
|
||||
IndexMode::PreAdjust,
|
||||
);
|
||||
writer.put_ldr_reg_u64(
|
||||
Aarch64Register::X0,
|
||||
((address >> 4) ^ (address << 8)) & (MAP_SIZE - 1) as u64,
|
||||
);
|
||||
writer.put_bl_imm(helper.current_log_impl);
|
||||
writer.put_ldp_reg_reg_reg_offset(
|
||||
Aarch64Register::Lr,
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::Sp,
|
||||
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64,
|
||||
IndexMode::PostAdjust,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
instruction.keep()
|
||||
}
|
||||
});
|
||||
|
||||
helper.transformer = Some(transformer);
|
||||
helper
|
||||
}
|
||||
}
|
||||
|
||||
struct FridaInProcessExecutor<'a, FH, H, I, OT>
|
||||
struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'a>,
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
base: InProcessExecutor<'a, H, I, OT>,
|
||||
base: TimeoutExecutor<InProcessExecutor<'a, H, I, OT>, I, OT>,
|
||||
/// Frida's dynamic rewriting engine
|
||||
stalker: Stalker<'a>,
|
||||
/// User provided callback for instrumentation
|
||||
helper: &'a FH,
|
||||
helper: &'c mut FH,
|
||||
followed: bool,
|
||||
_phantom: PhantomData<&'b u8>,
|
||||
}
|
||||
|
||||
impl<'a, FH, H, I, OT> Executor<I> for FridaInProcessExecutor<'a, FH, H, I, OT>
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> Executor<I> for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'a>,
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
@ -255,22 +66,34 @@ where
|
||||
where
|
||||
EM: EventManager<I, S>,
|
||||
{
|
||||
if self.helper.stalker_enabled() {
|
||||
if !self.followed {
|
||||
self.followed = true;
|
||||
self.stalker
|
||||
.follow_me::<NoneEventSink>(self.helper.transformer(), None);
|
||||
} else {
|
||||
self.stalker.activate(NativePointer(
|
||||
self.base.harness_mut() as *mut _ as *mut c_void
|
||||
self.base.inner().harness_mut() as *mut _ as *mut c_void
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
self.helper.pre_exec(input);
|
||||
|
||||
self.base.pre_exec(state, event_mgr, input)
|
||||
}
|
||||
|
||||
/// Instruct the target about the input and run
|
||||
#[inline]
|
||||
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
|
||||
self.base.run_target(input)
|
||||
let res = self.base.run_target(input);
|
||||
if unsafe { ASAN_ERRORS.is_some() && !ASAN_ERRORS.as_ref().unwrap().is_empty() } {
|
||||
println!("Crashing target as it had ASAN errors");
|
||||
unsafe {
|
||||
libc::raise(libc::SIGABRT);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Called right after execution finished.
|
||||
@ -284,14 +107,17 @@ where
|
||||
where
|
||||
EM: EventManager<I, S>,
|
||||
{
|
||||
if self.helper.stalker_enabled() {
|
||||
self.stalker.deactivate();
|
||||
}
|
||||
self.helper.post_exec(input);
|
||||
self.base.post_exec(state, event_mgr, input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, FH, H, I, OT> HasObservers<OT> for FridaInProcessExecutor<'a, FH, H, I, OT>
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> HasObservers<OT> for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'a>,
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
@ -307,9 +133,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, FH, H, I, OT> Named for FridaInProcessExecutor<'a, FH, H, I, OT>
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> Named for FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'a>,
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
@ -319,31 +145,37 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, FH, H, I, OT> FridaInProcessExecutor<'a, FH, H, I, OT>
|
||||
impl<'a, 'b, 'c, FH, H, I, OT> FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT>
|
||||
where
|
||||
FH: FridaHelper<'a>,
|
||||
FH: FridaHelper<'b>,
|
||||
H: FnMut(&[u8]) -> ExitKind,
|
||||
I: Input + HasTargetBytes,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
pub fn new(gum: &'a Gum, base: InProcessExecutor<'a, H, I, OT>, helper: &'a FH) -> Self {
|
||||
let mut stalker = Stalker::new(gum);
|
||||
pub fn new(
|
||||
gum: &'a Gum,
|
||||
base: InProcessExecutor<'a, H, I, OT>,
|
||||
helper: &'c mut FH,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
let stalker = Stalker::new(gum);
|
||||
|
||||
// Let's exclude the main module and libc.so at least:
|
||||
stalker.exclude(&MemoryRange::new(
|
||||
Module::find_base_address(&env::args().next().unwrap()),
|
||||
get_module_size(&env::args().next().unwrap()),
|
||||
));
|
||||
stalker.exclude(&MemoryRange::new(
|
||||
Module::find_base_address("libc.so"),
|
||||
get_module_size("libc.so"),
|
||||
));
|
||||
//stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address(&env::args().next().unwrap()),
|
||||
//get_module_size(&env::args().next().unwrap()),
|
||||
//));
|
||||
//stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address("libc.so"),
|
||||
//get_module_size("libc.so"),
|
||||
//));
|
||||
|
||||
Self {
|
||||
base,
|
||||
base: TimeoutExecutor::new(base, timeout),
|
||||
stalker,
|
||||
helper,
|
||||
followed: false,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -362,6 +194,11 @@ pub fn main() {
|
||||
fuzz(
|
||||
&env::args().nth(1).expect("no module specified"),
|
||||
&env::args().nth(2).expect("no symbol specified"),
|
||||
env::args()
|
||||
.nth(3)
|
||||
.expect("no modules to instrument specified")
|
||||
.split(":")
|
||||
.collect(),
|
||||
vec![PathBuf::from("./corpus")],
|
||||
PathBuf::from("./crashes"),
|
||||
1337,
|
||||
@ -387,6 +224,7 @@ fn fuzz(
|
||||
unsafe fn fuzz(
|
||||
module_name: &str,
|
||||
symbol_name: &str,
|
||||
modules_to_instrument: Vec<&str>,
|
||||
corpus_dirs: Vec<PathBuf>,
|
||||
objective_dir: PathBuf,
|
||||
broker_port: u16,
|
||||
@ -395,8 +233,7 @@ unsafe fn fuzz(
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
let (state, mut restarting_mgr) =
|
||||
match setup_restarting_mgr_std(stats, broker_port) {
|
||||
let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
@ -409,23 +246,30 @@ unsafe fn fuzz(
|
||||
};
|
||||
|
||||
let gum = Gum::obtain();
|
||||
|
||||
let lib = libloading::Library::new(module_name).unwrap();
|
||||
let target_func: libloading::Symbol<unsafe extern "C" fn(data: *const u8, size: usize) -> i32> =
|
||||
lib.get(symbol_name.as_bytes()).unwrap();
|
||||
let mut frida_helper = FridaEdgeCoverageHelper::new(&gum, module_name);
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr(
|
||||
"edges",
|
||||
frida_helper.map.as_mut_ptr(),
|
||||
MAP_SIZE,
|
||||
));
|
||||
|
||||
let mut frida_harness = move |buf: &[u8]| {
|
||||
(target_func)(buf.as_ptr(), buf.len());
|
||||
ExitKind::Ok
|
||||
};
|
||||
|
||||
let mut frida_helper = FridaInstrumentationHelper::new(
|
||||
&gum,
|
||||
FridaOptions::parse_env_options(),
|
||||
module_name,
|
||||
&modules_to_instrument,
|
||||
);
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr(
|
||||
"edges",
|
||||
frida_helper.map_ptr(),
|
||||
MAP_SIZE,
|
||||
));
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
State::new(
|
||||
@ -441,9 +285,14 @@ unsafe fn fuzz(
|
||||
)),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new(objective_dir).unwrap(),
|
||||
OnDiskCorpus::new_save_meta(objective_dir, Some(OnDiskMetadataFormat::JsonPretty))
|
||||
.unwrap(),
|
||||
// Feedbacks to recognize an input as solution
|
||||
tuple_list!(CrashFeedback::new()),
|
||||
tuple_list!(
|
||||
CrashFeedback::new(),
|
||||
TimeoutFeedback::new(),
|
||||
AsanErrorsFeedback::new()
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
@ -474,21 +323,22 @@ unsafe fn fuzz(
|
||||
InProcessExecutor::new(
|
||||
"in-process(edges)",
|
||||
&mut frida_harness,
|
||||
tuple_list!(edges_observer),
|
||||
tuple_list!(edges_observer, AsanErrorsObserver::new(&ASAN_ERRORS)),
|
||||
&mut state,
|
||||
&mut restarting_mgr,
|
||||
)?,
|
||||
&frida_helper,
|
||||
&mut frida_helper,
|
||||
Duration::new(10, 0),
|
||||
);
|
||||
// Let's exclude the main module and libc.so at least:
|
||||
executor.stalker.exclude(&MemoryRange::new(
|
||||
Module::find_base_address(&env::args().next().unwrap()),
|
||||
get_module_size(&env::args().next().unwrap()),
|
||||
));
|
||||
executor.stalker.exclude(&MemoryRange::new(
|
||||
Module::find_base_address("libc.so"),
|
||||
get_module_size("libc.so"),
|
||||
));
|
||||
//executor.stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address(&env::args().next().unwrap()),
|
||||
//get_module_size(&env::args().next().unwrap()),
|
||||
//));
|
||||
//executor.stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address("libc.so"),
|
||||
//get_module_size("libc.so"),
|
||||
//));
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
@ -501,6 +351,7 @@ unsafe fn fuzz(
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
//executor.helper.register_thread();
|
||||
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
|
||||
|
||||
// Never reached
|
||||
|
@ -4,12 +4,12 @@
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use libafl::{
|
||||
bolts::{shmem::StdShMem, tuples::tuple_list},
|
||||
bolts::tuples::tuple_list,
|
||||
corpus::{
|
||||
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
|
||||
QueueCorpusScheduler,
|
||||
},
|
||||
events::setup_restarting_mgr,
|
||||
events::setup_restarting_mgr_std,
|
||||
executors::{inprocess::InProcessExecutor, ExitKind},
|
||||
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
|
||||
fuzzer::{Fuzzer, StdFuzzer},
|
||||
@ -25,7 +25,6 @@ use libafl::{
|
||||
|
||||
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM};
|
||||
|
||||
/// The main fn, no_mangle as it is a C main
|
||||
pub fn main() {
|
||||
// Registry the metadata types used in this fuzzer
|
||||
// Needed only on no_std
|
||||
@ -49,8 +48,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
|
||||
let stats = SimpleStats::new(|s| println!("{}", s));
|
||||
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
let (state, mut restarting_mgr) =
|
||||
match setup_restarting_mgr::<_, _, StdShMem, _>(stats, broker_port) {
|
||||
let (state, mut restarting_mgr) = match setup_restarting_mgr_std(stats, broker_port) {
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
|
@ -39,7 +39,7 @@ std = [] # print, sharedmap, ... support
|
||||
anymap_debug = ["serde_json"] # uses serde_json to Debug the anymap trait. Disable for smaller footprint.
|
||||
derive = ["libafl_derive"] # provide derive(SerdeAny) macro.
|
||||
llmp_small_maps = [] # reduces initial map size for llmp
|
||||
llmp_debug = [] # Enables debug output for LLMP
|
||||
llmp_debug = ["backtrace"] # Enables debug output for LLMP
|
||||
|
||||
[[example]]
|
||||
name = "llmp_test"
|
||||
@ -59,13 +59,20 @@ ctor = "*"
|
||||
libafl_derive = { version = "*", optional = true, path = "../libafl_derive" }
|
||||
serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap
|
||||
num_enum = "0.5.1"
|
||||
spin = "0.9.0"
|
||||
|
||||
backtrace = "0.3" # for llmp_debug
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
backtrace = { version = "0.3", optional = true, default-features = false, features = ["std", "libbacktrace"] } # for llmp_debug
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
backtrace = { version = "0.3", optional = true } # for llmp_debug
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2" # For (*nix) libc
|
||||
nix = "0.20.0"
|
||||
uds = "0.2.3"
|
||||
lock_api = "0.4.3"
|
||||
regex = "1.4.5"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = "0.4.0"
|
||||
|
@ -25,8 +25,8 @@ const _TAG_1MEG_V1: Tag = 0xB1111161;
|
||||
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
fn adder_loop(port: u16) -> ! {
|
||||
let shmem_provider = Rc::new(RefCell::new(StdShMemProvider::new()));
|
||||
let mut client = llmp::LlmpClient::create_attach_to_tcp(&shmem_provider, port).unwrap();
|
||||
let shmem_provider = StdShMemProvider::new().unwrap();
|
||||
let mut client = llmp::LlmpClient::create_attach_to_tcp(shmem_provider, port).unwrap();
|
||||
let mut last_result: u32 = 0;
|
||||
let mut current_result: u32 = 0;
|
||||
loop {
|
||||
@ -68,11 +68,8 @@ fn adder_loop(port: u16) -> ! {
|
||||
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
fn large_msg_loop(port: u16) -> ! {
|
||||
let mut client = llmp::LlmpClient::create_attach_to_tcp(
|
||||
&Rc::new(RefCell::new(StdShMemProvider::new())),
|
||||
port,
|
||||
)
|
||||
.unwrap();
|
||||
let mut client =
|
||||
llmp::LlmpClient::create_attach_to_tcp(StdShMemProvider::new().unwrap(), port).unwrap();
|
||||
|
||||
let meg_buf = [1u8; 1 << 20];
|
||||
|
||||
@ -133,8 +130,7 @@ fn main() {
|
||||
|
||||
match mode.as_str() {
|
||||
"broker" => {
|
||||
let mut broker =
|
||||
llmp::LlmpBroker::new(&Rc::new(RefCell::new(StdShMemProvider::new()))).unwrap();
|
||||
let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap();
|
||||
broker
|
||||
.launch_listener(llmp::Listener::Tcp(
|
||||
std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap(),
|
||||
@ -143,10 +139,8 @@ fn main() {
|
||||
broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5)))
|
||||
}
|
||||
"ctr" => {
|
||||
let mut client = llmp::LlmpClient::create_attach_to_tcp(
|
||||
&Rc::new(RefCell::new(StdShMemProvider::new())),
|
||||
port,
|
||||
)
|
||||
let mut client =
|
||||
llmp::LlmpClient::create_attach_to_tcp(StdShMemProvider::new().unwrap(), port)
|
||||
.unwrap();
|
||||
let mut counter: u32 = 0;
|
||||
loop {
|
||||
|
@ -52,9 +52,8 @@ Then register some clientloops using llmp_broker_register_threaded_clientloop
|
||||
|
||||
*/
|
||||
|
||||
use alloc::{rc::Rc, string::String, vec::Vec};
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::{
|
||||
cell::RefCell,
|
||||
cmp::max,
|
||||
fmt::Debug,
|
||||
mem::size_of,
|
||||
@ -75,11 +74,13 @@ use std::{
|
||||
use backtrace::Backtrace;
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::bolts::os::unix_signals::{c_void, setup_signal_handler, siginfo_t, Handler, Signal};
|
||||
use crate::bolts::os::unix_signals::{setup_signal_handler, siginfo_t, Handler, Signal};
|
||||
use crate::{
|
||||
bolts::shmem::{ShMem, ShMemDescription, ShMemId, ShMemProvider},
|
||||
Error,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use libc::ucontext_t;
|
||||
|
||||
/// We'll start off with 256 megabyte maps per fuzzer client
|
||||
#[cfg(not(feature = "llmp_small_maps"))]
|
||||
@ -215,8 +216,12 @@ fn new_map_size(max_alloc: usize) -> usize {
|
||||
/// Initialize a new llmp_page. size should be relative to
|
||||
/// llmp_page->messages
|
||||
unsafe fn _llmp_page_init<SHM: ShMem>(shmem: &mut SHM, sender: u32, allow_reinit: bool) {
|
||||
let map_size = shmem.map().len();
|
||||
#[cfg(all(feature = "llmp_debug", feature = "std"))]
|
||||
dbg!("_llmp_page_init: shmem {}", &shmem);
|
||||
let map_size = shmem.len();
|
||||
let page = shmem2page_mut(shmem);
|
||||
#[cfg(all(feature = "llmp_debug", feature = "std"))]
|
||||
dbg!("_llmp_page_init: page {}", *page);
|
||||
if (*page).magic == PAGE_INITIALIZED_MAGIC && !allow_reinit {
|
||||
panic!(
|
||||
"Tried to initialize page {:?} twice (for shmem {:?})",
|
||||
@ -234,6 +239,7 @@ unsafe fn _llmp_page_init<SHM: ShMem>(shmem: &mut SHM, sender: u32, allow_reinit
|
||||
(*(*page).messages.as_mut_ptr()).tag = LLMP_TAG_UNSET;
|
||||
ptr::write_volatile(&mut (*page).save_to_unmap, 0);
|
||||
ptr::write_volatile(&mut (*page).sender_dead, 0);
|
||||
assert!((*page).size_total != 0);
|
||||
}
|
||||
|
||||
/// Get the next pointer and make sure it's in the current page, and has enough space.
|
||||
@ -365,7 +371,7 @@ where
|
||||
{
|
||||
#[cfg(feature = "std")]
|
||||
/// Creates either a broker, if the tcp port is not bound, or a client, connected to this port.
|
||||
pub fn on_port(shmem_provider: &Rc<RefCell<SP>>, port: u16) -> Result<Self, Error> {
|
||||
pub fn on_port(shmem_provider: SP, port: u16) -> Result<Self, Error> {
|
||||
match TcpListener::bind(format!("127.0.0.1:{}", port)) {
|
||||
Ok(listener) => {
|
||||
// We got the port. We are the broker! :)
|
||||
@ -390,12 +396,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shmem_provider(&mut self) -> &Rc<RefCell<SP>> {
|
||||
match self {
|
||||
LlmpConnection::IsBroker { broker } => &broker.shmem_provider,
|
||||
LlmpConnection::IsClient { client } => &client.shmem_provider,
|
||||
}
|
||||
}
|
||||
/// Describe this in a reproducable fashion, if it's a client
|
||||
pub fn describe(&self) -> Result<LlmpClientDescription, Error> {
|
||||
Ok(match self {
|
||||
@ -406,7 +406,7 @@ where
|
||||
|
||||
/// Recreate an existing client from the stored description
|
||||
pub fn existing_client_from_description(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
description: &LlmpClientDescription,
|
||||
) -> Result<LlmpConnection<SP>, Error> {
|
||||
Ok(LlmpConnection::IsClient {
|
||||
@ -480,7 +480,7 @@ where
|
||||
/// By keeping the message history around,
|
||||
/// new clients may join at any time in the future.
|
||||
pub keep_pages_forever: bool,
|
||||
shmem_provider: Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
}
|
||||
|
||||
/// An actor on the sending part of the shared map
|
||||
@ -488,23 +488,17 @@ impl<SP> LlmpSender<SP>
|
||||
where
|
||||
SP: ShMemProvider,
|
||||
{
|
||||
pub fn new(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
id: u32,
|
||||
keep_pages_forever: bool,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn new(mut shmem_provider: SP, id: u32, keep_pages_forever: bool) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
id,
|
||||
last_msg_sent: ptr::null_mut(),
|
||||
out_maps: vec![LlmpSharedMap::new(
|
||||
0,
|
||||
shmem_provider
|
||||
.borrow_mut()
|
||||
.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?,
|
||||
shmem_provider.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?,
|
||||
)],
|
||||
// drop pages to the broker if it already read them
|
||||
keep_pages_forever,
|
||||
shmem_provider: shmem_provider.clone(),
|
||||
shmem_provider,
|
||||
})
|
||||
}
|
||||
|
||||
@ -520,14 +514,11 @@ where
|
||||
|
||||
/// Reattach to a vacant out_map, to with a previous sender stored the information in an env before.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn on_existing_from_env(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
env_name: &str,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn on_existing_from_env(mut shmem_provider: SP, env_name: &str) -> Result<Self, Error> {
|
||||
let msg_sent_offset = msg_offset_from_env(env_name)?;
|
||||
Self::on_existing_map(
|
||||
shmem_provider.clone(),
|
||||
shmem_provider.borrow_mut().existing_from_env(env_name)?,
|
||||
shmem_provider.existing_from_env(env_name)?,
|
||||
msg_sent_offset,
|
||||
)
|
||||
}
|
||||
@ -565,7 +556,7 @@ where
|
||||
/// It is essential, that the receiver (or someone else) keeps a pointer to this map
|
||||
/// else reattach will get a new, empty page, from the OS, or fail.
|
||||
pub fn on_existing_map(
|
||||
shmem_provider: Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
current_out_map: SP::Mem,
|
||||
last_msg_sent_offset: Option<u64>,
|
||||
) -> Result<Self, Error> {
|
||||
@ -649,7 +640,7 @@ where
|
||||
#[cfg(all(feature = "llmp_debug", feature = "std"))]
|
||||
println!(
|
||||
"Allocating {} (>={}) bytes on page {:?} / map {:?} (last msg: {:?})",
|
||||
complete_msg_size, buf_len, page, map, last_msg
|
||||
complete_msg_size, buf_len, page, &map, last_msg
|
||||
);
|
||||
/* DBG("XXX complete_msg_size %lu (h: %lu)\n", complete_msg_size, sizeof(llmp_message)); */
|
||||
/* In case we don't have enough space, make sure the next page will be large
|
||||
@ -795,7 +786,6 @@ where
|
||||
let mut new_map_shmem = LlmpSharedMap::new(
|
||||
(*old_map).sender,
|
||||
self.shmem_provider
|
||||
.borrow_mut()
|
||||
.new_map(new_map_size((*old_map).max_alloc_size))?,
|
||||
);
|
||||
let mut new_map = new_map_shmem.page_mut();
|
||||
@ -909,14 +899,12 @@ where
|
||||
|
||||
// Create this client on an existing map from the given description. acquired with `self.describe`
|
||||
pub fn on_existing_from_description(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
mut shmem_provider: SP,
|
||||
description: &LlmpDescription,
|
||||
) -> Result<Self, Error> {
|
||||
Self::on_existing_map(
|
||||
shmem_provider.clone(),
|
||||
shmem_provider
|
||||
.borrow_mut()
|
||||
.from_description(description.shmem)?,
|
||||
shmem_provider.from_description(description.shmem)?,
|
||||
description.last_message_offset,
|
||||
)
|
||||
}
|
||||
@ -932,7 +920,7 @@ where
|
||||
/// Pointer to the last meg this received
|
||||
pub last_msg_recvd: *const LlmpMsg,
|
||||
/// The shmem provider
|
||||
pub shmem_provider: Rc<RefCell<SP>>,
|
||||
pub shmem_provider: SP,
|
||||
/// current page. After EOP, this gets replaced with the new one
|
||||
pub current_recv_map: LlmpSharedMap<SP::Mem>,
|
||||
}
|
||||
@ -944,13 +932,10 @@ where
|
||||
{
|
||||
/// Reattach to a vacant recv_map, to with a previous sender stored the information in an env before.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn on_existing_from_env(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
env_name: &str,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn on_existing_from_env(mut shmem_provider: SP, env_name: &str) -> Result<Self, Error> {
|
||||
Self::on_existing_map(
|
||||
shmem_provider.clone(),
|
||||
shmem_provider.borrow_mut().existing_from_env(env_name)?,
|
||||
shmem_provider.existing_from_env(env_name)?,
|
||||
msg_offset_from_env(env_name)?,
|
||||
)
|
||||
}
|
||||
@ -968,7 +953,7 @@ where
|
||||
/// It is essential, that the sender (or someone else) keeps a pointer to the sender_map
|
||||
/// else reattach will get a new, empty page, from the OS, or fail.
|
||||
pub fn on_existing_map(
|
||||
shmem_provider: Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
current_sender_map: SP::Mem,
|
||||
last_msg_recvd_offset: Option<u64>,
|
||||
) -> Result<Self, Error> {
|
||||
@ -1053,12 +1038,11 @@ where
|
||||
ptr::write_volatile(&mut (*page).save_to_unmap, 1);
|
||||
|
||||
// Map the new page. The old one should be unmapped by Drop
|
||||
self.current_recv_map = LlmpSharedMap::existing(
|
||||
self.shmem_provider.borrow_mut().from_id_and_size(
|
||||
self.current_recv_map =
|
||||
LlmpSharedMap::existing(self.shmem_provider.from_id_and_size(
|
||||
ShMemId::from_slice(&pageinfo_cpy.shm_str),
|
||||
pageinfo_cpy.map_size,
|
||||
)?,
|
||||
);
|
||||
)?);
|
||||
page = self.current_recv_map.page_mut();
|
||||
// Mark the new page save to unmap also (it's mapped by us, the broker now)
|
||||
ptr::write_volatile(&mut (*page).save_to_unmap, 1);
|
||||
@ -1151,14 +1135,12 @@ where
|
||||
|
||||
// Create this client on an existing map from the given description. acquired with `self.describe`
|
||||
pub fn on_existing_from_description(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
mut shmem_provider: SP,
|
||||
description: &LlmpDescription,
|
||||
) -> Result<Self, Error> {
|
||||
Self::on_existing_map(
|
||||
shmem_provider.clone(),
|
||||
shmem_provider
|
||||
.borrow_mut()
|
||||
.from_description(description.shmem)?,
|
||||
shmem_provider.from_description(description.shmem)?,
|
||||
description.last_message_offset,
|
||||
)
|
||||
}
|
||||
@ -1220,6 +1202,8 @@ where
|
||||
if (*ret.page()).magic != PAGE_INITIALIZED_MAGIC {
|
||||
panic!("Map was not priviously initialized at {:?}", &ret.shmem);
|
||||
}
|
||||
#[cfg(all(feature = "llmp_debug", feature = "std"))]
|
||||
dbg!("PAGE: {}", *ret.page());
|
||||
}
|
||||
ret
|
||||
}
|
||||
@ -1328,7 +1312,7 @@ where
|
||||
/// handlers
|
||||
shutting_down: bool,
|
||||
/// The ShMemProvider to use
|
||||
shmem_provider: Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@ -1336,9 +1320,9 @@ pub struct LlmpBrokerSignalHandler {
|
||||
shutting_down: bool,
|
||||
}
|
||||
|
||||
#[cfg(all(unix))]
|
||||
#[cfg(unix)]
|
||||
impl Handler for LlmpBrokerSignalHandler {
|
||||
fn handle(&mut self, _signal: Signal, _info: siginfo_t, _void: *const c_void) {
|
||||
fn handle(&mut self, _signal: Signal, _info: siginfo_t, _context: &mut ucontext_t) {
|
||||
unsafe { ptr::write_volatile(&mut self.shutting_down, true) };
|
||||
}
|
||||
|
||||
@ -1354,14 +1338,14 @@ where
|
||||
SP: ShMemProvider,
|
||||
{
|
||||
/// Create and initialize a new llmp_broker
|
||||
pub fn new(shmem_provider: &Rc<RefCell<SP>>) -> Result<Self, Error> {
|
||||
pub fn new(mut shmem_provider: SP) -> Result<Self, Error> {
|
||||
Ok(LlmpBroker {
|
||||
llmp_out: LlmpSender {
|
||||
id: 0,
|
||||
last_msg_sent: ptr::null_mut(),
|
||||
out_maps: vec![LlmpSharedMap::new(
|
||||
0,
|
||||
shmem_provider.borrow_mut().new_map(new_map_size(0))?,
|
||||
shmem_provider.new_map(new_map_size(0))?,
|
||||
)],
|
||||
// Broker never cleans up the pages so that new
|
||||
// clients may join at any time
|
||||
@ -1371,7 +1355,7 @@ where
|
||||
llmp_clients: vec![],
|
||||
socket_name: None,
|
||||
shutting_down: false,
|
||||
shmem_provider: shmem_provider.clone(),
|
||||
shmem_provider,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1515,33 +1499,30 @@ where
|
||||
// Tcp out map sends messages from background thread tcp server to foreground client
|
||||
let tcp_out_map = LlmpSharedMap::new(
|
||||
llmp_tcp_id,
|
||||
self.shmem_provider
|
||||
.borrow_mut()
|
||||
.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?,
|
||||
self.shmem_provider.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?,
|
||||
);
|
||||
let shmem_id = tcp_out_map.shmem.id();
|
||||
let tcp_out_map_str = *shmem_id.as_slice();
|
||||
let tcp_out_map_size = tcp_out_map.shmem.len();
|
||||
self.register_client(tcp_out_map);
|
||||
|
||||
let shmem_provider_clone = self.shmem_provider.borrow_mut().clone();
|
||||
let mut shmem_provider_clone = self.shmem_provider.clone();
|
||||
|
||||
Ok(thread::spawn(move || {
|
||||
let shmem_provider = Rc::new(RefCell::new(shmem_provider_clone));
|
||||
shmem_provider_clone.post_fork();
|
||||
// Clone so we get a new connection to the AshmemServer if we are using
|
||||
// ServedShMemProvider
|
||||
let mut new_client_sender = LlmpSender {
|
||||
id: 0,
|
||||
last_msg_sent: ptr::null_mut(),
|
||||
out_maps: vec![LlmpSharedMap::existing(
|
||||
shmem_provider
|
||||
.borrow_mut()
|
||||
shmem_provider_clone
|
||||
.from_id_and_size(ShMemId::from_slice(&tcp_out_map_str), tcp_out_map_size)
|
||||
.unwrap(),
|
||||
)],
|
||||
// drop pages to the broker if it already read them
|
||||
keep_pages_forever: false,
|
||||
shmem_provider: shmem_provider.clone(),
|
||||
shmem_provider: shmem_provider_clone.clone(),
|
||||
};
|
||||
|
||||
loop {
|
||||
@ -1627,7 +1608,7 @@ where
|
||||
} else {
|
||||
let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo;
|
||||
|
||||
match self.shmem_provider.borrow_mut().from_id_and_size(
|
||||
match self.shmem_provider.from_id_and_size(
|
||||
ShMemId::from_slice(&(*pageinfo).shm_str),
|
||||
(*pageinfo).map_size,
|
||||
) {
|
||||
@ -1686,7 +1667,7 @@ pub struct LlmpClient<SP>
|
||||
where
|
||||
SP: ShMemProvider,
|
||||
{
|
||||
shmem_provider: Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
/// Outgoing channel to the broker
|
||||
pub sender: LlmpSender<SP>,
|
||||
/// Incoming (broker) broadcast map
|
||||
@ -1703,7 +1684,7 @@ where
|
||||
/// It is essential, that the broker (or someone else) kept a pointer to the out_map
|
||||
/// else reattach will get a new, empty page, from the OS, or fail
|
||||
pub fn on_existing_map(
|
||||
shmem_provider: Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
_current_out_map: SP::Mem,
|
||||
_last_msg_sent_offset: Option<u64>,
|
||||
current_broker_map: SP::Mem,
|
||||
@ -1726,20 +1707,17 @@ where
|
||||
|
||||
/// Recreate this client from a previous client.to_env
|
||||
#[cfg(feature = "std")]
|
||||
pub fn on_existing_from_env(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
env_name: &str,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn on_existing_from_env(shmem_provider: SP, env_name: &str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
sender: LlmpSender::on_existing_from_env(
|
||||
shmem_provider,
|
||||
shmem_provider.clone(),
|
||||
&format!("{}_SENDER", env_name),
|
||||
)?,
|
||||
receiver: LlmpReceiver::on_existing_from_env(
|
||||
shmem_provider,
|
||||
shmem_provider.clone(),
|
||||
&format!("{}_RECEIVER", env_name),
|
||||
)?,
|
||||
shmem_provider: shmem_provider.clone(),
|
||||
shmem_provider,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1761,16 +1739,19 @@ where
|
||||
|
||||
/// Create an existing client from description
|
||||
fn existing_client_from_description(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
description: &LlmpClientDescription,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
sender: LlmpSender::on_existing_from_description(shmem_provider, &description.sender)?,
|
||||
sender: LlmpSender::on_existing_from_description(
|
||||
shmem_provider.clone(),
|
||||
&description.sender,
|
||||
)?,
|
||||
receiver: LlmpReceiver::on_existing_from_description(
|
||||
shmem_provider,
|
||||
shmem_provider.clone(),
|
||||
&description.receiver,
|
||||
)?,
|
||||
shmem_provider: shmem_provider.clone(),
|
||||
shmem_provider,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1787,7 +1768,7 @@ where
|
||||
|
||||
/// Creates a new LlmpClient
|
||||
pub fn new(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
mut shmem_provider: SP,
|
||||
initial_broker_map: LlmpSharedMap<SP::Mem>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
@ -1795,9 +1776,7 @@ where
|
||||
id: 0,
|
||||
last_msg_sent: ptr::null_mut(),
|
||||
out_maps: vec![LlmpSharedMap::new(0, {
|
||||
shmem_provider
|
||||
.borrow_mut()
|
||||
.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?
|
||||
shmem_provider.new_map(LLMP_CFG_INITIAL_MAP_SIZE)?
|
||||
})],
|
||||
// drop pages to the broker if it already read them
|
||||
keep_pages_forever: false,
|
||||
@ -1810,7 +1789,7 @@ where
|
||||
last_msg_recvd: ptr::null_mut(),
|
||||
shmem_provider: shmem_provider.clone(),
|
||||
},
|
||||
shmem_provider: shmem_provider.clone(),
|
||||
shmem_provider,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1887,20 +1866,14 @@ where
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Creates a new LlmpClient, reading the map id and len from env
|
||||
pub fn create_using_env(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
env_var: &str,
|
||||
) -> Result<Self, Error> {
|
||||
let map = LlmpSharedMap::existing(shmem_provider.borrow_mut().existing_from_env(env_var)?);
|
||||
pub fn create_using_env(mut shmem_provider: SP, env_var: &str) -> Result<Self, Error> {
|
||||
let map = LlmpSharedMap::existing(shmem_provider.existing_from_env(env_var)?);
|
||||
Self::new(shmem_provider, map)
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Create a LlmpClient, getting the ID from a given port
|
||||
pub fn create_attach_to_tcp(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
port: u16,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn create_attach_to_tcp(mut shmem_provider: SP, port: u16) -> Result<Self, Error> {
|
||||
let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port))?;
|
||||
println!("Connected to port {}", port);
|
||||
|
||||
@ -1915,11 +1888,7 @@ where
|
||||
|
||||
let broker_map_description: ShMemDescription = postcard::from_bytes(&new_broker_map_str)?;
|
||||
|
||||
let map = LlmpSharedMap::existing(
|
||||
shmem_provider
|
||||
.borrow_mut()
|
||||
.from_description(broker_map_description)?,
|
||||
);
|
||||
let map = LlmpSharedMap::existing(shmem_provider.from_description(broker_map_description)?);
|
||||
let ret = Self::new(shmem_provider, map)?;
|
||||
|
||||
let own_map_description_bytes =
|
||||
@ -1933,7 +1902,6 @@ where
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
mod tests {
|
||||
|
||||
use alloc::rc::Rc;
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
use super::{
|
||||
@ -1945,18 +1913,16 @@ mod tests {
|
||||
|
||||
use crate::bolts::shmem::{ShMemProvider, StdShMemProvider};
|
||||
|
||||
use core::cell::RefCell;
|
||||
|
||||
#[test]
|
||||
pub fn llmp_connection() {
|
||||
let shmem_provider = Rc::new(RefCell::new(StdShMemProvider::new()));
|
||||
let mut broker = match LlmpConnection::on_port(&shmem_provider, 1337).unwrap() {
|
||||
let shmem_provider = StdShMemProvider::new().unwrap();
|
||||
let mut broker = match LlmpConnection::on_port(shmem_provider.clone(), 1337).unwrap() {
|
||||
IsClient { client: _ } => panic!("Could not bind to port as broker"),
|
||||
IsBroker { broker } => broker,
|
||||
};
|
||||
|
||||
// Add the first client (2nd, actually, because of the tcp listener client)
|
||||
let mut client = match LlmpConnection::on_port(&shmem_provider, 1337).unwrap() {
|
||||
let mut client = match LlmpConnection::on_port(shmem_provider.clone(), 1337).unwrap() {
|
||||
IsBroker { broker: _ } => panic!("Second connect should be a client!"),
|
||||
IsClient { client } => client,
|
||||
};
|
||||
@ -1973,6 +1939,7 @@ mod tests {
|
||||
client.send_buf(tag, &arr).unwrap();
|
||||
|
||||
client.to_env("_ENV_TEST").unwrap();
|
||||
#[cfg(all(feature = "llmp_debug", feature = "std"))]
|
||||
dbg!(std::env::vars());
|
||||
|
||||
for (key, value) in std::env::vars_os() {
|
||||
@ -1980,7 +1947,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/* recreate the client from env, check if it still works */
|
||||
client = LlmpClient::on_existing_from_env(&shmem_provider, "_ENV_TEST").unwrap();
|
||||
client = LlmpClient::on_existing_from_env(shmem_provider, "_ENV_TEST").unwrap();
|
||||
|
||||
client.send_buf(tag, &arr).unwrap();
|
||||
|
||||
|
@ -11,10 +11,13 @@ use crate::{
|
||||
},
|
||||
Error,
|
||||
};
|
||||
use core::mem::ManuallyDrop;
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
io::{Read, Write},
|
||||
rc::Rc,
|
||||
sync::{Arc, Condvar, Mutex},
|
||||
};
|
||||
|
||||
@ -39,11 +42,12 @@ const ASHMEM_SERVER_NAME: &str = "@ashmem_server";
|
||||
pub struct ServedShMemProvider {
|
||||
stream: UnixStream,
|
||||
inner: AshmemShMemProvider,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServedShMem {
|
||||
inner: AshmemShMem,
|
||||
inner: ManuallyDrop<AshmemShMem>,
|
||||
server_fd: i32,
|
||||
}
|
||||
|
||||
@ -95,13 +99,13 @@ impl ServedShMemProvider {
|
||||
|
||||
impl Default for ServedShMemProvider {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ServedShMemProvider {
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,22 +113,26 @@ impl ShMemProvider for ServedShMemProvider {
|
||||
type Mem = ServedShMem;
|
||||
|
||||
/// Connect to the server and return a new ServedShMemProvider
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
fn new() -> Result<Self, Error> {
|
||||
let mut res = Self {
|
||||
stream: UnixStream::connect_to_unix_addr(
|
||||
&UnixSocketAddr::new(ASHMEM_SERVER_NAME).unwrap(),
|
||||
)
|
||||
.expect("Unable to open connection to ashmem service"),
|
||||
inner: AshmemShMemProvider::new(),
|
||||
}
|
||||
)?,
|
||||
inner: AshmemShMemProvider::new()?,
|
||||
id: -1,
|
||||
};
|
||||
let (id, _) = res.send_receive(AshmemRequest::Hello(None));
|
||||
res.id = id;
|
||||
Ok(res)
|
||||
}
|
||||
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, crate::Error> {
|
||||
let (server_fd, client_fd) = self.send_receive(AshmemRequest::NewMap(map_size));
|
||||
|
||||
Ok(ServedShMem {
|
||||
inner: self
|
||||
.inner
|
||||
inner: ManuallyDrop::new(
|
||||
self.inner
|
||||
.from_id_and_size(ShMemId::from_string(&format!("{}", client_fd)), map_size)?,
|
||||
),
|
||||
server_fd,
|
||||
})
|
||||
}
|
||||
@ -136,12 +144,30 @@ impl ShMemProvider for ServedShMemProvider {
|
||||
ShMemDescription::from_string_and_size(server_id_str, size),
|
||||
));
|
||||
Ok(ServedShMem {
|
||||
inner: self
|
||||
.inner
|
||||
inner: ManuallyDrop::new(
|
||||
self.inner
|
||||
.from_id_and_size(ShMemId::from_string(&format!("{}", client_fd)), size)?,
|
||||
),
|
||||
server_fd,
|
||||
})
|
||||
}
|
||||
|
||||
fn post_fork(&mut self) {
|
||||
self.stream =
|
||||
UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME).unwrap())
|
||||
.expect("Unable to reconnect to the ashmem service");
|
||||
let (id, _) = self.send_receive(AshmemRequest::Hello(Some(self.id)));
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
fn release_map(&mut self, map: &mut Self::Mem) {
|
||||
let (refcount, _) = self.send_receive(AshmemRequest::Deregister(map.server_fd));
|
||||
if refcount == 0 {
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut map.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request sent to the ShMem server to receive a fd to a shared map
|
||||
@ -152,38 +178,119 @@ pub enum AshmemRequest {
|
||||
/// Another client already has a map with this description mapped.
|
||||
ExistingMap(ShMemDescription),
|
||||
/// A client tells us it unregisters the previously allocated map
|
||||
Deregister(u32),
|
||||
Deregister(i32),
|
||||
/// A message that tells us hello, and optionally which other client we were created from, we
|
||||
/// return a client id.
|
||||
Hello(Option<i32>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AshmemClient {
|
||||
stream: UnixStream,
|
||||
maps: HashMap<i32, Vec<Rc<RefCell<AshmemShMem>>>>,
|
||||
}
|
||||
|
||||
impl AshmemClient {
|
||||
fn new(stream: UnixStream) -> Self {
|
||||
Self { stream }
|
||||
Self {
|
||||
stream,
|
||||
maps: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AshmemService {
|
||||
provider: AshmemShMemProvider,
|
||||
maps: Vec<AshmemShMem>,
|
||||
clients: HashMap<RawFd, AshmemClient>,
|
||||
all_maps: HashMap<i32, Rc<RefCell<AshmemShMem>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AshmemResponse {
|
||||
Mapping(Rc<RefCell<AshmemShMem>>),
|
||||
Id(i32),
|
||||
RefCount(u32),
|
||||
}
|
||||
|
||||
impl AshmemService {
|
||||
/// Create a new AshMem service
|
||||
#[must_use]
|
||||
fn new() -> Self {
|
||||
AshmemService {
|
||||
provider: AshmemShMemProvider::new(),
|
||||
maps: Vec::new(),
|
||||
}
|
||||
fn new() -> Result<Self, Error> {
|
||||
Ok(AshmemService {
|
||||
provider: AshmemShMemProvider::new()?,
|
||||
clients: HashMap::new(),
|
||||
all_maps: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Read and handle the client request, send the answer over unix fd.
|
||||
fn handle_client(&mut self, client: &mut AshmemClient) -> Result<(), Error> {
|
||||
fn handle_request(&mut self, client_id: RawFd) -> Result<AshmemResponse, Error> {
|
||||
let request = self.read_request(client_id)?;
|
||||
|
||||
//println!("got ashmem client: {}, request:{:?}", client_id, request);
|
||||
// Handle the client request
|
||||
let response = match request {
|
||||
AshmemRequest::Hello(other_id) => {
|
||||
if let Some(other_id) = other_id {
|
||||
if other_id != client_id {
|
||||
// remove temporarily
|
||||
let other_client = self.clients.remove(&other_id);
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
for (id, map) in other_client.as_ref().unwrap().maps.iter() {
|
||||
client.maps.insert(*id, map.clone());
|
||||
}
|
||||
self.clients.insert(other_id, other_client.unwrap());
|
||||
}
|
||||
};
|
||||
Ok(AshmemResponse::Id(client_id))
|
||||
}
|
||||
AshmemRequest::NewMap(map_size) => Ok(AshmemResponse::Mapping(Rc::new(RefCell::new(
|
||||
self.provider.new_map(map_size)?,
|
||||
)))),
|
||||
AshmemRequest::ExistingMap(description) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
if client.maps.contains_key(&description.id.to_int()) {
|
||||
Ok(AshmemResponse::Mapping(
|
||||
client
|
||||
.maps
|
||||
.get_mut(&description.id.to_int())
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.first()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.clone(),
|
||||
))
|
||||
} else if self.all_maps.contains_key(&description.id.to_int()) {
|
||||
Ok(AshmemResponse::Mapping(
|
||||
self.all_maps
|
||||
.get_mut(&description.id.to_int())
|
||||
.unwrap()
|
||||
.clone(),
|
||||
))
|
||||
} else {
|
||||
let new_rc =
|
||||
Rc::new(RefCell::new(self.provider.from_description(description)?));
|
||||
self.all_maps
|
||||
.insert(description.id.to_int(), new_rc.clone());
|
||||
Ok(AshmemResponse::Mapping(new_rc))
|
||||
}
|
||||
}
|
||||
AshmemRequest::Deregister(map_id) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
let map = client.maps.entry(map_id).or_default().pop().unwrap();
|
||||
Ok(AshmemResponse::RefCount(Rc::strong_count(&map) as u32))
|
||||
}
|
||||
};
|
||||
//println!("send ashmem client: {}, response: {:?}", client_id, &response);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn read_request(&mut self, client_id: RawFd) -> Result<AshmemRequest, Error> {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
|
||||
// Always receive one be u32 of size, then the command.
|
||||
let mut size_bytes = [0u8; 4];
|
||||
client.stream.read_exact(&mut size_bytes)?;
|
||||
@ -196,23 +303,36 @@ impl AshmemService {
|
||||
.expect("Failed to read message body");
|
||||
let request: AshmemRequest = postcard::from_bytes(&bytes)?;
|
||||
|
||||
// Handle the client request
|
||||
let mapping = match request {
|
||||
AshmemRequest::NewMap(map_size) => self.provider.new_map(map_size)?,
|
||||
AshmemRequest::ExistingMap(description) => {
|
||||
self.provider.from_description(description)?
|
||||
Ok(request)
|
||||
}
|
||||
AshmemRequest::Deregister(_) => {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
fn handle_client(&mut self, client_id: RawFd) -> Result<(), Error> {
|
||||
let response = self.handle_request(client_id)?;
|
||||
|
||||
let id = mapping.id();
|
||||
match response {
|
||||
AshmemResponse::Mapping(mapping) => {
|
||||
let id = mapping.borrow().id();
|
||||
let server_fd: i32 = id.to_string().parse().unwrap();
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
client
|
||||
.stream
|
||||
.send_fds(&id.to_string().as_bytes(), &[server_fd])?;
|
||||
self.maps.push(mapping);
|
||||
client
|
||||
.maps
|
||||
.entry(server_fd)
|
||||
.or_default()
|
||||
.push(mapping.clone());
|
||||
}
|
||||
AshmemResponse::Id(id) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
client.stream.send_fds(&id.to_string().as_bytes(), &[])?;
|
||||
}
|
||||
AshmemResponse::RefCount(refcount) => {
|
||||
let client = self.clients.get_mut(&client_id).unwrap();
|
||||
client
|
||||
.stream
|
||||
.send_fds(&refcount.to_string().as_bytes(), &[])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -222,7 +342,7 @@ impl AshmemService {
|
||||
let syncpair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
let childsyncpair = Arc::clone(&syncpair);
|
||||
let join_handle =
|
||||
thread::spawn(move || Self::new().listen(ASHMEM_SERVER_NAME, childsyncpair));
|
||||
thread::spawn(move || Self::new()?.listen(ASHMEM_SERVER_NAME, childsyncpair));
|
||||
|
||||
let (lock, cvar) = &*syncpair;
|
||||
let mut started = lock.lock().unwrap();
|
||||
@ -252,7 +372,6 @@ impl AshmemService {
|
||||
"The server appears to already be running. We are probably a client".to_string(),
|
||||
));
|
||||
};
|
||||
let mut clients: HashMap<RawFd, AshmemClient> = HashMap::new();
|
||||
let mut poll_fds: Vec<PollFd> = vec![PollFd::new(
|
||||
listener.as_raw_fd(),
|
||||
PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND,
|
||||
@ -278,11 +397,10 @@ impl AshmemService {
|
||||
unsafe { *((&poll_fd as *const PollFd) as *const libc::pollfd) }.fd;
|
||||
if revents.contains(PollFlags::POLLHUP) {
|
||||
poll_fds.remove(poll_fds.iter().position(|item| *item == poll_fd).unwrap());
|
||||
clients.remove(&raw_polled_fd);
|
||||
self.clients.remove(&raw_polled_fd);
|
||||
} else if revents.contains(PollFlags::POLLIN) {
|
||||
if clients.contains_key(&raw_polled_fd) {
|
||||
let mut client = clients.get_mut(&raw_polled_fd).unwrap();
|
||||
match self.handle_client(&mut client) {
|
||||
if self.clients.contains_key(&raw_polled_fd) {
|
||||
match self.handle_client(raw_polled_fd) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
dbg!("Ignoring failed read from client", e, poll_fd);
|
||||
@ -304,14 +422,15 @@ impl AshmemService {
|
||||
PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND,
|
||||
);
|
||||
poll_fds.push(pollfd);
|
||||
let mut client = AshmemClient::new(stream);
|
||||
match self.handle_client(&mut client) {
|
||||
let client = AshmemClient::new(stream);
|
||||
let client_id = client.stream.as_raw_fd();
|
||||
self.clients.insert(client_id, client);
|
||||
match self.handle_client(client_id) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
dbg!("Ignoring failed read from client", e);
|
||||
}
|
||||
};
|
||||
clients.insert(client.stream.as_raw_fd(), client);
|
||||
}
|
||||
} else {
|
||||
//println!("Unknown revents flags: {:?}", revents);
|
||||
|
@ -13,9 +13,9 @@ use core::{
|
||||
use std::ffi::CString;
|
||||
|
||||
use libc::{
|
||||
c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, SA_NODEFER, SA_ONSTACK,
|
||||
SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE,
|
||||
SIGQUIT, SIGSEGV, SIGTERM, SIGUSR2,
|
||||
c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, ucontext_t, SA_NODEFER,
|
||||
SA_ONSTACK, SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL,
|
||||
SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, SIGTRAP, SIGUSR2,
|
||||
};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
|
||||
@ -40,6 +40,7 @@ pub enum Signal {
|
||||
SigQuit = SIGQUIT,
|
||||
SigTerm = SIGTERM,
|
||||
SigInterrupt = SIGINT,
|
||||
SigTrap = SIGTRAP,
|
||||
}
|
||||
|
||||
pub static CRASH_SIGNALS: &[Signal] = &[
|
||||
@ -77,6 +78,7 @@ impl Display for Signal {
|
||||
Signal::SigQuit => write!(f, "SIGQUIT")?,
|
||||
Signal::SigTerm => write!(f, "SIGTERM")?,
|
||||
Signal::SigInterrupt => write!(f, "SIGINT")?,
|
||||
Signal::SigTrap => write!(f, "SIGTRAP")?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
@ -85,7 +87,7 @@ impl Display for Signal {
|
||||
|
||||
pub trait Handler {
|
||||
/// Handle a signal
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, _void: *const c_void);
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, _context: &mut ucontext_t);
|
||||
/// Return a list of signals to handle
|
||||
fn signals(&self) -> Vec<Signal>;
|
||||
}
|
||||
@ -113,7 +115,7 @@ static mut SIGNAL_HANDLERS: [Option<HandlerHolder>; 32] = [
|
||||
/// # Safety
|
||||
/// This should be somewhat safe to call for signals previously registered,
|
||||
/// unless the signal handlers registered using [setup_signal_handler] are broken.
|
||||
unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: *const c_void) {
|
||||
unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: *mut c_void) {
|
||||
let signal = &Signal::try_from(sig).unwrap();
|
||||
let handler = {
|
||||
match &SIGNAL_HANDLERS[*signal as usize] {
|
||||
@ -121,7 +123,7 @@ unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: *const c_void) {
|
||||
None => return,
|
||||
}
|
||||
};
|
||||
handler.handle(*signal, info, void);
|
||||
handler.handle(*signal, info, &mut *(void as *mut ucontext_t));
|
||||
}
|
||||
|
||||
/// Setup signal handlers in a somewhat rusty way.
|
||||
|
@ -16,11 +16,11 @@ pub type OsShMemProvider = Win32ShMemProvider;
|
||||
pub type OsShMem = Win32ShMem;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use crate::bolts::os::ashmem_server::{ServedShMem, ServedShMemProvider};
|
||||
use crate::bolts::os::ashmem_server::ServedShMemProvider;
|
||||
#[cfg(target_os = "android")]
|
||||
pub type StdShMemProvider = ServedShMemProvider;
|
||||
pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider>;
|
||||
#[cfg(target_os = "android")]
|
||||
pub type StdShMem = ServedShMem;
|
||||
pub type StdShMem = RcShMem<ServedShMemProvider>;
|
||||
|
||||
#[cfg(all(feature = "std", not(target_os = "android")))]
|
||||
pub type StdShMemProvider = OsShMemProvider;
|
||||
@ -32,7 +32,9 @@ use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "std")]
|
||||
use std::env;
|
||||
|
||||
use alloc::string::ToString;
|
||||
use alloc::{rc::Rc, string::ToString};
|
||||
use core::cell::RefCell;
|
||||
use core::mem::ManuallyDrop;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
@ -137,11 +139,11 @@ pub trait ShMem: Sized + Debug + Clone {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ShMemProvider: Send + Clone + Default {
|
||||
pub trait ShMemProvider: Send + Clone + Default + Debug {
|
||||
type Mem: ShMem;
|
||||
|
||||
/// Create a new instance of the provider
|
||||
fn new() -> Self;
|
||||
fn new() -> Result<Self, Error>;
|
||||
|
||||
/// Create a new shared memory mapping
|
||||
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, Error>;
|
||||
@ -168,6 +170,108 @@ pub trait ShMemProvider: Send + Clone + Default {
|
||||
map_size,
|
||||
))
|
||||
}
|
||||
|
||||
/// This method should be called after a fork or thread creation event, allowing the ShMem to
|
||||
/// reset thread specific info.
|
||||
fn post_fork(&mut self) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/// Release the resources associated with the given ShMem
|
||||
fn release_map(&mut self, _map: &mut Self::Mem) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RcShMem<T: ShMemProvider> {
|
||||
internal: ManuallyDrop<T::Mem>,
|
||||
provider: Rc<RefCell<T>>,
|
||||
}
|
||||
|
||||
impl<T> ShMem for RcShMem<T>
|
||||
where
|
||||
T: ShMemProvider + alloc::fmt::Debug,
|
||||
{
|
||||
fn id(&self) -> ShMemId {
|
||||
self.internal.id()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.internal.len()
|
||||
}
|
||||
|
||||
fn map(&self) -> &[u8] {
|
||||
self.internal.map()
|
||||
}
|
||||
|
||||
fn map_mut(&mut self) -> &mut [u8] {
|
||||
self.internal.map_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ShMemProvider> Drop for RcShMem<T> {
|
||||
fn drop(&mut self) {
|
||||
self.provider.borrow_mut().release_map(&mut self.internal)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RcShMemProvider<T: ShMemProvider> {
|
||||
internal: Rc<RefCell<T>>,
|
||||
}
|
||||
|
||||
unsafe impl<T: ShMemProvider> Send for RcShMemProvider<T> {}
|
||||
|
||||
impl<T> ShMemProvider for RcShMemProvider<T>
|
||||
where
|
||||
T: ShMemProvider + alloc::fmt::Debug,
|
||||
{
|
||||
type Mem = RcShMem<T>;
|
||||
|
||||
fn new() -> Result<Self, Error> {
|
||||
return Ok(Self {
|
||||
internal: Rc::new(RefCell::new(T::new()?)),
|
||||
});
|
||||
}
|
||||
|
||||
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, Error> {
|
||||
Ok(Self::Mem {
|
||||
internal: ManuallyDrop::new(self.internal.borrow_mut().new_map(map_size)?),
|
||||
provider: self.internal.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result<Self::Mem, Error> {
|
||||
Ok(Self::Mem {
|
||||
internal: ManuallyDrop::new(self.internal.borrow_mut().from_id_and_size(id, size)?),
|
||||
provider: self.internal.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn release_map(&mut self, map: &mut Self::Mem) {
|
||||
self.internal.borrow_mut().release_map(&mut map.internal)
|
||||
}
|
||||
|
||||
fn clone_ref(&mut self, mapping: &Self::Mem) -> Result<Self::Mem, Error> {
|
||||
Ok(Self::Mem {
|
||||
internal: ManuallyDrop::new(self.internal.borrow_mut().clone_ref(&mapping.internal)?),
|
||||
provider: self.internal.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn post_fork(&mut self) {
|
||||
self.internal.borrow_mut().post_fork()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for RcShMemProvider<T>
|
||||
where
|
||||
T: ShMemProvider + alloc::fmt::Debug,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "std"))]
|
||||
@ -320,7 +424,7 @@ pub mod unix_shmem {
|
||||
#[cfg(unix)]
|
||||
impl Default for CommonUnixShMemProvider {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,8 +433,8 @@ pub mod unix_shmem {
|
||||
impl ShMemProvider for CommonUnixShMemProvider {
|
||||
type Mem = CommonUnixShMem;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
fn new() -> Result<Self, Error> {
|
||||
Ok(Self {})
|
||||
}
|
||||
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, Error> {
|
||||
CommonUnixShMem::new(map_size)
|
||||
@ -507,7 +611,6 @@ pub mod unix_shmem {
|
||||
impl Drop for AshmemShMem {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
//let fd = Self::fd_from_id(self.id).unwrap();
|
||||
let fd: i32 = self.id.to_string().parse().unwrap();
|
||||
|
||||
let length = ioctl(fd, ASHMEM_GET_SIZE);
|
||||
@ -533,7 +636,7 @@ pub mod unix_shmem {
|
||||
#[cfg(unix)]
|
||||
impl Default for AshmemShMemProvider {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -542,8 +645,8 @@ pub mod unix_shmem {
|
||||
impl ShMemProvider for AshmemShMemProvider {
|
||||
type Mem = AshmemShMem;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
fn new() -> Result<Self, Error> {
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, Error> {
|
||||
@ -693,7 +796,7 @@ pub mod win32_shmem {
|
||||
|
||||
impl Default for Win32ShMemProvider {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -701,8 +804,8 @@ pub mod win32_shmem {
|
||||
impl ShMemProvider for Win32ShMemProvider {
|
||||
type Mem = Win32ShMem;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
fn new() -> Result<Self, Error> {
|
||||
Ok(Self {})
|
||||
}
|
||||
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, Error> {
|
||||
Win32ShMem::new_map(map_size)
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! LLMP-backed event manager for scalable multi-processed fuzzing
|
||||
|
||||
use alloc::{rc::Rc, string::ToString, vec::Vec};
|
||||
use core::{cell::RefCell, marker::PhantomData, time::Duration};
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use core::{marker::PhantomData, time::Duration};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@ -12,6 +12,7 @@ use crate::bolts::{
|
||||
llmp::{LlmpClient, LlmpReceiver},
|
||||
shmem::StdShMemProvider,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
bolts::{
|
||||
llmp::{self, LlmpClientDescription, LlmpSender, Tag},
|
||||
@ -32,10 +33,7 @@ use crate::{
|
||||
use crate::utils::startable_self;
|
||||
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
use crate::{
|
||||
bolts::shmem::UnixShMemProvider,
|
||||
utils::{fork, ForkResult},
|
||||
};
|
||||
use crate::utils::{fork, ForkResult};
|
||||
|
||||
#[cfg(all(feature = "std", target_os = "android"))]
|
||||
use crate::bolts::os::ashmem_server::AshmemService;
|
||||
@ -65,41 +63,6 @@ where
|
||||
phantom: PhantomData<(I, S)>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg(unix)]
|
||||
impl<I, S, ST> LlmpEventManager<I, S, UnixShMemProvider, ST>
|
||||
where
|
||||
I: Input,
|
||||
S: IfInteresting<I>,
|
||||
ST: Stats,
|
||||
{
|
||||
/// Create llmp on a port
|
||||
/// If the port is not yet bound, it will act as broker
|
||||
/// Else, it will act as client.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new_on_port_std(
|
||||
shmem_provider: &Rc<RefCell<UnixShMemProvider>>,
|
||||
stats: ST,
|
||||
port: u16,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: Some(stats),
|
||||
llmp: llmp::LlmpConnection::on_port(shmem_provider, port)?,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// If a client respawns, it may reuse the existing connection, previously stored by LlmpClient::to_env
|
||||
/// Std uses UnixShMem.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn existing_client_from_env_std(
|
||||
shmem_provider: &Rc<RefCell<UnixShMemProvider>>,
|
||||
env_name: &str,
|
||||
) -> Result<Self, Error> {
|
||||
Self::existing_client_from_env(shmem_provider, env_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, SP, ST> Drop for LlmpEventManager<I, S, SP, ST>
|
||||
where
|
||||
I: Input,
|
||||
@ -124,11 +87,7 @@ where
|
||||
/// If the port is not yet bound, it will act as broker
|
||||
/// Else, it will act as client.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new_on_port(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
stats: ST,
|
||||
port: u16,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn new_on_port(shmem_provider: SP, stats: ST, port: u16) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: Some(stats),
|
||||
llmp: llmp::LlmpConnection::on_port(shmem_provider, port)?,
|
||||
@ -138,10 +97,7 @@ where
|
||||
|
||||
/// If a client respawns, it may reuse the existing connection, previously stored by LlmpClient::to_env
|
||||
#[cfg(feature = "std")]
|
||||
pub fn existing_client_from_env(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
env_name: &str,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn existing_client_from_env(shmem_provider: SP, env_name: &str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
stats: None,
|
||||
llmp: llmp::LlmpConnection::IsClient {
|
||||
@ -160,7 +116,7 @@ where
|
||||
|
||||
/// Create an existing client from description
|
||||
pub fn existing_client_from_description(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
description: &LlmpClientDescription,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
@ -304,7 +260,7 @@ where
|
||||
|
||||
let observers: OT = postcard::from_bytes(&observers_buf)?;
|
||||
// TODO include ExitKind in NewTestcase
|
||||
let fitness = state.is_interesting(&input, &observers, ExitKind::Ok)?;
|
||||
let fitness = state.is_interesting(&input, &observers, &ExitKind::Ok)?;
|
||||
if fitness > 0
|
||||
&& state
|
||||
.add_if_interesting(&input, fitness, scheduler)?
|
||||
@ -400,7 +356,7 @@ where
|
||||
/// Deserialize the state and corpus tuple, previously serialized with `serialize_state_corpus(...)`
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deserialize_state_mgr<I, S, SP, ST>(
|
||||
shmem_provider: &Rc<RefCell<SP>>,
|
||||
shmem_provider: SP,
|
||||
state_corpus_serialized: &[u8],
|
||||
) -> Result<(S, LlmpEventManager<I, S, SP, ST>), Error>
|
||||
where
|
||||
@ -525,7 +481,7 @@ where
|
||||
#[cfg(target_os = "android")]
|
||||
AshmemService::start().expect("Error starting Ashmem Service");
|
||||
|
||||
setup_restarting_mgr(StdShMemProvider::new(), stats, broker_port)
|
||||
setup_restarting_mgr(StdShMemProvider::new()?, stats, broker_port)
|
||||
}
|
||||
|
||||
/// A restarting state is a combination of restarter and runner, that can be used on systems without `fork`.
|
||||
@ -537,7 +493,7 @@ where
|
||||
clippy::similar_names
|
||||
)] // for { mgr = LlmpEventManager... }
|
||||
pub fn setup_restarting_mgr<I, S, SP, ST>(
|
||||
shmem_provider: SP,
|
||||
mut shmem_provider: SP,
|
||||
//mgr: &mut LlmpEventManager<I, S, SH, ST>,
|
||||
stats: ST,
|
||||
broker_port: u16,
|
||||
@ -548,13 +504,13 @@ where
|
||||
SP: ShMemProvider,
|
||||
ST: Stats,
|
||||
{
|
||||
let shmem_provider = Rc::new(RefCell::new(shmem_provider));
|
||||
|
||||
let mut mgr =
|
||||
LlmpEventManager::<I, S, SP, ST>::new_on_port(&shmem_provider, stats, broker_port)?;
|
||||
LlmpEventManager::<I, S, SP, ST>::new_on_port(shmem_provider.clone(), stats, broker_port)?;
|
||||
|
||||
// We start ourself as child process to actually fuzz
|
||||
let (sender, mut receiver, shmem_provider) = if std::env::var(_ENV_FUZZER_SENDER).is_err() {
|
||||
let (sender, mut receiver, mut new_shmem_provider) = if std::env::var(_ENV_FUZZER_SENDER)
|
||||
.is_err()
|
||||
{
|
||||
if mgr.is_broker() {
|
||||
// Yep, broker. Just loop here.
|
||||
println!("Doing broker things. Run this tool again to start fuzzing in a client.");
|
||||
@ -566,13 +522,9 @@ where
|
||||
mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL);
|
||||
|
||||
// First, create a channel from the fuzzer (sender) to us (receiver) to report its state for restarts.
|
||||
let sender = { LlmpSender::new(&shmem_provider, 0, false)? };
|
||||
let sender = { LlmpSender::new(shmem_provider.clone(), 0, false)? };
|
||||
|
||||
let map = {
|
||||
shmem_provider
|
||||
.borrow_mut()
|
||||
.clone_ref(&sender.out_maps.last().unwrap().shmem)?
|
||||
};
|
||||
let map = { shmem_provider.clone_ref(&sender.out_maps.last().unwrap().shmem)? };
|
||||
let receiver = LlmpReceiver::on_existing_map(shmem_provider.clone(), map, None)?;
|
||||
// Store the information to a map.
|
||||
sender.to_env(_ENV_FUZZER_SENDER)?;
|
||||
@ -613,14 +565,16 @@ where
|
||||
// A sender and a receiver for single communication
|
||||
// Clone so we get a new connection to the AshmemServer if we are using
|
||||
// ServedShMemProvider
|
||||
let shmem_provider = Rc::new(RefCell::new(shmem_provider.borrow_mut().clone()));
|
||||
shmem_provider.post_fork();
|
||||
(
|
||||
LlmpSender::on_existing_from_env(&shmem_provider, _ENV_FUZZER_SENDER)?,
|
||||
LlmpReceiver::on_existing_from_env(&shmem_provider, _ENV_FUZZER_RECEIVER)?,
|
||||
LlmpSender::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_SENDER)?,
|
||||
LlmpReceiver::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_RECEIVER)?,
|
||||
shmem_provider,
|
||||
)
|
||||
};
|
||||
|
||||
new_shmem_provider.post_fork();
|
||||
|
||||
println!("We're a client, let's fuzz :)");
|
||||
|
||||
for (var, val) in std::env::vars() {
|
||||
@ -633,7 +587,7 @@ where
|
||||
println!("First run. Let's set it all up");
|
||||
// Mgr to send and receive msgs from/to all other fuzzer instances
|
||||
let client_mgr = LlmpEventManager::<I, S, SP, ST>::existing_client_from_env(
|
||||
&shmem_provider,
|
||||
new_shmem_provider,
|
||||
_ENV_FUZZER_BROKER_CLIENT_INITIAL,
|
||||
)?;
|
||||
|
||||
@ -643,7 +597,7 @@ where
|
||||
Some((_sender, _tag, msg)) => {
|
||||
println!("Subsequent run. Let's load all data from shmem (received {} bytes from previous instance)", msg.len());
|
||||
let (state, mgr): (S, LlmpEventManager<I, S, SP, ST>) =
|
||||
deserialize_state_mgr(&shmem_provider, &msg)?;
|
||||
deserialize_state_mgr(new_shmem_provider, &msg)?;
|
||||
|
||||
(Some(state), LlmpRestartingEventManager::new(mgr, sender))
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ where
|
||||
mod unix_signal_handler {
|
||||
use alloc::vec::Vec;
|
||||
use core::ptr;
|
||||
use libc::{c_void, siginfo_t};
|
||||
use libc::{c_void, siginfo_t, ucontext_t};
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::{stdout, Write};
|
||||
|
||||
@ -273,8 +273,8 @@ mod unix_signal_handler {
|
||||
pub event_mgr_ptr: *mut c_void,
|
||||
pub observers_ptr: *const c_void,
|
||||
pub current_input_ptr: *const c_void,
|
||||
pub crash_handler: unsafe fn(Signal, siginfo_t, *const c_void, data: &mut Self),
|
||||
pub timeout_handler: unsafe fn(Signal, siginfo_t, *const c_void, data: &mut Self),
|
||||
pub crash_handler: unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut Self),
|
||||
pub timeout_handler: unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut Self),
|
||||
}
|
||||
|
||||
unsafe impl Send for InProcessExecutorHandlerData {}
|
||||
@ -283,21 +283,21 @@ mod unix_signal_handler {
|
||||
unsafe fn nop_handler(
|
||||
_signal: Signal,
|
||||
_info: siginfo_t,
|
||||
_void: *const c_void,
|
||||
_context: &mut ucontext_t,
|
||||
_data: &mut InProcessExecutorHandlerData,
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Handler for InProcessExecutorHandlerData {
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, void: *const c_void) {
|
||||
fn handle(&mut self, signal: Signal, info: siginfo_t, context: &mut ucontext_t) {
|
||||
unsafe {
|
||||
let data = &mut GLOBAL_STATE;
|
||||
match signal {
|
||||
Signal::SigUser2 | Signal::SigAlarm => {
|
||||
(data.timeout_handler)(signal, info, void, data)
|
||||
(data.timeout_handler)(signal, info, context, data)
|
||||
}
|
||||
_ => (data.crash_handler)(signal, info, void, data),
|
||||
_ => (data.crash_handler)(signal, info, context, data),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,6 +312,7 @@ mod unix_signal_handler {
|
||||
Signal::SigFloatingPointException,
|
||||
Signal::SigIllegalInstruction,
|
||||
Signal::SigSegmentationFault,
|
||||
Signal::SigTrap,
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -320,7 +321,7 @@ mod unix_signal_handler {
|
||||
pub unsafe fn inproc_timeout_handler<EM, I, OC, OFT, OT, S>(
|
||||
_signal: Signal,
|
||||
_info: siginfo_t,
|
||||
_void: *const c_void,
|
||||
_context: &mut ucontext_t,
|
||||
data: &mut InProcessExecutorHandlerData,
|
||||
) where
|
||||
EM: EventManager<I, S>,
|
||||
@ -348,7 +349,7 @@ mod unix_signal_handler {
|
||||
|
||||
let obj_fitness = state
|
||||
.objectives_mut()
|
||||
.is_interesting_all(&input, observers, ExitKind::Timeout)
|
||||
.is_interesting_all(&input, observers, &ExitKind::Timeout)
|
||||
.expect("In timeout handler objectives failure.");
|
||||
if obj_fitness > 0 {
|
||||
state
|
||||
@ -382,7 +383,7 @@ mod unix_signal_handler {
|
||||
pub unsafe fn inproc_crash_handler<EM, I, OC, OFT, OT, S>(
|
||||
_signal: Signal,
|
||||
_info: siginfo_t,
|
||||
_void: *const c_void,
|
||||
_context: &mut ucontext_t,
|
||||
data: &mut InProcessExecutorHandlerData,
|
||||
) where
|
||||
EM: EventManager<I, S>,
|
||||
@ -392,6 +393,10 @@ mod unix_signal_handler {
|
||||
S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
|
||||
I: Input + HasTargetBytes,
|
||||
{
|
||||
#[cfg(all(target_os = "android", target_arch = "aarch64"))]
|
||||
let _context = *(((_context as *mut _ as *mut c_void as usize) + 128) as *mut c_void
|
||||
as *mut ucontext_t);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
println!("Crashed with {}", _signal);
|
||||
if !data.current_input_ptr.is_null() {
|
||||
@ -401,6 +406,45 @@ mod unix_signal_handler {
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
println!("Child crashed!");
|
||||
|
||||
#[cfg(all(
|
||||
feature = "std",
|
||||
any(target_os = "linux", target_os = "android"),
|
||||
target_arch = "aarch64"
|
||||
))]
|
||||
{
|
||||
use crate::utils::find_mapping_for_address;
|
||||
println!("{:━^100}", " CRASH ");
|
||||
println!(
|
||||
"Received signal {} at 0x{:016x}, fault address: 0x{:016x}",
|
||||
_signal, _context.uc_mcontext.pc, _context.uc_mcontext.fault_address
|
||||
);
|
||||
if let Ok((start, _, _, path)) =
|
||||
find_mapping_for_address(_context.uc_mcontext.pc as usize)
|
||||
{
|
||||
println!(
|
||||
"pc is at offset 0x{:08x} in {}",
|
||||
_context.uc_mcontext.pc as usize - start,
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
println!("{:━^100}", " REGISTERS ");
|
||||
for reg in 0..31 {
|
||||
print!(
|
||||
"x{:02}: 0x{:016x} ",
|
||||
reg, _context.uc_mcontext.regs[reg as usize]
|
||||
);
|
||||
if reg % 4 == 3 {
|
||||
println!();
|
||||
}
|
||||
}
|
||||
println!("pc : 0x{:016x} ", _context.uc_mcontext.pc);
|
||||
|
||||
//println!("{:━^100}", " BACKTRACE ");
|
||||
//println!("{:?}", backtrace::Backtrace::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
let _ = stdout().flush();
|
||||
|
||||
@ -410,7 +454,7 @@ mod unix_signal_handler {
|
||||
|
||||
let obj_fitness = state
|
||||
.objectives_mut()
|
||||
.is_interesting_all(&input, observers, ExitKind::Crash)
|
||||
.is_interesting_all(&input, observers, &ExitKind::Crash)
|
||||
.expect("In crash handler objectives failure.");
|
||||
if obj_fitness > 0 {
|
||||
let new_input = input.clone();
|
||||
@ -574,7 +618,7 @@ mod windows_exception_handler {
|
||||
|
||||
let obj_fitness = state
|
||||
.objectives_mut()
|
||||
.is_interesting_all(&input, observers, ExitKind::Crash)
|
||||
.is_interesting_all(&input, observers, &ExitKind::Crash)
|
||||
.expect("In crash handler objectives failure.");
|
||||
if obj_fitness > 0 {
|
||||
let new_input = input.clone();
|
||||
|
@ -5,24 +5,28 @@ pub use inprocess::InProcessExecutor;
|
||||
pub mod timeout;
|
||||
pub use timeout::TimeoutExecutor;
|
||||
|
||||
use core::cmp::PartialEq;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
bolts::tuples::Named,
|
||||
bolts::{serdeany::SerdeAny, tuples::Named},
|
||||
events::EventManager,
|
||||
inputs::{HasTargetBytes, Input},
|
||||
observers::ObserversTuple,
|
||||
Error,
|
||||
};
|
||||
|
||||
use alloc::boxed::Box;
|
||||
|
||||
pub trait CustomExitKind: core::fmt::Debug + SerdeAny + 'static {}
|
||||
|
||||
/// How an execution finished.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum ExitKind {
|
||||
Ok,
|
||||
Crash,
|
||||
Oom,
|
||||
Timeout,
|
||||
Custom(Box<dyn CustomExitKind>),
|
||||
}
|
||||
|
||||
pub trait HasObservers<OT>
|
||||
|
@ -91,6 +91,10 @@ where
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner(&mut self) -> &mut E {
|
||||
&mut self.executor
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I, OT> Executor<I> for TimeoutExecutor<E, I, OT>
|
||||
@ -126,6 +130,11 @@ where
|
||||
null_mut(),
|
||||
);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// TODO
|
||||
let _ = self.exec_tmout.as_millis();
|
||||
}
|
||||
self.executor.pre_exec(_state, _event_mgr, _input)
|
||||
}
|
||||
|
||||
@ -155,6 +164,10 @@ where
|
||||
null_mut(),
|
||||
);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
self.executor.post_exec(_state, _event_mgr, _input)
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ where
|
||||
&mut self,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: ExitKind,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error> {
|
||||
let mut interesting = 0;
|
||||
// TODO optimize
|
||||
|
@ -29,7 +29,7 @@ where
|
||||
&mut self,
|
||||
input: &I,
|
||||
observers: &OT,
|
||||
exit_kind: ExitKind,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error>;
|
||||
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
@ -54,7 +54,7 @@ where
|
||||
&mut self,
|
||||
input: &I,
|
||||
observers: &OT,
|
||||
exit_kind: ExitKind,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error>;
|
||||
|
||||
/// Write metadata for this testcase
|
||||
@ -73,7 +73,7 @@ where
|
||||
&mut self,
|
||||
_: &I,
|
||||
_: &OT,
|
||||
_: ExitKind,
|
||||
_: &ExitKind,
|
||||
) -> Result<u32, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
@ -99,9 +99,9 @@ where
|
||||
&mut self,
|
||||
input: &I,
|
||||
observers: &OT,
|
||||
exit_kind: ExitKind,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error> {
|
||||
Ok(self.0.is_interesting(input, observers, exit_kind.clone())?
|
||||
Ok(self.0.is_interesting(input, observers, exit_kind)?
|
||||
+ self.1.is_interesting_all(input, observers, exit_kind)?)
|
||||
}
|
||||
|
||||
@ -128,9 +128,9 @@ where
|
||||
&mut self,
|
||||
_input: &I,
|
||||
_observers: &OT,
|
||||
exit_kind: ExitKind,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error> {
|
||||
if exit_kind == ExitKind::Crash {
|
||||
if let ExitKind::Crash = exit_kind {
|
||||
Ok(1)
|
||||
} else {
|
||||
Ok(0)
|
||||
@ -168,9 +168,9 @@ where
|
||||
&mut self,
|
||||
_input: &I,
|
||||
_observers: &OT,
|
||||
exit_kind: ExitKind,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error> {
|
||||
if exit_kind == ExitKind::Timeout {
|
||||
if let ExitKind::Timeout = exit_kind {
|
||||
Ok(1)
|
||||
} else {
|
||||
Ok(0)
|
||||
@ -211,7 +211,7 @@ where
|
||||
&mut self,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: ExitKind,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error> {
|
||||
let observer = observers.match_first_type::<TimeObserver>().unwrap();
|
||||
self.exec_time = *observer.last_runtime();
|
||||
|
@ -169,7 +169,7 @@ where
|
||||
&mut self,
|
||||
input: &I,
|
||||
observers: &OT,
|
||||
exit_kind: ExitKind,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error>
|
||||
where
|
||||
OT: ObserversTuple;
|
||||
@ -448,7 +448,7 @@ where
|
||||
&mut self,
|
||||
input: &I,
|
||||
observers: &OT,
|
||||
exit_kind: ExitKind,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<u32, Error>
|
||||
where
|
||||
OT: ObserversTuple,
|
||||
@ -654,13 +654,13 @@ where
|
||||
executor.post_exec_observers()?;
|
||||
|
||||
let observers = executor.observers();
|
||||
let fitness =
|
||||
self.feedbacks_mut()
|
||||
.is_interesting_all(&input, observers, exit_kind.clone())?;
|
||||
let fitness = self
|
||||
.feedbacks_mut()
|
||||
.is_interesting_all(&input, observers, &exit_kind)?;
|
||||
|
||||
let is_solution = self
|
||||
.objectives_mut()
|
||||
.is_interesting_all(&input, observers, exit_kind)?
|
||||
.is_interesting_all(&input, observers, &exit_kind)?
|
||||
> 0;
|
||||
Ok((fitness, is_solution))
|
||||
}
|
||||
|
@ -438,6 +438,77 @@ pub fn startable_self() -> Result<Command, Error> {
|
||||
Ok(startable)
|
||||
}
|
||||
|
||||
/// Allows one to walk the mappings in /proc/self/maps, caling a callback function for each
|
||||
/// mapping.
|
||||
/// If the callback returns true, we stop the walk.
|
||||
#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))]
|
||||
pub fn walk_self_maps(visitor: &mut dyn FnMut(usize, usize, String, String) -> bool) {
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
};
|
||||
let re = Regex::new(r"^(?P<start>[0-9a-f]{8,16})-(?P<end>[0-9a-f]{8,16}) (?P<perm>[-rwxp]{4}) (?P<offset>[0-9a-f]{8}) [0-9a-f]+:[0-9a-f]+ [0-9]+\s+(?P<path>.*)$")
|
||||
.unwrap();
|
||||
|
||||
let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
|
||||
|
||||
for line in BufReader::new(mapsfile).lines() {
|
||||
let line = line.unwrap();
|
||||
if let Some(caps) = re.captures(&line) {
|
||||
if visitor(
|
||||
usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
|
||||
usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
|
||||
caps.name("perm").unwrap().as_str().to_string(),
|
||||
caps.name("path").unwrap().as_str().to_string(),
|
||||
) {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the start and end address, permissions and path of the mapping containing a particular address
|
||||
#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))]
|
||||
pub fn find_mapping_for_address(address: usize) -> Result<(usize, usize, String, String), Error> {
|
||||
let mut result = (0, 0, "".to_string(), "".to_string());
|
||||
walk_self_maps(&mut |start, end, permissions, path| {
|
||||
if start <= address && address < end {
|
||||
result = (start, end, permissions, path);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if result.0 != 0 {
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(Error::Unknown(
|
||||
"Couldn't find a mapping for this address".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the start and end address of the mapping containing with a particular path
|
||||
#[cfg(all(feature = "std", any(target_os = "linux", target_os = "android")))]
|
||||
pub fn find_mapping_for_path(libpath: &str) -> (usize, usize) {
|
||||
let mut libstart = 0;
|
||||
let mut libend = 0;
|
||||
walk_self_maps(&mut |start, end, _permissions, path| {
|
||||
if libpath == path {
|
||||
if libstart == 0 {
|
||||
libstart = start;
|
||||
}
|
||||
|
||||
libend = end;
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
(libstart, libend)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//use xxhash_rust::xxh3::xxh3_64_with_seed;
|
||||
|
33
libafl_frida/Cargo.toml
Normal file
33
libafl_frida/Cargo.toml
Normal file
@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "libafl_frida"
|
||||
version = "0.1.0"
|
||||
authors = ["s1341 <github@shmarya.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../libafl", version = "0.1.0", features = ["std", "libafl_derive"] }
|
||||
libafl_targets = { path = "../libafl_targets", version = "0.1.0" }
|
||||
nix = "0.20.0"
|
||||
libc = "0.2.92"
|
||||
hashbrown = "0.11"
|
||||
libloading = "0.7.0"
|
||||
rangemap = "0.1.10"
|
||||
frida-gum = { version = "0.4.0", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
|
||||
frida-gum-sys = { version = "0.2.4", features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
regex = "1.4"
|
||||
dynasmrt = "1.0.1"
|
||||
capstone = "0.8.0"
|
||||
color-backtrace ={ version = "0.5", features = [ "resolve-modules" ] }
|
||||
termcolor = "1.1.2"
|
||||
serde = "1.0"
|
||||
backtrace = { version = "0.3.58", default-features = false, features = ["std", "serde"] }
|
||||
num-traits = "0.2.14"
|
||||
seahash = "4.1.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
gothook = { version = "0.1" }
|
5
libafl_frida/build.rs
Normal file
5
libafl_frida/build.rs
Normal file
@ -0,0 +1,5 @@
|
||||
// build.rs
|
||||
|
||||
fn main() {
|
||||
cc::Build::new().file("src/gettls.c").compile("libgettls.a");
|
||||
}
|
1880
libafl_frida/src/asan_rt.rs
Normal file
1880
libafl_frida/src/asan_rt.rs
Normal file
File diff suppressed because it is too large
Load Diff
9
libafl_frida/src/gettls.c
Normal file
9
libafl_frida/src/gettls.c
Normal file
@ -0,0 +1,9 @@
|
||||
#ifdef _MSC_VER
|
||||
__declspec( thread ) int i = 0;
|
||||
#else
|
||||
__thread int i = 0;
|
||||
#endif
|
||||
|
||||
void * get_tls_ptr() {
|
||||
return (void*)&i;
|
||||
}
|
617
libafl_frida/src/helper.rs
Normal file
617
libafl_frida/src/helper.rs
Normal file
@ -0,0 +1,617 @@
|
||||
use libafl::inputs::{HasTargetBytes, Input};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use libafl::utils::find_mapping_for_path;
|
||||
|
||||
use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter};
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use capstone::arch::{arm64::Arm64OperandType, ArchOperand::Arm64Operand};
|
||||
use capstone::{
|
||||
arch::{self, BuildsCapstone},
|
||||
Capstone, Insn,
|
||||
};
|
||||
|
||||
use core::cell::RefCell;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use frida_gum::instruction_writer::X86Register;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use frida_gum::instruction_writer::{Aarch64Register, IndexMode};
|
||||
use frida_gum::{
|
||||
instruction_writer::InstructionWriter,
|
||||
stalker::{StalkerOutput, Transformer},
|
||||
CpuContext,
|
||||
};
|
||||
use frida_gum::{Gum, Module, PageProtection};
|
||||
use num_traits::cast::FromPrimitive;
|
||||
|
||||
use rangemap::RangeMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{asan_rt::AsanRuntime, FridaOptions};
|
||||
|
||||
/// An helper that feeds FridaInProcessExecutor with user-supplied instrumentation
|
||||
pub trait FridaHelper<'a> {
|
||||
fn transformer(&self) -> &Transformer<'a>;
|
||||
|
||||
fn register_thread(&self);
|
||||
|
||||
fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I);
|
||||
|
||||
fn post_exec<I: Input + HasTargetBytes>(&mut self, input: &I);
|
||||
|
||||
fn stalker_enabled(&self) -> bool;
|
||||
|
||||
fn map_ptr(&mut self) -> *mut u8;
|
||||
}
|
||||
|
||||
pub const MAP_SIZE: usize = 64 * 1024;
|
||||
|
||||
/// An helper that feeds FridaInProcessExecutor with edge-coverage instrumentation
|
||||
pub struct FridaInstrumentationHelper<'a> {
|
||||
map: [u8; MAP_SIZE],
|
||||
previous_pc: [u64; 1],
|
||||
current_log_impl: u64,
|
||||
/// Transformer that has to be passed to FridaInProcessExecutor
|
||||
transformer: Option<Transformer<'a>>,
|
||||
capstone: Capstone,
|
||||
asan_runtime: Rc<RefCell<AsanRuntime>>,
|
||||
ranges: RangeMap<usize, (u16, &'a str)>,
|
||||
options: FridaOptions,
|
||||
drcov_basic_blocks: Vec<DrCovBasicBlock>,
|
||||
}
|
||||
|
||||
impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> {
|
||||
fn transformer(&self) -> &Transformer<'a> {
|
||||
self.transformer.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Register the current thread with the FridaInstrumentationHelper
|
||||
fn register_thread(&self) {
|
||||
self.asan_runtime.borrow().register_thread();
|
||||
}
|
||||
|
||||
fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I) {
|
||||
let target_bytes = input.target_bytes();
|
||||
let slice = target_bytes.as_slice();
|
||||
//println!("target_bytes: {:02x?}", slice);
|
||||
self.asan_runtime
|
||||
.borrow()
|
||||
.unpoison(slice.as_ptr() as usize, slice.len());
|
||||
}
|
||||
|
||||
fn post_exec<I: Input + HasTargetBytes>(&mut self, input: &I) {
|
||||
if self.options.drcov_enabled() {
|
||||
let filename = format!(
|
||||
"./coverage/{:016x}.drcov",
|
||||
seahash::hash(input.target_bytes().as_slice())
|
||||
);
|
||||
DrCovWriter::new(&filename, &self.ranges, &mut self.drcov_basic_blocks).write();
|
||||
}
|
||||
|
||||
if self.options.asan_enabled() {
|
||||
if self.options.asan_detect_leaks() {
|
||||
self.asan_runtime.borrow_mut().check_for_leaks();
|
||||
}
|
||||
self.asan_runtime.borrow_mut().reset_allocations();
|
||||
}
|
||||
}
|
||||
|
||||
fn stalker_enabled(&self) -> bool {
|
||||
self.options.stalker_enabled()
|
||||
}
|
||||
|
||||
fn map_ptr(&mut self) -> *mut u8 {
|
||||
self.map.as_mut_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to get the size of a module's CODE section from frida
|
||||
pub fn get_module_size(module_name: &str) -> usize {
|
||||
let mut code_size = 0;
|
||||
let code_size_ref = &mut code_size;
|
||||
Module::enumerate_ranges(module_name, PageProtection::ReadExecute, move |details| {
|
||||
*code_size_ref = details.memory_range().size() as usize;
|
||||
true
|
||||
});
|
||||
|
||||
code_size
|
||||
}
|
||||
|
||||
/// A minimal maybe_log implementation. We insert this into the transformed instruction stream
|
||||
/// every time we need a copy that is within a direct branch of the start of the transformed basic
|
||||
/// block.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const MAYBE_LOG_CODE: [u8; 47] = [
|
||||
0x9c, /* pushfq */
|
||||
0x50, /* push rax */
|
||||
0x51, /* push rcx */
|
||||
0x52, /* push rdx */
|
||||
0x48, 0x8d, 0x05, 0x24, 0x00, 0x00, 0x00, /* lea rax, sym._afl_area_ptr_ptr */
|
||||
0x48, 0x8b, 0x00, /* mov rax, qword [rax] */
|
||||
0x48, 0x8d, 0x0d, 0x22, 0x00, 0x00, 0x00, /* lea rcx, sym.previous_pc */
|
||||
0x48, 0x8b, 0x11, /* mov rdx, qword [rcx] */
|
||||
0x48, 0x8b, 0x12, /* mov rdx, qword [rdx] */
|
||||
0x48, 0x31, 0xfa, /* xor rdx, rdi */
|
||||
0xfe, 0x04, 0x10, /* inc byte [rax + rdx] */
|
||||
0x48, 0xd1, 0xef, /* shr rdi, 1 */
|
||||
0x48, 0x8b, 0x01, /* mov rax, qword [rcx] */
|
||||
0x48, 0x89, 0x38, /* mov qword [rax], rdi */
|
||||
0x5a, /* pop rdx */
|
||||
0x59, /* pop rcx */
|
||||
0x58, /* pop rax */
|
||||
0x9d, /* popfq */
|
||||
0xc3, /* ret */
|
||||
|
||||
/* Read-only data goes here: */
|
||||
/* uint8_t* afl_area_ptr */
|
||||
/* uint64_t* afl_prev_loc_ptr */
|
||||
];
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const MAYBE_LOG_CODE: [u8; 60] = [
|
||||
// __afl_area_ptr[current_pc ^ previous_pc]++;
|
||||
// previous_pc = current_pc >> 1;
|
||||
0xE1, 0x0B, 0xBF, 0xA9, // stp x1, x2, [sp, -0x10]!
|
||||
0xE3, 0x13, 0xBF, 0xA9, // stp x3, x4, [sp, -0x10]!
|
||||
// x0 = current_pc
|
||||
0xa1, 0x01, 0x00, 0x58, // ldr x1, #0x30, =__afl_area_ptr
|
||||
0x82, 0x01, 0x00, 0x58, // ldr x2, #0x38, =&previous_pc
|
||||
0x44, 0x00, 0x40, 0xf9, // ldr x4, [x2] (=previous_pc)
|
||||
// __afl_area_ptr[current_pc ^ previous_pc]++;
|
||||
0x84, 0x00, 0x00, 0xca, // eor x4, x4, x0
|
||||
0x84, 0x3c, 0x40, 0x92, // and x4, x4, 0xffff (=MAP_SIZE - 1)
|
||||
//0x20, 0x13, 0x20, 0xd4,
|
||||
0x23, 0x68, 0x64, 0xf8, // ldr x3, [x1, x4]
|
||||
0x63, 0x04, 0x00, 0x91, // add x3, x3, #1
|
||||
0x23, 0x68, 0x24, 0xf8, // str x3, [x1, x4]
|
||||
// previous_pc = current_pc >> 1;
|
||||
0xe0, 0x07, 0x40, 0x8b, // add x0, xzr, x0, LSR #1
|
||||
0x40, 0x00, 0x00, 0xf9, // str x0, [x2]
|
||||
0xE3, 0x13, 0xc1, 0xA8, // ldp x3, x4, [sp], #0x10
|
||||
0xE1, 0x0B, 0xc1, 0xA8, // ldp x1, x2, [sp], #0x10
|
||||
0xC0, 0x03, 0x5F, 0xD6, // ret
|
||||
|
||||
// &afl_area_ptr
|
||||
// &afl_prev_loc_ptr
|
||||
];
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_pc(context: &CpuContext) -> usize {
|
||||
context.pc() as usize
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_pc(context: &CpuContext) -> usize {
|
||||
context.rip() as usize
|
||||
}
|
||||
|
||||
/// The implementation of the FridaInstrumentationHelper
|
||||
impl<'a> FridaInstrumentationHelper<'a> {
|
||||
/// Constructor function to create a new FridaInstrumentationHelper, given a module_name.
|
||||
pub fn new(
|
||||
gum: &'a Gum,
|
||||
options: FridaOptions,
|
||||
_harness_module_name: &str,
|
||||
modules_to_instrument: &'a Vec<&str>,
|
||||
) -> Self {
|
||||
let mut helper = Self {
|
||||
map: [0u8; MAP_SIZE],
|
||||
previous_pc: [0u64; 1],
|
||||
current_log_impl: 0,
|
||||
transformer: None,
|
||||
capstone: Capstone::new()
|
||||
.arm64()
|
||||
.mode(arch::arm64::ArchMode::Arm)
|
||||
.detail(true)
|
||||
.build()
|
||||
.expect("Failed to create Capstone object"),
|
||||
asan_runtime: AsanRuntime::new(options),
|
||||
ranges: RangeMap::new(),
|
||||
options,
|
||||
drcov_basic_blocks: vec![],
|
||||
};
|
||||
|
||||
if options.stalker_enabled() {
|
||||
for (id, module_name) in modules_to_instrument.iter().enumerate() {
|
||||
let (lib_start, lib_end) = find_mapping_for_path(module_name);
|
||||
println!("including range {:x}-{:x}", lib_start, lib_end);
|
||||
helper
|
||||
.ranges
|
||||
.insert(lib_start..lib_end, (id as u16, module_name));
|
||||
}
|
||||
|
||||
if helper.options.drcov_enabled() {
|
||||
std::fs::create_dir_all("./coverage")
|
||||
.expect("failed to create directory for coverage files");
|
||||
}
|
||||
|
||||
let transformer = Transformer::from_callback(gum, |basic_block, output| {
|
||||
let mut first = true;
|
||||
for instruction in basic_block {
|
||||
let instr = instruction.instr();
|
||||
let address = instr.address();
|
||||
//println!("address: {:x} contains: {:?}", address, helper.ranges.contains(&(address as usize)));
|
||||
if helper.ranges.contains_key(&(address as usize)) {
|
||||
if first {
|
||||
first = false;
|
||||
//println!("block @ {:x} transformed to {:x}", address, output.writer().pc());
|
||||
if helper.options.coverage_enabled() {
|
||||
helper.emit_coverage_mapping(address, &output);
|
||||
}
|
||||
if helper.options.drcov_enabled() {
|
||||
instruction.put_callout(|context| {
|
||||
let real_address = match helper
|
||||
.asan_runtime
|
||||
.borrow()
|
||||
.real_address_for_stalked(get_pc(&context))
|
||||
{
|
||||
Some(address) => *address,
|
||||
_ => get_pc(&context),
|
||||
};
|
||||
//let (range, (id, name)) = helper.ranges.get_key_value(&real_address).unwrap();
|
||||
//println!("{}:0x{:016x}", name, real_address - range.start);
|
||||
helper
|
||||
.drcov_basic_blocks
|
||||
.push(DrCovBasicBlock::new(real_address, real_address + 4));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if helper.options.asan_enabled() {
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
todo!("Implement ASAN for non-aarch64 targets");
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
if let Ok((basereg, indexreg, displacement, width)) =
|
||||
helper.is_interesting_instruction(address, instr)
|
||||
{
|
||||
helper.emit_shadow_check(
|
||||
address,
|
||||
&output,
|
||||
basereg,
|
||||
indexreg,
|
||||
displacement,
|
||||
width,
|
||||
);
|
||||
}
|
||||
}
|
||||
if helper.options.asan_enabled() || helper.options.drcov_enabled() {
|
||||
helper.asan_runtime.borrow_mut().add_stalked_address(
|
||||
output.writer().pc() as usize - 4,
|
||||
address as usize,
|
||||
);
|
||||
}
|
||||
}
|
||||
instruction.keep()
|
||||
}
|
||||
});
|
||||
helper.transformer = Some(transformer);
|
||||
if helper.options.asan_enabled() || helper.options.drcov_enabled() {
|
||||
helper.asan_runtime.borrow_mut().init(modules_to_instrument);
|
||||
}
|
||||
}
|
||||
helper
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[inline]
|
||||
fn get_writer_register(&self, reg: capstone::RegId) -> Aarch64Register {
|
||||
let regint: u16 = reg.0;
|
||||
Aarch64Register::from_u32(regint as u32).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[inline]
|
||||
fn emit_shadow_check(
|
||||
&self,
|
||||
_address: u64,
|
||||
output: &StalkerOutput,
|
||||
basereg: capstone::RegId,
|
||||
indexreg: capstone::RegId,
|
||||
displacement: i32,
|
||||
width: u32,
|
||||
) {
|
||||
let writer = output.writer();
|
||||
|
||||
let basereg = self.get_writer_register(basereg);
|
||||
let indexreg = if indexreg.0 != 0 {
|
||||
Some(self.get_writer_register(indexreg))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
//writer.put_brk_imm(1);
|
||||
|
||||
// Preserve x0, x1:
|
||||
writer.put_stp_reg_reg_reg_offset(
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X1,
|
||||
Aarch64Register::Sp,
|
||||
-(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64,
|
||||
IndexMode::PreAdjust,
|
||||
);
|
||||
|
||||
// Make sure the base register is copied into x0
|
||||
match basereg {
|
||||
Aarch64Register::X0 | Aarch64Register::W0 => {}
|
||||
Aarch64Register::X1 | Aarch64Register::W1 => {
|
||||
writer.put_mov_reg_reg(Aarch64Register::X0, Aarch64Register::X1);
|
||||
}
|
||||
_ => {
|
||||
if !writer.put_mov_reg_reg(Aarch64Register::X0, basereg) {
|
||||
writer.put_mov_reg_reg(Aarch64Register::W0, basereg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the index register is copied into x1
|
||||
if indexreg.is_some() {
|
||||
if let Some(indexreg) = indexreg {
|
||||
match indexreg {
|
||||
Aarch64Register::X0 | Aarch64Register::W0 => {
|
||||
writer.put_ldr_reg_reg_offset(
|
||||
Aarch64Register::X1,
|
||||
Aarch64Register::Sp,
|
||||
0u64,
|
||||
);
|
||||
}
|
||||
Aarch64Register::X1 | Aarch64Register::W1 => {}
|
||||
_ => {
|
||||
if !writer.put_mov_reg_reg(Aarch64Register::X1, indexreg) {
|
||||
writer.put_mov_reg_reg(Aarch64Register::W1, indexreg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.put_add_reg_reg_reg(
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X1,
|
||||
);
|
||||
}
|
||||
|
||||
let displacement = displacement
|
||||
+ if basereg == Aarch64Register::Sp {
|
||||
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
#[allow(clippy::comparison_chain)]
|
||||
if displacement < 0 {
|
||||
if displacement > -4096 {
|
||||
// Subtract the displacement into x0
|
||||
writer.put_sub_reg_reg_imm(
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X0,
|
||||
displacement.abs() as u64,
|
||||
);
|
||||
} else {
|
||||
let displacement_hi = displacement.abs() / 4096;
|
||||
let displacement_lo = displacement.abs() % 4096;
|
||||
writer.put_bytes(&(0xd1400000u32 | ((displacement_hi as u32) << 10)).to_le_bytes());
|
||||
writer.put_sub_reg_reg_imm(
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X0,
|
||||
displacement_lo as u64,
|
||||
);
|
||||
}
|
||||
} else if displacement > 0 {
|
||||
if displacement < 4096 {
|
||||
// Add the displacement into x0
|
||||
writer.put_add_reg_reg_imm(
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X0,
|
||||
displacement as u64,
|
||||
);
|
||||
} else {
|
||||
let displacement_hi = displacement / 4096;
|
||||
let displacement_lo = displacement % 4096;
|
||||
writer.put_bytes(&(0x91400000u32 | ((displacement_hi as u32) << 10)).to_le_bytes());
|
||||
writer.put_add_reg_reg_imm(
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X0,
|
||||
displacement_lo as u64,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Insert the check_shadow_mem code blob
|
||||
match width {
|
||||
1 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_byte()),
|
||||
2 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_halfword()),
|
||||
3 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_3bytes()),
|
||||
4 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_dword()),
|
||||
6 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_6bytes()),
|
||||
8 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_qword()),
|
||||
12 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_12bytes()),
|
||||
16 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_16bytes()),
|
||||
24 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_24bytes()),
|
||||
32 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_32bytes()),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Restore x0, x1
|
||||
assert!(writer.put_ldp_reg_reg_reg_offset(
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::X1,
|
||||
Aarch64Register::Sp,
|
||||
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64,
|
||||
IndexMode::PostAdjust,
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[inline]
|
||||
fn get_instruction_width(&self, instr: &Insn, operands: &Vec<arch::ArchOperand>) -> u32 {
|
||||
use capstone::arch::arm64::Arm64Insn as I;
|
||||
use capstone::arch::arm64::Arm64Reg as R;
|
||||
use capstone::arch::arm64::Arm64Vas as V;
|
||||
|
||||
let num_registers = match instr.id().0.into() {
|
||||
I::ARM64_INS_STP
|
||||
| I::ARM64_INS_STXP
|
||||
| I::ARM64_INS_STNP
|
||||
| I::ARM64_INS_STLXP
|
||||
| I::ARM64_INS_LDP
|
||||
| I::ARM64_INS_LDXP
|
||||
| I::ARM64_INS_LDNP => 2,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
let mnemonic = instr.mnemonic().unwrap();
|
||||
match mnemonic.as_bytes().last().unwrap() {
|
||||
b'b' => return 1,
|
||||
b'h' => return 2,
|
||||
b'w' => return 4 * num_registers,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Arm64Operand(operand) = operands.first().unwrap() {
|
||||
if operand.vas != V::ARM64_VAS_INVALID {
|
||||
let count_byte: u32 = if mnemonic.starts_with("st") || mnemonic.starts_with("ld") {
|
||||
mnemonic.chars().nth(2).unwrap().to_digit(10).unwrap()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
return match operand.vas {
|
||||
V::ARM64_VAS_1B => 1 * count_byte,
|
||||
V::ARM64_VAS_1H => 2 * count_byte,
|
||||
V::ARM64_VAS_4B | V::ARM64_VAS_1S | V::ARM64_VAS_1D | V::ARM64_VAS_2H => {
|
||||
4 * count_byte
|
||||
}
|
||||
V::ARM64_VAS_8B
|
||||
| V::ARM64_VAS_4H
|
||||
| V::ARM64_VAS_2S
|
||||
| V::ARM64_VAS_2D
|
||||
| V::ARM64_VAS_1Q => 8 * count_byte,
|
||||
V::ARM64_VAS_8H | V::ARM64_VAS_4S | V::ARM64_VAS_16B => 16 * count_byte,
|
||||
V::ARM64_VAS_INVALID => {
|
||||
panic!("should not be reached");
|
||||
}
|
||||
};
|
||||
} else if let Arm64OperandType::Reg(operand) = operand.op_type {
|
||||
match operand.0 as u32 {
|
||||
R::ARM64_REG_W0..=R::ARM64_REG_W30
|
||||
| R::ARM64_REG_WZR
|
||||
| R::ARM64_REG_WSP
|
||||
| R::ARM64_REG_S0..=R::ARM64_REG_S31 => return 4 * num_registers,
|
||||
R::ARM64_REG_D0..=R::ARM64_REG_D31 => return 8 * num_registers,
|
||||
R::ARM64_REG_Q0..=R::ARM64_REG_Q31 => return 16,
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
8 * num_registers
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[inline]
|
||||
fn is_interesting_instruction(
|
||||
&self,
|
||||
_address: u64,
|
||||
instr: &Insn,
|
||||
) -> Result<(capstone::RegId, capstone::RegId, i32, u32), ()> {
|
||||
// We have to ignore these instructions. Simulating them with their side effects is
|
||||
// complex, to say the least.
|
||||
match instr.mnemonic().unwrap() {
|
||||
"ldaxr" | "stlxr" | "ldxr" | "stxr" | "ldar" | "stlr" | "ldarb" | "ldarh" | "ldaxp"
|
||||
| "ldaxrb" | "ldaxrh" | "stlrb" | "stlrh" | "stlxp" | "stlxrb" | "stlxrh" | "ldxrb"
|
||||
| "ldxrh" | "stxrb" | "stxrh" => return Err(()),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let operands = self
|
||||
.capstone
|
||||
.insn_detail(instr)
|
||||
.unwrap()
|
||||
.arch_detail()
|
||||
.operands();
|
||||
if operands.len() < 2 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if let Arm64Operand(arm64operand) = operands.last().unwrap() {
|
||||
if let Arm64OperandType::Mem(opmem) = arm64operand.op_type {
|
||||
return Ok((
|
||||
opmem.base(),
|
||||
opmem.index(),
|
||||
opmem.disp(),
|
||||
self.get_instruction_width(instr, &operands),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_coverage_mapping(&mut self, address: u64, output: &StalkerOutput) {
|
||||
let writer = output.writer();
|
||||
if self.current_log_impl == 0
|
||||
|| !writer.can_branch_directly_to(self.current_log_impl)
|
||||
|| !writer.can_branch_directly_between(writer.pc() + 128, self.current_log_impl)
|
||||
{
|
||||
let after_log_impl = writer.code_offset() + 1;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
writer.put_jmp_near_label(after_log_impl);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
writer.put_b_label(after_log_impl);
|
||||
|
||||
self.current_log_impl = writer.pc();
|
||||
writer.put_bytes(&MAYBE_LOG_CODE);
|
||||
let prev_loc_pointer = self.previous_pc.as_ptr() as usize;
|
||||
let map_pointer = self.map.as_ptr() as usize;
|
||||
|
||||
writer.put_bytes(&map_pointer.to_ne_bytes());
|
||||
writer.put_bytes(&prev_loc_pointer.to_ne_bytes());
|
||||
|
||||
writer.put_label(after_log_impl);
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
println!("here");
|
||||
writer.put_lea_reg_reg_offset(
|
||||
X86Register::Rsp,
|
||||
X86Register::Rsp,
|
||||
-(frida_gum_sys::GUM_RED_ZONE_SIZE as i32),
|
||||
);
|
||||
writer.put_push_reg(X86Register::Rdi);
|
||||
writer.put_mov_reg_address(
|
||||
X86Register::Rdi,
|
||||
((address >> 4) ^ (address << 8)) & (MAP_SIZE - 1) as u64,
|
||||
);
|
||||
writer.put_call_address(self.current_log_impl);
|
||||
writer.put_pop_reg(X86Register::Rdi);
|
||||
writer.put_lea_reg_reg_offset(
|
||||
X86Register::Rsp,
|
||||
X86Register::Rsp,
|
||||
frida_gum_sys::GUM_RED_ZONE_SIZE as i32,
|
||||
);
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
writer.put_stp_reg_reg_reg_offset(
|
||||
Aarch64Register::Lr,
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::Sp,
|
||||
-(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64,
|
||||
IndexMode::PreAdjust,
|
||||
);
|
||||
writer.put_ldr_reg_u64(
|
||||
Aarch64Register::X0,
|
||||
((address >> 4) ^ (address << 8)) & (MAP_SIZE - 1) as u64,
|
||||
);
|
||||
writer.put_bl_imm(self.current_log_impl);
|
||||
writer.put_ldp_reg_reg_reg_offset(
|
||||
Aarch64Register::Lr,
|
||||
Aarch64Register::X0,
|
||||
Aarch64Register::Sp,
|
||||
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64,
|
||||
IndexMode::PostAdjust,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
122
libafl_frida/src/lib.rs
Normal file
122
libafl_frida/src/lib.rs
Normal file
@ -0,0 +1,122 @@
|
||||
pub mod asan_rt;
|
||||
pub mod helper;
|
||||
|
||||
/// A representation of the various Frida options
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FridaOptions {
|
||||
enable_asan: bool,
|
||||
enable_asan_leak_detection: bool,
|
||||
enable_asan_continue_after_error: bool,
|
||||
enable_asan_allocation_backtraces: bool,
|
||||
enable_coverage: bool,
|
||||
enable_drcov: bool,
|
||||
}
|
||||
|
||||
impl FridaOptions {
|
||||
/// Parse the frida options from the LIBAFL_FRIDA_OPTIONS environment variable.
|
||||
///
|
||||
/// Options are ':' separated, and each options is a 'name=value' string.
|
||||
pub fn parse_env_options() -> Self {
|
||||
let mut options = Self::default();
|
||||
|
||||
if let Ok(env_options) = std::env::var("LIBAFL_FRIDA_OPTIONS") {
|
||||
for option in env_options.trim().to_lowercase().split(':') {
|
||||
let (name, mut value) =
|
||||
option.split_at(option.find('=').expect("Expected a '=' in option string"));
|
||||
value = value.get(1..).unwrap();
|
||||
match name {
|
||||
"asan" => {
|
||||
options.enable_asan = value.parse().unwrap();
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
if options.enable_asan {
|
||||
panic!("ASAN is not currently supported on targets other than aarch64");
|
||||
}
|
||||
}
|
||||
"asan-detect-leaks" => {
|
||||
options.enable_asan_leak_detection = value.parse().unwrap();
|
||||
}
|
||||
"asan-continue-after-error" => {
|
||||
options.enable_asan_continue_after_error = value.parse().unwrap();
|
||||
}
|
||||
"asan-allocation-backtraces" => {
|
||||
options.enable_asan_allocation_backtraces = value.parse().unwrap();
|
||||
}
|
||||
"coverage" => {
|
||||
options.enable_coverage = value.parse().unwrap();
|
||||
}
|
||||
"drcov" => {
|
||||
options.enable_drcov = value.parse().unwrap();
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
if options.enable_drcov {
|
||||
panic!(
|
||||
"DrCov is not currently supported on targets other than aarch64"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("unknown FRIDA option: '{}'", option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Is ASAN enabled?
|
||||
#[inline]
|
||||
pub fn asan_enabled(&self) -> bool {
|
||||
self.enable_asan
|
||||
}
|
||||
|
||||
/// Is coverage enabled?
|
||||
#[inline]
|
||||
pub fn coverage_enabled(&self) -> bool {
|
||||
self.enable_coverage
|
||||
}
|
||||
|
||||
/// Is DrCov enabled?
|
||||
#[inline]
|
||||
pub fn drcov_enabled(&self) -> bool {
|
||||
self.enable_drcov
|
||||
}
|
||||
|
||||
/// Should ASAN detect leaks
|
||||
#[inline]
|
||||
pub fn asan_detect_leaks(&self) -> bool {
|
||||
self.enable_asan_leak_detection
|
||||
}
|
||||
|
||||
/// Should ASAN continue after a memory error is detected
|
||||
#[inline]
|
||||
pub fn asan_continue_after_error(&self) -> bool {
|
||||
self.enable_asan_continue_after_error
|
||||
}
|
||||
|
||||
/// Should ASAN gather (and report) allocation-/free-site backtraces
|
||||
#[inline]
|
||||
pub fn asan_allocation_backtraces(&self) -> bool {
|
||||
self.enable_asan_allocation_backtraces
|
||||
}
|
||||
|
||||
/// Whether stalker should be enabled. I.e. whether at least one stalker requiring option is
|
||||
/// enabled.
|
||||
#[inline]
|
||||
pub fn stalker_enabled(&self) -> bool {
|
||||
self.enable_asan || self.enable_coverage || self.enable_drcov
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FridaOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable_asan: false,
|
||||
enable_asan_leak_detection: false,
|
||||
enable_asan_continue_after_error: false,
|
||||
enable_asan_allocation_backtraces: true,
|
||||
enable_coverage: true,
|
||||
enable_drcov: false,
|
||||
}
|
||||
}
|
||||
}
|
@ -22,3 +22,4 @@ pcguard = ["pcguard_hitcounts"]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
|
||||
[dependencies]
|
||||
rangemap = "0.1.10"
|
||||
|
90
libafl_targets/src/drcov.rs
Normal file
90
libafl_targets/src/drcov.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use rangemap::RangeMap;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct DrCovBasicBlock {
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
pub struct DrCovWriter<'a> {
|
||||
writer: BufWriter<File>,
|
||||
module_mapping: &'a RangeMap<usize, (u16, &'a str)>,
|
||||
basic_blocks: &'a mut Vec<DrCovBasicBlock>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct DrCovBasicBlockEntry {
|
||||
start: u32,
|
||||
size: u16,
|
||||
mod_id: u16,
|
||||
}
|
||||
|
||||
impl DrCovBasicBlock {
|
||||
pub fn new(start: usize, end: usize) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
}
|
||||
impl<'a> DrCovWriter<'a> {
|
||||
pub fn new(
|
||||
path: &str,
|
||||
module_mapping: &'a RangeMap<usize, (u16, &str)>,
|
||||
basic_blocks: &'a mut Vec<DrCovBasicBlock>,
|
||||
) -> Self {
|
||||
Self {
|
||||
writer: BufWriter::new(
|
||||
File::create(path).expect("unable to create file for coverage data"),
|
||||
),
|
||||
module_mapping,
|
||||
basic_blocks,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self) {
|
||||
self.writer
|
||||
.write_all(b"DRCOV VERSION: 2\nDRCOV FLAVOR: libafl\n")
|
||||
.unwrap();
|
||||
|
||||
let modules: Vec<(&std::ops::Range<usize>, &(u16, &str))> =
|
||||
self.module_mapping.iter().collect();
|
||||
self.writer
|
||||
.write_all(format!("Module Table: version 2, count {}\n", modules.len()).as_bytes())
|
||||
.unwrap();
|
||||
self.writer
|
||||
.write_all(b"Columns: id, base, end, entry, checksum, timestamp, path\n")
|
||||
.unwrap();
|
||||
for module in modules {
|
||||
let (range, (id, path)) = module;
|
||||
self.writer
|
||||
.write_all(
|
||||
format!(
|
||||
"{:03}, 0x{:x}, 0x{:x}, 0x00000000, 0x00000000, 0x00000000, {}\n",
|
||||
id, range.start, range.end, path
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
self.writer
|
||||
.write_all(format!("BB Table: {} bbs\n", self.basic_blocks.len()).as_bytes())
|
||||
.unwrap();
|
||||
for block in self.basic_blocks.drain(0..) {
|
||||
let (range, (id, _)) = self.module_mapping.get_key_value(&block.start).unwrap();
|
||||
let basic_block = DrCovBasicBlockEntry {
|
||||
start: (block.start - range.start) as u32,
|
||||
size: (block.end - block.start) as u16,
|
||||
mod_id: *id,
|
||||
};
|
||||
self.writer
|
||||
.write_all(unsafe {
|
||||
std::slice::from_raw_parts(&basic_block as *const _ as *const u8, 8)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
self.writer.flush().unwrap();
|
||||
}
|
||||
}
|
@ -19,3 +19,5 @@ pub use libfuzzer::*;
|
||||
pub mod cmplog;
|
||||
#[cfg(feature = "cmplog")]
|
||||
pub use cmplog::*;
|
||||
|
||||
pub mod drcov;
|
||||
|
Loading…
x
Reference in New Issue
Block a user