From 16c3a07be73294d95c3077bbecdac697f46efa80 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Thu, 5 Aug 2021 17:08:01 +0200 Subject: [PATCH] ShMem Server for MacOS (#238) * generalized ashmem server * fixed macos testcases * added StdShMemService * no_st * fmt * added testcase, fixed some bugs (not all) * solidified unix shmem * initial impl for MmapShMem * Added shmem service start to more testcases * clippy * fixed tetcases * added frida_libpng makefile for easy use * trying to fix build on ubuntu * fixed ubuntu build for libpng * no_std * fixed testcase --- fuzzers/baby_no_std/src/main.rs | 2 +- fuzzers/forkserver_simple/src/main.rs | 5 +- fuzzers/frida_libpng/.gitignore | 4 +- fuzzers/frida_libpng/Makefile | 39 +++ fuzzers/frida_libpng/corpus/not_kitty.png | Bin 0 -> 218 bytes .../frida_libpng/corpus/not_kitty_alpha.png | Bin 0 -> 376 bytes .../frida_libpng/corpus/not_kitty_gamma.png | Bin 0 -> 228 bytes fuzzers/frida_libpng/corpus/not_kitty_icc.png | Bin 0 -> 427 bytes fuzzers/frida_libpng/src/fuzzer.rs | 21 +- fuzzers/fuzzbench/src/lib.rs | 5 +- fuzzers/fuzzbench_qemu/src/fuzzer.rs | 2 +- fuzzers/generic_inmemory/src/lib.rs | 5 +- fuzzers/libfuzzer_libpng_launcher/src/lib.rs | 5 +- libafl/Cargo.toml | 1 + libafl/examples/llmp_test/main.rs | 5 + libafl/src/bolts/llmp.rs | 8 +- libafl/src/bolts/os/mod.rs | 2 +- ...{ashmem_server.rs => unix_shmem_server.rs} | 286 +++++++++++----- libafl/src/bolts/shmem.rs | 319 ++++++++++++++++-- libafl/src/bolts/staterestore.rs | 10 +- libafl/src/events/llmp.rs | 14 +- libafl/src/executors/command.rs | 1 + libafl/src/executors/forkserver.rs | 7 +- libafl/src/executors/mod.rs | 2 + libafl_frida/src/helper.rs | 5 + libafl_sugar/src/inmemory.rs | 2 +- libafl_sugar/src/qemu.rs | 2 +- scripts/shmem_limits_macos.sh | 2 +- 28 files changed, 615 insertions(+), 139 deletions(-) create mode 100644 fuzzers/frida_libpng/Makefile create mode 100644 fuzzers/frida_libpng/corpus/not_kitty.png create mode 100644 fuzzers/frida_libpng/corpus/not_kitty_alpha.png create mode 100644 fuzzers/frida_libpng/corpus/not_kitty_gamma.png create mode 100644 fuzzers/frida_libpng/corpus/not_kitty_icc.png rename libafl/src/bolts/os/{ashmem_server.rs => unix_shmem_server.rs} (64%) diff --git a/fuzzers/baby_no_std/src/main.rs b/fuzzers/baby_no_std/src/main.rs index 0a56698164..b705ad76fe 100644 --- a/fuzzers/baby_no_std/src/main.rs +++ b/fuzzers/baby_no_std/src/main.rs @@ -97,7 +97,7 @@ pub fn main() { #[cfg(any(windows, unix))] unsafe { printf( - [b'%' as c_char, b's' as c_char, b'\n' as c_char, 0 as c_char].as_ptr(), + b"%s\n\0".as_ptr() as *const c_char, CString::new(s).unwrap().as_ptr() as *const c_char, ); } diff --git a/fuzzers/forkserver_simple/src/main.rs b/fuzzers/forkserver_simple/src/main.rs index efa40c6d47..791e1077d2 100644 --- a/fuzzers/forkserver_simple/src/main.rs +++ b/fuzzers/forkserver_simple/src/main.rs @@ -3,7 +3,7 @@ use libafl::{ bolts::{ current_nanos, rands::StdRand, - shmem::{ShMem, ShMemProvider, StdShMemProvider}, + shmem::{ShMem, ShMemProvider, StdShMemProvider, StdShMemService}, tuples::tuple_list, }, corpus::{ @@ -29,6 +29,9 @@ pub fn main() { let corpus_dirs = vec![PathBuf::from("./corpus")]; const MAP_SIZE: usize = 65536; + + let _service = StdShMemService::start().unwrap(); + //Coverage map shared between observer and executor let mut shmem = StdShMemProvider::new().unwrap().new_map(MAP_SIZE).unwrap(); //let the forkserver know the shmid diff --git a/fuzzers/frida_libpng/.gitignore b/fuzzers/frida_libpng/.gitignore index a977a2ca5b..e052527149 100644 --- a/fuzzers/frida_libpng/.gitignore +++ b/fuzzers/frida_libpng/.gitignore @@ -1 +1,3 @@ -libpng-* \ No newline at end of file +libpng-* +corpus_discovered +libafl_frida \ No newline at end of file diff --git a/fuzzers/frida_libpng/Makefile b/fuzzers/frida_libpng/Makefile new file mode 100644 index 0000000000..70bf0d289f --- /dev/null +++ b/fuzzers/frida_libpng/Makefile @@ -0,0 +1,39 @@ +FUZZER_NAME="libafl_frida" +PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +PHONY: all + +all: libafl_frida libpng-harness.so + +libpng-1.6.37: + wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz + tar -xvf libpng-1.6.37.tar.xz + +target/release/frida_libpng: src/* + # Build the frida libpng libfuzzer fuzzer + cargo build --release + +libpng-1.6.37/.libs/libpng16.a: libpng-1.6.37 + cd libpng-1.6.37 && ./configure --enable-hardware-optimizations=yes --with-pic=yes + $(MAKE) -C libpng-1.6.37 + +libpng-harness.so: libpng-1.6.37/.libs/libpng16.a + $(CXX) -O3 -c -fPIC harness.cc -o harness.o + $(CXX) -O3 harness.o libpng-1.6.37/.libs/libpng16.a -shared -lz -o libpng-harness.so + +libafl_frida: target/release/frida_libpng + cp target/release/frida_libpng libafl_frida + +clean: + $(MAKE) -C libpng-1.6.37 clean + rm $(FUZZER_NAME) + +run: all + ./$(FUZZER_NAME) ./libpng-harness.so LLVMFuzzerTestOneInput ./libpng-harness.so --cores=0 + +short_test: all + # We allow exit code 124 too, which is sigterm + (timeout 3s ./libafl_frida ./libpng-harness.so LLVMFuzzerTestOneInput ./libpng-harness.so --cores=0,1 || [ $$? -eq 124 ]) + +test: all + timeout 60s ./$(FUZZER_NAME) ./libpng-harness.so LLVMFuzzerTestOneInput ./libpng-harness.so --cores=0,1 \ No newline at end of file diff --git a/fuzzers/frida_libpng/corpus/not_kitty.png b/fuzzers/frida_libpng/corpus/not_kitty.png new file mode 100644 index 0000000000000000000000000000000000000000..eff7c1707b936a8f8df725814f604d454b78b5c3 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X_yc@GT+_~+`TzevkY_wIZRYx+5&y#hyq+?%!C8<`)MX5lF!N|bSRM)^r*U&J;z}U*bz{;0L z1Vuw`eoAIqC5i?kD`P_|6GMoGiCWXn12ss3YzWRzD=AMbN@Z|N$xljE@XSq2PYp^< WOsOn9nQ8-6#Ng@b=d#Wzp$PyV*n0l} literal 0 HcmV?d00001 diff --git a/fuzzers/frida_libpng/corpus/not_kitty_gamma.png b/fuzzers/frida_libpng/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000000000000000000000000000000000..939d9d29a9b9f95bac5e9a72854361ee85469921 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmTQ929t;oCfmw1AIbU z)6Sgv|NlRbXFM})=KnKxKI=t+9LW;bh?3y^w370~qErUQl>DSr1<%~X^wgl##FWay zlc_d9MbVxvjv*GO?@o5)YH;9THa`3B|5>?^8?LvjJ}xLe>!7e@k)r^sLedir0mCVe z=5sMjEm$*~tHD+}{NS_$nMdb|ABqg-@UGMMsZ=uY-X%Cq@&3vmZ%&@H{P?6&+U!yq VvuXWlo?M_c44$rjF6*2UngF4cP+$N6 literal 0 HcmV?d00001 diff --git a/fuzzers/frida_libpng/corpus/not_kitty_icc.png b/fuzzers/frida_libpng/corpus/not_kitty_icc.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c7804d99829cc6307c1c6ae9915cf42d555414 GIT binary patch literal 427 zcmV;c0aX5pP)9xSWu9|B*4Isn^#g47m^r~thH)GiR<@yX0fO)OF<2Kt#qCldyUF#H?{4jV?XGw9)psxE&K1B1m^ z1_tH{2(hG@3=G>_85ksPA;eS`Ffj19FfeR8pIlm01~rBeWCZ{dbvfq;rA3DT000kA zOjJc?%*_A){{R30GnreSaefwW^{L9a%BKPWN%_+AW3auXJt}l zVPtu6$z?nM003J_L_t(I%iWVf3V=Wi12fJ3|IHp$*hSlV@t||fKp?cDK@bHXV&o_g zF_hw;3ILUGteXmeJsVfSmcVJno)^MdQwU3bFHCtNG)uY>mLcD%`0UBaIq~Fq8#dBr V12uok3~c}a002ovPDHLkV1nKBo!S5Z literal 0 HcmV?d00001 diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index e9b6e9ee85..4d85e38a88 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -3,8 +3,8 @@ use clap::{App, Arg}; -#[cfg(target_os = "android")] -use libafl::bolts::os::ashmem_server::AshmemService; +#[cfg(all(cfg = "std", unix))] +use libafl::bolts::os::unix_shmem_server::ShMemService; use libafl::{ bolts::{ @@ -12,7 +12,7 @@ use libafl::{ launcher::Launcher, os::parse_core_bind_arg, rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, tuples::{tuple_list, Merge}, }, corpus::{ @@ -233,7 +233,7 @@ pub fn main() { .map(|addrstr| addrstr.parse().unwrap()); unsafe { - fuzz( + match fuzz( matches.value_of("harness").unwrap(), matches.value_of("symbol").unwrap(), &matches @@ -252,8 +252,10 @@ pub fn main() { .value_of("configuration") .unwrap_or("default launcher") .to_string(), - ) - .expect("An error occurred while fuzzing"); + ) { + Ok(()) | Err(Error::ShuttingDown) => println!("Finished fuzzing. Good bye."), + Err(e) => panic!("Error during fuzzing: {:?}", e), + } } } @@ -292,8 +294,7 @@ unsafe fn fuzz( // 'While the stats are state, they are usually used in the broker - which is likely never restarted let stats = MultiStats::new(|s| println!("{}", s)); - #[cfg(target_os = "android")] - AshmemService::start().expect("Failed to start Ashmem service"); + let _service = StdShMemService::start().expect("Failed to start ShMem service"); let shmem_provider = StdShMemProvider::new()?; let mut run_client = |state: Option>, mut mgr| { @@ -317,7 +318,7 @@ unsafe fn fuzz( &gum, &frida_options, module_name, - &modules_to_instrument, + modules_to_instrument, ); // Create an observation channel using the coverage map @@ -411,7 +412,7 @@ unsafe fn fuzz( // In case the corpus is empty (on first run), reset if state.corpus().count() < 1 { state - .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs) + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, corpus_dirs) .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs)); println!("We imported {} inputs from disk.", state.corpus().count()); } diff --git a/fuzzers/fuzzbench/src/lib.rs b/fuzzers/fuzzbench/src/lib.rs index adbe850330..8383258ee0 100644 --- a/fuzzers/fuzzbench/src/lib.rs +++ b/fuzzers/fuzzbench/src/lib.rs @@ -19,7 +19,7 @@ use libafl::{ current_nanos, current_time, os::dup2, rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, tuples::{tuple_list, Merge}, }, corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler}, @@ -181,8 +181,7 @@ fn fuzz( // We need a shared map to store our state before a crash. // This way, we are able to continue fuzzing afterwards. - #[cfg(target_os = "android")] - AshmemService::start().expect("Failed to start Ashmem service"); + let _service = StdShMemService::start().expect("Failed to start ShMem service"); let mut shmem_provider = StdShMemProvider::new()?; let (state, mut mgr) = match SimpleRestartingEventManager::launch(stats, &mut shmem_provider) { diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs index 72c7e1ceb3..be692e01ca 100644 --- a/fuzzers/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_qemu/src/fuzzer.rs @@ -19,7 +19,7 @@ use libafl::{ current_nanos, current_time, os::dup2, rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, tuples::{tuple_list, Merge}, }, corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler}, diff --git a/fuzzers/generic_inmemory/src/lib.rs b/fuzzers/generic_inmemory/src/lib.rs index 91f9d2253d..30a65806be 100644 --- a/fuzzers/generic_inmemory/src/lib.rs +++ b/fuzzers/generic_inmemory/src/lib.rs @@ -11,7 +11,7 @@ use libafl::{ launcher::Launcher, os::parse_core_bind_arg, rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, tuples::{tuple_list, Merge}, }, corpus::{ @@ -78,8 +78,7 @@ pub fn libafl_main() { println!("Workdir: {:?}", workdir.to_string_lossy().to_string()); - #[cfg(target_os = "android")] - AshmemService::start().expect("Failed to start Ashmem service"); + let _service = StdShMemService::start().expect("Failed to start ShMem service"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let stats = MultiStats::new(|s| println!("{}", s)); diff --git a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs index 5af992b26a..596412fad3 100644 --- a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs +++ b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs @@ -13,7 +13,7 @@ use libafl::{ launcher::Launcher, os::parse_core_bind_arg, rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, tuples::{tuple_list, Merge}, }, corpus::{ @@ -54,8 +54,7 @@ pub fn libafl_main() { env::current_dir().unwrap().to_string_lossy().to_string() ); - #[cfg(target_os = "android")] - AshmemService::start().expect("Failed to start Ashmem service"); + let _service = StdShMemService::start().expect("Failed to start ShMem service"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let stats = MultiStats::new(|s| println!("{}", s)); diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index a7d33c97c8..8ceb645cd8 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -21,6 +21,7 @@ fxhash = "0.2.1" # yet another hash xxhash-rust = { version = "0.8.2", features = ["xxh3"] } # xxh3 hashing for rust serde_json = "1.0.60" num_cpus = "1.0" # cpu count, for llmp example +serial_test = "0.5" [[bench]] name = "rand_speeds" diff --git a/libafl/examples/llmp_test/main.rs b/libafl/examples/llmp_test/main.rs index 43a73ea375..72abd69128 100644 --- a/libafl/examples/llmp_test/main.rs +++ b/libafl/examples/llmp_test/main.rs @@ -117,6 +117,8 @@ fn main() { #[cfg(unix)] fn main() { + use libafl::bolts::shmem::StdShMemService; + /* The main node has a broker, and a few worker threads */ let mode = std::env::args() @@ -137,6 +139,9 @@ fn main() { match mode.as_str() { "broker" => { + // The shmem service is needed on some platforms like Android and MacOS + let _service = StdShMemService::start().unwrap(); + let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap(); broker.launch_tcp_listener_on(port).unwrap(); broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5))) diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index d1f1f73520..5420690240 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -2650,6 +2650,8 @@ mod tests { use std::{thread::sleep, time::Duration}; + use serial_test::serial; + use super::{ LlmpClient, LlmpConnection::{self, IsBroker, IsClient}, @@ -2657,10 +2659,14 @@ mod tests { Tag, }; - use crate::bolts::shmem::{ShMemProvider, StdShMemProvider}; + use crate::bolts::shmem::{ShMemProvider, StdShMemProvider, StdShMemService}; #[test] + #[serial] pub fn llmp_connection() { + #[allow(unused_variables)] + let service = StdShMemService::start().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"), diff --git a/libafl/src/bolts/os/mod.rs b/libafl/src/bolts/os/mod.rs index 8cf8842277..a26e0175bd 100644 --- a/libafl/src/bolts/os/mod.rs +++ b/libafl/src/bolts/os/mod.rs @@ -7,7 +7,7 @@ use crate::Error; use std::{env, process::Command}; #[cfg(all(unix, feature = "std"))] -pub mod ashmem_server; +pub mod unix_shmem_server; #[cfg(unix)] pub mod unix_signals; diff --git a/libafl/src/bolts/os/ashmem_server.rs b/libafl/src/bolts/os/unix_shmem_server.rs similarity index 64% rename from libafl/src/bolts/os/ashmem_server.rs rename to libafl/src/bolts/os/unix_shmem_server.rs index acb2f6c28d..3a6d458183 100644 --- a/libafl/src/bolts/os/ashmem_server.rs +++ b/libafl/src/bolts/os/unix_shmem_server.rs @@ -1,14 +1,12 @@ /*! -On Android, we can only share maps between processes by serializing fds over sockets. -Hence, the `ashmem_server` keeps track of existing maps, creates new maps for clients, +On `Android`, we can only share maps between processes by serializing fds over sockets. +On `MacOS`, we cannot rely on reference counting for Maps. +Hence, the [`unix_shmem_server`] keeps track of existing maps, creates new maps for clients, and forwards them over unix domain sockets. */ use crate::{ - bolts::shmem::{ - unix_shmem::ashmem::{AshmemShMem, AshmemShMemProvider}, - ShMem, ShMemDescription, ShMemId, ShMemProvider, - }, + bolts::shmem::{ShMem, ShMemDescription, ShMemId, ShMemProvider}, Error, }; use core::mem::ManuallyDrop; @@ -16,9 +14,12 @@ use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::{ cell::RefCell, + fs, io::{Read, Write}, + marker::PhantomData, rc::{Rc, Weak}, sync::{Arc, Condvar, Mutex}, + thread::JoinHandle, }; #[cfg(all(feature = "std", unix))] @@ -36,25 +37,36 @@ use std::{ #[cfg(all(unix, feature = "std"))] use uds::{UnixListenerExt, UnixSocketAddr, UnixStreamExt}; -const ASHMEM_SERVER_NAME: &str = "@ashmem_server"; +/// The default server name for our abstract shmem server +#[cfg(all(unix, not(any(target_os = "ios", target_os = "macos"))))] +const UNIX_SERVER_NAME: &str = "@libafl_unix_shmem_server"; +/// `MacOS` server name is on disk, since `MacOS` doesn't support abtract domain sockets. +#[cfg(any(target_os = "ios", target_os = "macos"))] +const UNIX_SERVER_NAME: &str = "./libafl_unix_shmem_server"; /// Hands out served shared maps, as used on Android. #[derive(Debug)] -pub struct ServedShMemProvider { +pub struct ServedShMemProvider { stream: UnixStream, - inner: AshmemShMemProvider, + inner: SP, id: i32, } /// [`ShMem`] that got served from a [`AshmemService`] via domain sockets and can now be used in this program. /// It works around Android's lack of "proper" shared maps. #[derive(Clone, Debug)] -pub struct ServedShMem { - inner: ManuallyDrop, +pub struct ServedShMem +where + SH: ShMem, +{ + inner: ManuallyDrop, server_fd: i32, } -impl ShMem for ServedShMem { +impl ShMem for ServedShMem +where + SH: ShMem, +{ fn id(&self) -> ShMemId { let client_id = self.inner.id(); ShMemId::from_string(&format!("{}:{}", self.server_fd, client_id.to_string())) @@ -73,10 +85,10 @@ impl ShMem for ServedShMem { } } -impl ServedShMemProvider { +impl ServedShMemProvider { /// Send a request to the server, and wait for a response #[allow(clippy::similar_names)] // id and fd - fn send_receive(&mut self, request: AshmemRequest) -> Result<(i32, i32), Error> { + fn send_receive(&mut self, request: ServedShMemRequest) -> Result<(i32, i32), Error> { let body = postcard::to_allocvec(&request)?; let header = (body.len() as u32).to_be_bytes(); @@ -99,34 +111,43 @@ impl ServedShMemProvider { } } -impl Default for ServedShMemProvider { +impl Default for ServedShMemProvider +where + SP: ShMemProvider, +{ fn default() -> Self { Self::new().unwrap() } } -impl Clone for ServedShMemProvider { +impl Clone for ServedShMemProvider +where + SP: ShMemProvider, +{ fn clone(&self) -> Self { Self::new().unwrap() } } -impl ShMemProvider for ServedShMemProvider { - type Mem = ServedShMem; +impl ShMemProvider for ServedShMemProvider +where + SP: ShMemProvider, +{ + type Mem = ServedShMem; /// Connect to the server and return a new [`ServedShMemProvider`] fn new() -> Result { let mut res = Self { - stream: UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME)?)?, - inner: AshmemShMemProvider::new()?, + stream: UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(UNIX_SERVER_NAME)?)?, + inner: SP::new()?, id: -1, }; - let (id, _) = res.send_receive(AshmemRequest::Hello(None))?; + let (id, _) = res.send_receive(ServedShMemRequest::Hello(None))?; res.id = id; Ok(res) } fn new_map(&mut self, map_size: usize) -> Result { - let (server_fd, client_fd) = self.send_receive(AshmemRequest::NewMap(map_size))?; + let (server_fd, client_fd) = self.send_receive(ServedShMemRequest::NewMap(map_size))?; Ok(ServedShMem { inner: ManuallyDrop::new( @@ -140,7 +161,7 @@ impl ShMemProvider for ServedShMemProvider { fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result { let parts = id.as_str().split(':').collect::>(); let server_id_str = parts.get(0).unwrap(); - let (server_fd, client_fd) = self.send_receive(AshmemRequest::ExistingMap( + let (server_fd, client_fd) = self.send_receive(ServedShMemRequest::ExistingMap( ShMemDescription::from_string_and_size(server_id_str, size), ))?; Ok(ServedShMem { @@ -156,8 +177,8 @@ impl ShMemProvider for ServedShMemProvider { if is_child { // After fork, the child needs to reconnect as to not share the fds with the parent. self.stream = - UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME)?)?; - let (id, _) = self.send_receive(AshmemRequest::Hello(Some(self.id)))?; + UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(UNIX_SERVER_NAME)?)?; + let (id, _) = self.send_receive(ServedShMemRequest::Hello(Some(self.id)))?; self.id = id; } Ok(()) @@ -165,8 +186,8 @@ impl ShMemProvider for ServedShMemProvider { fn release_map(&mut self, map: &mut Self::Mem) { let (refcount, _) = self - .send_receive(AshmemRequest::Deregister(map.server_fd)) - .expect("Could not communicate with AshMem server!"); + .send_receive(ServedShMemRequest::Deregister(map.server_fd)) + .expect("Could not communicate with ServedShMem server!"); if refcount == 1 { unsafe { ManuallyDrop::drop(&mut map.inner); @@ -177,7 +198,7 @@ impl ShMemProvider for ServedShMemProvider { /// A request sent to the [`ShMem`] server to receive a fd to a shared map #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub enum AshmemRequest { +pub enum ServedShMemRequest { /// Register a new map with a given size. NewMap(usize), /// Another client already has a map with this description mapped. @@ -187,15 +208,23 @@ pub enum AshmemRequest { /// A message that tells us hello, and optionally which other client we were created from, we /// return a client id. Hello(Option), + /// The ShMem Service should exit. This is sually sent internally on `drop`, but feel free to do whatever with it? + Exit, } #[derive(Debug)] -struct AshmemClient { +struct SharedShMemClient +where + SH: ShMem, +{ stream: UnixStream, - maps: HashMap>>>, + maps: HashMap>>>, } -impl AshmemClient { +impl SharedShMemClient +where + SH: ShMem, +{ fn new(stream: UnixStream) -> Self { Self { stream, @@ -207,37 +236,134 @@ impl AshmemClient { /// The [`AshmemService`] is a service handing out [`ShMem`] pages via unix domain sockets. /// It is mainly used and needed on Android. #[derive(Debug)] -pub struct AshmemService { - provider: AshmemShMemProvider, - clients: HashMap, - all_maps: HashMap>>, +pub struct ShMemService +where + SP: ShMemProvider, +{ + join_handle: Option>>, + phantom: PhantomData<*const SP>, } #[derive(Debug)] -enum AshmemResponse { - Mapping(Rc>), +enum ServedShMemResponse +where + SP: ShMemProvider, +{ + Mapping(Rc>), Id(i32), RefCount(u32), } -impl AshmemService { - /// Create a new [`AshMem`] service +impl ShMemService +where + SP: ShMemProvider, +{ + /// Create a new [`ShMemService`], then listen and service incoming connections in a new thread. + pub fn start() -> Result { + println!("Starting ShMemService"); + + #[allow(clippy::mutex_atomic)] + let syncpair = Arc::new((Mutex::new(false), Condvar::new())); + let childsyncpair = Arc::clone(&syncpair); + let join_handle = thread::spawn(move || { + println!("Thread..."); + + let mut worker = match ServedShMemServiceWorker::::new() { + Ok(worker) => worker, + Err(e) => { + // Make sure the parent processes can continue + let (lock, cvar) = &*childsyncpair; + *lock.lock().unwrap() = true; + cvar.notify_one(); + + println!("Error creating ShMemService: {:?}", e); + return Err(e); + } + }; + if let Err(e) = worker.listen(UNIX_SERVER_NAME, &childsyncpair) { + println!("Error spawning ShMemService: {:?}", e); + Err(e) + } else { + Ok(()) + } + }); + + let (lock, cvar) = &*syncpair; + let mut started = lock.lock().unwrap(); + while !*started { + started = cvar.wait(started).unwrap(); + } + + Ok(Self { + join_handle: Some(join_handle), + phantom: PhantomData, + }) + } +} + +impl Drop for ShMemService +where + SP: ShMemProvider, +{ + fn drop(&mut self) { + let join_handle = self.join_handle.take(); + // TODO: Guess we could use the `cvar` // Mutex here instead? + if let Some(join_handle) = join_handle { + let mut stream = match UnixStream::connect_to_unix_addr( + &UnixSocketAddr::new(UNIX_SERVER_NAME).unwrap(), + ) { + Ok(stream) => stream, + Err(_) => return, // ignoring non-started server + }; + + let body = postcard::to_allocvec(&ServedShMemRequest::Exit).unwrap(); + + let header = (body.len() as u32).to_be_bytes(); + let mut message = header.to_vec(); + message.extend(body); + + stream + .write_all(&message) + .expect("Failed to send bye-message to ShMemService"); + join_handle + .join() + .expect("Failed to join ShMemService thread!") + .expect("Error in ShMemService thread!"); + } + } +} + +/// The struct for the worker, handling incoming requests for [`ShMem`]. +struct ServedShMemServiceWorker +where + SP: ShMemProvider, +{ + provider: SP, + clients: HashMap>, + all_maps: HashMap>>, +} + +impl ServedShMemServiceWorker +where + SP: ShMemProvider, +{ + /// Create a new [`ShMemService`] fn new() -> Result { - Ok(AshmemService { - provider: AshmemShMemProvider::new()?, + Ok(Self { + provider: SP::new()?, clients: HashMap::new(), all_maps: HashMap::new(), }) } /// Read and handle the client request, send the answer over unix fd. - fn handle_request(&mut self, client_id: RawFd) -> Result { + fn handle_request(&mut self, client_id: RawFd) -> Result, 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) => { + ServedShMemRequest::Hello(other_id) => { if let Some(other_id) = other_id { if other_id != client_id { // remove temporarily @@ -249,21 +375,21 @@ impl AshmemService { self.clients.insert(other_id, other_client.unwrap()); } }; - Ok(AshmemResponse::Id(client_id)) + Ok(ServedShMemResponse::Id(client_id)) } - AshmemRequest::NewMap(map_size) => { + ServedShMemRequest::NewMap(map_size) => { let new_map = self.provider.new_map(map_size)?; let description = new_map.description(); let new_rc = Rc::new(RefCell::new(new_map)); self.all_maps .insert(description.id.into(), Rc::downgrade(&new_rc)); - Ok(AshmemResponse::Mapping(new_rc)) + Ok(ServedShMemResponse::Mapping(new_rc)) } - AshmemRequest::ExistingMap(description) => { + ServedShMemRequest::ExistingMap(description) => { let client = self.clients.get_mut(&client_id).unwrap(); let description_id: i32 = description.id.into(); if client.maps.contains_key(&description_id) { - Ok(AshmemResponse::Mapping( + Ok(ServedShMemResponse::Mapping( client .maps .get_mut(&description_id) @@ -275,7 +401,7 @@ impl AshmemService { .clone(), )) } else { - Ok(AshmemResponse::Mapping( + Ok(ServedShMemResponse::Mapping( self.all_maps .get_mut(&description_id) .unwrap() @@ -285,24 +411,29 @@ impl AshmemService { )) } } - AshmemRequest::Deregister(map_id) => { + ServedShMemRequest::Deregister(map_id) => { let client = self.clients.get_mut(&client_id).unwrap(); let maps = client.maps.entry(map_id).or_default(); if maps.is_empty() { - Ok(AshmemResponse::RefCount(0u32)) + Ok(ServedShMemResponse::RefCount(0u32)) } else { - Ok(AshmemResponse::RefCount( + Ok(ServedShMemResponse::RefCount( Rc::strong_count(&maps.pop().unwrap()) as u32, )) } } + ServedShMemRequest::Exit => { + println!("ShMemService - Exiting"); + // stopping the server + return Err(Error::ShuttingDown); + } }; //println!("send ashmem client: {}, response: {:?}", client_id, &response); response } - fn read_request(&mut self, client_id: RawFd) -> Result { + fn read_request(&mut self, client_id: RawFd) -> Result { let client = self.clients.get_mut(&client_id).unwrap(); // Always receive one be u32 of size, then the command. @@ -315,7 +446,7 @@ impl AshmemService { .stream .read_exact(&mut bytes) .expect("Failed to read message body"); - let request: AshmemRequest = postcard::from_bytes(&bytes)?; + let request: ServedShMemRequest = postcard::from_bytes(&bytes)?; Ok(request) } @@ -323,7 +454,7 @@ impl AshmemService { let response = self.handle_request(client_id)?; match response { - AshmemResponse::Mapping(mapping) => { + ServedShMemResponse::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(); @@ -332,11 +463,11 @@ impl AshmemService { .send_fds(id.to_string().as_bytes(), &[server_fd])?; client.maps.entry(server_fd).or_default().push(mapping); } - AshmemResponse::Id(id) => { + ServedShMemResponse::Id(id) => { let client = self.clients.get_mut(&client_id).unwrap(); client.stream.send_fds(id.to_string().as_bytes(), &[])?; } - AshmemResponse::RefCount(refcount) => { + ServedShMemResponse::RefCount(refcount) => { let client = self.clients.get_mut(&client_id).unwrap(); client .stream @@ -346,23 +477,6 @@ impl AshmemService { Ok(()) } - /// Create a new [`AshmemService`], then listen and service incoming connections in a new thread. - pub fn start() -> Result>, Error> { - #[allow(clippy::mutex_atomic)] - 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)); - - let (lock, cvar) = &*syncpair; - let mut started = lock.lock().unwrap(); - while !*started { - started = cvar.wait(started).unwrap(); - } - - Ok(join_handle) - } - /// Listen on a filename (or abstract name) for new connections and serve them. This function /// should not return. fn listen( @@ -378,10 +492,13 @@ impl AshmemService { let (lock, cvar) = &**syncpair; *lock.lock().unwrap() = true; cvar.notify_one(); + + println!("Error in ShMem Worker"); return Err(Error::Unknown( "The server appears to already be running. We are probably a client".to_string(), )); }; + let mut poll_fds: Vec = vec![PollFd::new( listener.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND, @@ -418,7 +535,7 @@ impl AshmemService { } }; } else { - let (stream, addr) = match listener.accept_unix_addr() { + let (stream, _addr) = match listener.accept_unix_addr() { Ok(stream_val) => stream_val, Err(e) => { println!("Error accepting client: {:?}", e); @@ -426,17 +543,21 @@ impl AshmemService { } }; - println!("Recieved connection from {:?}", addr); + // println!("Recieved connection from {:?}", addr); let pollfd = PollFd::new( stream.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND, ); poll_fds.push(pollfd); - let client = AshmemClient::new(stream); + let client = SharedShMemClient::new(stream); let client_id = client.stream.as_raw_fd(); self.clients.insert(client_id, client); match self.handle_client(client_id) { Ok(()) => (), + Err(Error::ShuttingDown) => { + println!("Shutting down"); + return Ok(()); + } Err(e) => { dbg!("Ignoring failed read from client", e); } @@ -449,3 +570,14 @@ impl AshmemService { } } } + +impl Drop for ServedShMemServiceWorker +where + SP: ShMemProvider, +{ + fn drop(&mut self) { + // try to remove the file from fs, and ignore errors. + #[cfg(target_os = "macos")] + drop(fs::remove_file(&UNIX_SERVER_NAME)); + } +} diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index 647c5bf947..a65cb60f45 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -7,38 +7,58 @@ use core::{ fmt::{self, Debug, Display}, mem::ManuallyDrop, }; +#[cfg(all(feature = "std", unix, not(target_os = "android")))] +pub use unix_shmem::{MmapShMem, MmapShMemProvider}; #[cfg(all(feature = "std", unix))] pub use unix_shmem::{UnixShMem, UnixShMemProvider}; -/// The default [`ShMemProvider`] for this os. + +use crate::Error; + #[cfg(all(feature = "std", unix))] -pub type OsShMemProvider = UnixShMemProvider; -/// The default [`ShMem`] for this os. -#[cfg(all(feature = "std", unix))] -pub type OsShMem = UnixShMem; +pub use crate::bolts::os::unix_shmem_server::{ServedShMemProvider, ShMemService}; #[cfg(all(windows, feature = "std"))] pub use win32_shmem::{Win32ShMem, Win32ShMemProvider}; #[cfg(all(windows, feature = "std"))] -pub type OsShMemProvider = Win32ShMemProvider; +pub type StdShMemProvider = Win32ShMemProvider; #[cfg(all(windows, feature = "std"))] -pub type OsShMem = Win32ShMem; - -use crate::Error; +pub type StdShMem = Win32ShMem; #[cfg(all(target_os = "android", feature = "std"))] -use crate::bolts::os::ashmem_server::ServedShMemProvider; +pub type StdShMemProvider = RcShMemProvider>; #[cfg(all(target_os = "android", feature = "std"))] -pub type StdShMemProvider = RcShMemProvider; +pub type StdShMem = RcShMem>; #[cfg(all(target_os = "android", feature = "std"))] -pub type StdShMem = RcShMem; +pub type StdShMemService = ShMemService; + +#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))] +pub type StdShMemProvider = RcShMemProvider>; +#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))] +pub type StdShMem = RcShMem>; +#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))] +pub type StdShMemService = ShMemService; /// The default [`ShMemProvider`] for this os. -#[cfg(all(feature = "std", not(target_os = "android")))] -pub type StdShMemProvider = OsShMemProvider; -/// The default [`ShMem`] for this os. -#[cfg(all(feature = "std", not(target_os = "android")))] -pub type StdShMem = OsShMem; +#[cfg(all( + feature = "std", + unix, + not(any(target_os = "android", target_os = "ios", target_os = "macos")) +))] +pub type StdShMemProvider = UnixShMemProvider; +/// The default [`ShMemProvider`] for this os. +#[cfg(all( + feature = "std", + unix, + not(any(target_os = "android", target_os = "ios", target_os = "macos")) +))] +pub type StdShMem = UnixShMem; + +#[cfg(any( + not(any(target_os = "android", target_os = "macos", target_os = "ios")), + not(feature = "std") +))] +pub type StdShMemService = DummyShMemService; use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] @@ -47,6 +67,12 @@ use std::{ env, }; +#[cfg(all( + unix, + feature = "std", + any(target_os = "ios", target_os = "macos", target_os = "android") +))] +use super::os::unix_shmem_server::ServedShMem; #[cfg(all(unix, feature = "std"))] use crate::bolts::os::pipes::Pipe; #[cfg(all(unix, feature = "std"))] @@ -436,14 +462,13 @@ where /// A Unix sharedmem implementation. /// /// On Android, this is partially reused to wrap [`unix_shmem::ashmem::AshmemShMem`], -/// Although for an [`unix_shmem::ashmem::AshmemShMemProvider`] using a unix domain socket +/// Although for an [`unix_shmem::ashmem::ServedShMemProvider`] using a unix domain socket /// Is needed on top. #[cfg(all(unix, feature = "std"))] pub mod unix_shmem { - /// Shared memory provider for Android, allocating and forwarding maps over unix domain sockets. #[cfg(target_os = "android")] - pub type UnixShMemProvider = ashmem::AshmemShMemProvider; + pub type UnixShMemProvider = ashmem::ServedShMemProvider; /// Shared memory for Android #[cfg(target_os = "android")] pub type UnixShMem = ashmem::AshmemShMem; @@ -454,10 +479,22 @@ pub mod unix_shmem { #[cfg(not(target_os = "android"))] pub type UnixShMem = ashmem::AshmemShMem; + /// Mmap [`ShMem`] for Unix + #[cfg(not(target_os = "android"))] + pub use default::MmapShMem; + /// Mmap [`ShMemProvider`] for Unix + #[cfg(not(target_os = "android"))] + pub use default::MmapShMemProvider; + #[cfg(all(unix, feature = "std", not(target_os = "android")))] mod default { - use core::{ptr, slice}; - use libc::{c_int, c_long, c_uchar, c_uint, c_ulong, c_ushort, c_void}; + + use core::{convert::TryInto, ptr, slice}; + use libc::{ + c_int, c_long, c_uchar, c_uint, c_ulong, c_ushort, c_void, close, ftruncate, mmap, + munmap, perror, shm_open, shm_unlink, + }; + use std::{io::Write, process, ptr::null_mut}; use crate::{ bolts::shmem::{ShMem, ShMemId, ShMemProvider}, @@ -502,6 +539,197 @@ pub mod unix_shmem { fn shmat(__shmid: c_int, __shmaddr: *const c_void, __shmflg: c_int) -> *mut c_void; } + const MAX_MMAP_FILENAME_LEN: usize = 256; + + /// Mmap-based The sharedmap impl for unix using [`shm_open`] and [`mmap`]. + /// Default on `MacOS` and `iOS`, where we need a central point to unmap + /// shared mem segments for dubious Mach kernel reasons. + #[derive(Clone, Debug)] + pub struct MmapShMem { + /// The path of this shared memory segment. + /// None in case we didn't [`shm_open`] this ourselves, but someone sent us the FD. + filename_path: Option<[u8; MAX_MMAP_FILENAME_LEN]>, + /// The size of this map + map_size: usize, + /// The map ptr + map: *mut u8, + /// The shmem id, containing the file descriptor and size, to send over the wire + id: ShMemId, + /// The file descriptor of the shmem + shm_fd: c_int, + } + + impl MmapShMem { + pub fn new(map_size: usize, shmem_ctr: usize) -> Result { + unsafe { + let mut filename_path = [0_u8; MAX_MMAP_FILENAME_LEN]; + write!( + &mut filename_path[..MAX_MMAP_FILENAME_LEN - 1], + "/libafl_{}_{}", + process::id(), + shmem_ctr + )?; + + /* create the shared memory segment as if it was a file */ + let shm_fd = shm_open( + filename_path.as_ptr() as *const _, + libc::O_CREAT | libc::O_RDWR | libc::O_EXCL, + 0o600, + ); + if shm_fd == -1 { + perror(b"shm_open\0".as_ptr() as *const _); + return Err(Error::Unknown(format!( + "Failed to shm_open map with id {:?}", + shmem_ctr + ))); + } + + /* configure the size of the shared memory segment */ + if ftruncate(shm_fd, map_size.try_into()?) != 0 { + perror(b"ftruncate\0".as_ptr() as *const _); + shm_unlink(filename_path.as_ptr() as *const _); + return Err(Error::Unknown(format!( + "setup_shm(): ftruncate() failed for map with id {:?}", + shmem_ctr + ))); + } + + /* map the shared memory segment to the address space of the process */ + let map = mmap( + null_mut(), + map_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED, + shm_fd, + 0, + ); + if map == libc::MAP_FAILED || map.is_null() { + perror(b"mmap\0".as_ptr() as *const _); + close(shm_fd); + shm_unlink(filename_path.as_ptr() as *const _); + return Err(Error::Unknown(format!( + "mmap() failed for map with id {:?}", + shmem_ctr + ))); + } + + Ok(Self { + filename_path: Some(filename_path), + map: map as *mut u8, + map_size, + shm_fd, + id: ShMemId::from_string(&format!("{}", shm_fd)), + }) + } + } + + fn from_id_and_size(id: ShMemId, map_size: usize) -> Result { + unsafe { + let shm_fd: i32 = id.to_string().parse().unwrap(); + + /* map the shared memory segment to the address space of the process */ + let map = mmap( + null_mut(), + map_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED, + shm_fd, + 0, + ); + if map == libc::MAP_FAILED || map.is_null() { + perror(b"mmap\0".as_ptr() as *const _); + close(shm_fd); + return Err(Error::Unknown(format!( + "mmap() failed for map with fd {:?}", + shm_fd + ))); + } + + Ok(Self { + filename_path: None, + map: map as *mut u8, + map_size, + shm_fd, + id: ShMemId::from_string(&format!("{}", shm_fd)), + }) + } + } + } + + /// A [`ShMemProvider`] which uses `shmget`/`shmat`/`shmctl` to provide shared memory mappings. + #[cfg(unix)] + #[derive(Clone, Debug)] + pub struct MmapShMemProvider { + current_map_id: usize, + } + + unsafe impl Send for MmapShMemProvider {} + + #[cfg(unix)] + impl Default for MmapShMemProvider { + fn default() -> Self { + Self::new().unwrap() + } + } + + /// Implement [`ShMemProvider`] for [`UnixShMemProvider`]. + #[cfg(unix)] + impl ShMemProvider for MmapShMemProvider { + type Mem = MmapShMem; + + fn new() -> Result { + Ok(Self { current_map_id: 0 }) + } + fn new_map(&mut self, map_size: usize) -> Result { + self.current_map_id += 1; + MmapShMem::new(map_size, self.current_map_id) + } + + fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result { + MmapShMem::from_id_and_size(id, size) + } + } + + impl ShMem for MmapShMem { + fn id(&self) -> ShMemId { + self.id + } + + fn len(&self) -> usize { + self.map_size + } + + fn map(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.map, self.map_size) } + } + + fn map_mut(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.map, self.map_size) } + } + } + + impl Drop for MmapShMem { + fn drop(&mut self) { + unsafe { + if self.map.is_null() { + panic!("Map should never be null for MmapShMem (on Drop)"); + } + + munmap(self.map as *mut _, self.map_size); + self.map = ptr::null_mut(); + + if self.shm_fd == -1 { + panic!("FD should never be -1 for MmapShMem (on Drop)"); + } + + // None in case we didn't [`shm_open`] this ourselves, but someone sent us the FD. + if let Some(filename_path) = self.filename_path { + shm_unlink(filename_path.as_ptr() as *const _); + } + } + } + } + /// The default sharedmap impl for unix using shmctl & shmget #[derive(Clone, Debug)] pub struct CommonUnixShMem { @@ -514,7 +742,11 @@ pub mod unix_shmem { /// Create a new shared memory mapping, using shmget/shmat pub fn new(map_size: usize) -> Result { unsafe { - let os_id = shmget(0, map_size as c_ulong, 0o1000 | 0o2000 | 0o600); + let os_id = shmget( + libc::IPC_PRIVATE, + map_size as c_ulong, + libc::IPC_CREAT | libc::IPC_EXCL | libc::SHM_R | libc::SHM_W, + ); if os_id < 0_i32 { return Err(Error::Unknown(format!("Failed to allocate a shared mapping of size {} - check OS limits (i.e shmall, shmmax)", map_size))); @@ -523,7 +755,7 @@ pub mod unix_shmem { let map = shmat(os_id, ptr::null(), 0) as *mut c_uchar; if map as c_int == -1 || map.is_null() { - shmctl(os_id, 0, ptr::null_mut()); + shmctl(os_id, libc::IPC_RMID, ptr::null_mut()); return Err(Error::Unknown( "Failed to map the shared mapping".to_string(), )); @@ -543,7 +775,7 @@ pub mod unix_shmem { let id_int: i32 = id.into(); let map = shmat(id_int, ptr::null(), 0) as *mut c_uchar; - if map == usize::MAX as *mut c_void as *mut c_uchar || map.is_null() { + if map.is_null() || map == null_mut::().wrapping_sub(1) { return Err(Error::Unknown( "Failed to map the shared mapping".to_string(), )); @@ -579,7 +811,7 @@ pub mod unix_shmem { fn drop(&mut self) { unsafe { let id_int: i32 = self.id.into(); - shmctl(id_int, 0, ptr::null_mut()); + shmctl(id_int, libc::IPC_RMID, ptr::null_mut()); } } } @@ -999,12 +1231,28 @@ pub mod win32_shmem { } } +/// A `ShMemService` dummy, that does nothing on start. +/// Drop in for targets that don't need a server for ref counting and page creation. +#[derive(Debug)] +pub struct DummyShMemService; + +impl DummyShMemService { + /// Create a new [`DummyShMemService`] that does nothing. + /// Useful only to have the same API for [`StdShMemService`] on Operating Systems that don't need it. + #[inline] + pub fn start() -> Result { + Ok(Self {}) + } +} + +#[cfg(feature = "std")] /// A cursor around [`ShMem`] that immitates [`std::io::Cursor`]. Notably, this implements [`Write`] for [`ShMem`] in std environments. pub struct ShMemCursor { inner: T, pos: usize, } +#[cfg(feature = "std")] impl ShMemCursor { pub fn new(shmem: T) -> Self { Self { @@ -1083,3 +1331,22 @@ impl std::io::Seek for ShMemCursor { Ok(effective_new_pos) } } + +#[cfg(feature = "std")] +#[cfg(test)] +mod tests { + use serial_test::serial; + + use crate::bolts::shmem::{ShMem, ShMemProvider, StdShMemProvider, StdShMemService}; + + #[test] + #[serial] + fn test_shmem_service() { + #[allow(unused_variables)] + let service = StdShMemService::start().unwrap(); + let mut provider = StdShMemProvider::new().unwrap(); + let mut map = provider.new_map(1024).unwrap(); + map.map_mut()[0] = 1; + assert!(map.map()[0] == 1); + } +} diff --git a/libafl/src/bolts/staterestore.rs b/libafl/src/bolts/staterestore.rs index 10562f7ca2..966f9624b8 100644 --- a/libafl/src/bolts/staterestore.rs +++ b/libafl/src/bolts/staterestore.rs @@ -111,7 +111,8 @@ where if size_of::() + serialized.len() > self.shmem.len() { // generate a filename let mut hasher = AHasher::new_with_keys(0, 0); - hasher.write(&serialized[serialized.len() - 1024..]); + // Using the last few k as randomness for a filename, hoping it's unique. + hasher.write(&serialized[serialized.len().saturating_sub(4096)..]); let filename = format!("{:016x}.libafl_state", hasher.finish()); let tmpfile = temp_dir().join(&filename); @@ -238,15 +239,20 @@ where #[cfg(test)] mod tests { + use serial_test::serial; + use crate::bolts::{ - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, staterestore::StateRestorer, }; #[test] + #[serial] fn test_state_restore() { const TESTMAP_SIZE: usize = 1024; + let _service = StdShMemService::start().unwrap(); + let mut shmem_provider = StdShMemProvider::new().unwrap(); let shmem = shmem_provider.new_map(TESTMAP_SIZE).unwrap(); let mut state_restorer = StateRestorer::::new(shmem); diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 3bff17678b..e6a804b80a 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -15,7 +15,7 @@ use std::net::{SocketAddr, ToSocketAddrs}; #[cfg(feature = "std")] use crate::bolts::{ llmp::{LlmpClient, LlmpConnection}, - shmem::StdShMemProvider, + shmem::{StdShMemProvider, StdShMemService}, staterestore::StateRestorer, }; @@ -49,7 +49,7 @@ use crate::bolts::os::startable_self; use crate::bolts::os::{fork, ForkResult}; #[cfg(all(target_os = "android", feature = "std"))] -use crate::bolts::os::ashmem_server::AshmemService; +use crate::bolts::os::unix_shmem_server::ShMemService; #[cfg(feature = "std")] use typed_builder::TypedBuilder; @@ -689,8 +689,7 @@ where OT: ObserversTuple + serde::de::DeserializeOwned, S: DeserializeOwned, { - #[cfg(target_os = "android")] - AshmemService::start().expect("Error starting Ashmem Service"); + let _service = StdShMemService::start().expect("Error starting ShMem Service"); RestartingMgr::builder() .shmem_provider(StdShMemProvider::new()?) @@ -929,11 +928,13 @@ where #[cfg(test)] #[cfg(feature = "std")] mod tests { + use serial_test::serial; + use crate::{ bolts::{ llmp::{LlmpClient, LlmpSharedMap}, rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, staterestore::StateRestorer, tuples::tuple_list, }, @@ -949,7 +950,10 @@ mod tests { use core::sync::atomic::{compiler_fence, Ordering}; #[test] + #[serial] fn test_mgr_state_restore() { + let _service = StdShMemService::start().unwrap(); + let rand = StdRand::with_seed(0); let mut corpus = InMemoryCorpus::::new(); diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index c9316df3ed..41bc3e944d 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -3,6 +3,7 @@ use core::marker::PhantomData; #[cfg(feature = "std")] use std::{process::Child, time::Duration}; +#[cfg(feature = "std")] use crate::{ executors::{Executor, ExitKind, HasObservers}, inputs::Input, diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 13460509c3..1d249bae76 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -614,9 +614,11 @@ where #[cfg(test)] mod tests { + use serial_test::serial; + use crate::{ bolts::{ - shmem::{ShMem, ShMemProvider, StdShMemProvider}, + shmem::{ShMem, ShMemProvider, StdShMemProvider, StdShMemService}, tuples::tuple_list, }, executors::ForkserverExecutor, @@ -625,11 +627,14 @@ mod tests { Error, }; #[test] + #[serial] fn test_forkserver() { const MAP_SIZE: usize = 65536; let bin = "echo"; let args = vec![String::from("@@")]; + let _service = StdShMemService::start().unwrap(); + let mut shmem = StdShMemProvider::new() .unwrap() .new_map(MAP_SIZE as usize) diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index fc7d965230..f3162a3f7d 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -19,7 +19,9 @@ pub use shadow::ShadowExecutor; pub mod with_observers; pub use with_observers::WithObservers; +#[cfg(feature = "std")] pub mod command; +#[cfg(feature = "std")] pub use command::CommandExecutor; use crate::{ diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index c3f2625e85..23dce4e5b0 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -107,7 +107,12 @@ impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> { self.asan_runtime.register_thread(); } + #[cfg(not(target_arch = "aarch64"))] + fn pre_exec(&mut self, _input: &I) {} + + #[cfg(target_arch = "aarch64")] fn pre_exec(&mut self, input: &I) { + #[cfg(target_arch = "aarch64")] let target_bytes = input.target_bytes(); let slice = target_bytes.as_slice(); //println!("target_bytes: {:#x}: {:02x?}", slice.as_ptr() as usize, slice); diff --git a/libafl_sugar/src/inmemory.rs b/libafl_sugar/src/inmemory.rs index a8f55cc3c2..09d80a927f 100644 --- a/libafl_sugar/src/inmemory.rs +++ b/libafl_sugar/src/inmemory.rs @@ -71,7 +71,7 @@ impl<'a, H> InMemoryBytesCoverageSugar<'a, H> where H: FnMut(&[u8]), { - #[allow(clippy::too_many_lines)] + #[allow(clippy::too_many_lines, clippy::similar_names)] pub fn run(&mut self) { let conf = self .configuration diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index 7ecf28cad1..cb11153e51 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -7,7 +7,7 @@ use libafl::{ current_nanos, launcher::Launcher, rands::StdRand, - shmem::{ShMemProvider, StdShMemProvider}, + shmem::{ShMemProvider, StdShMemProvider, StdShMemService}, tuples::{tuple_list, Merge}, }, corpus::{ diff --git a/scripts/shmem_limits_macos.sh b/scripts/shmem_limits_macos.sh index 70cf8f46d8..f8a3b67f8b 100755 --- a/scripts/shmem_limits_macos.sh +++ b/scripts/shmem_limits_macos.sh @@ -1,7 +1,7 @@ #!/bin/sh echo "Warning: this script is not a proper fix to do LLMP fuzzing." \ - "Instead, run `afl-persistent-config` with SIP disabled." + 'Instead, run `afl-persistent-config` with SIP disabled.' sudo sysctl -w kern.sysv.shmmax=524288000 sudo sysctl -w kern.sysv.shmmin=1