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
This commit is contained in:
Dominik Maier 2021-08-05 17:08:01 +02:00 committed by GitHub
parent 704830a501
commit 16c3a07be7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 615 additions and 139 deletions

View File

@ -97,7 +97,7 @@ pub fn main() {
#[cfg(any(windows, unix))] #[cfg(any(windows, unix))]
unsafe { unsafe {
printf( 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, CString::new(s).unwrap().as_ptr() as *const c_char,
); );
} }

View File

@ -3,7 +3,7 @@ use libafl::{
bolts::{ bolts::{
current_nanos, current_nanos,
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, StdShMemProvider}, shmem::{ShMem, ShMemProvider, StdShMemProvider, StdShMemService},
tuples::tuple_list, tuples::tuple_list,
}, },
corpus::{ corpus::{
@ -29,6 +29,9 @@ pub fn main() {
let corpus_dirs = vec![PathBuf::from("./corpus")]; let corpus_dirs = vec![PathBuf::from("./corpus")];
const MAP_SIZE: usize = 65536; const MAP_SIZE: usize = 65536;
let _service = StdShMemService::start().unwrap();
//Coverage map shared between observer and executor //Coverage map shared between observer and executor
let mut shmem = StdShMemProvider::new().unwrap().new_map(MAP_SIZE).unwrap(); let mut shmem = StdShMemProvider::new().unwrap().new_map(MAP_SIZE).unwrap();
//let the forkserver know the shmid //let the forkserver know the shmid

View File

@ -1 +1,3 @@
libpng-* libpng-*
corpus_discovered
libafl_frida

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

View File

@ -3,8 +3,8 @@
use clap::{App, Arg}; use clap::{App, Arg};
#[cfg(target_os = "android")] #[cfg(all(cfg = "std", unix))]
use libafl::bolts::os::ashmem_server::AshmemService; use libafl::bolts::os::unix_shmem_server::ShMemService;
use libafl::{ use libafl::{
bolts::{ bolts::{
@ -12,7 +12,7 @@ use libafl::{
launcher::Launcher, launcher::Launcher,
os::parse_core_bind_arg, os::parse_core_bind_arg,
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
}, },
corpus::{ corpus::{
@ -233,7 +233,7 @@ pub fn main() {
.map(|addrstr| addrstr.parse().unwrap()); .map(|addrstr| addrstr.parse().unwrap());
unsafe { unsafe {
fuzz( match fuzz(
matches.value_of("harness").unwrap(), matches.value_of("harness").unwrap(),
matches.value_of("symbol").unwrap(), matches.value_of("symbol").unwrap(),
&matches &matches
@ -252,8 +252,10 @@ pub fn main() {
.value_of("configuration") .value_of("configuration")
.unwrap_or("default launcher") .unwrap_or("default launcher")
.to_string(), .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 // 'While the stats are state, they are usually used in the broker - which is likely never restarted
let stats = MultiStats::new(|s| println!("{}", s)); let stats = MultiStats::new(|s| println!("{}", s));
#[cfg(target_os = "android")] let _service = StdShMemService::start().expect("Failed to start ShMem service");
AshmemService::start().expect("Failed to start Ashmem service");
let shmem_provider = StdShMemProvider::new()?; let shmem_provider = StdShMemProvider::new()?;
let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut mgr| { let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut mgr| {
@ -317,7 +318,7 @@ unsafe fn fuzz(
&gum, &gum,
&frida_options, &frida_options,
module_name, module_name,
&modules_to_instrument, modules_to_instrument,
); );
// Create an observation channel using the coverage map // 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 // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state 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)); .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs));
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());
} }

View File

@ -19,7 +19,7 @@ use libafl::{
current_nanos, current_time, current_nanos, current_time,
os::dup2, os::dup2,
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
}, },
corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler}, corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler},
@ -181,8 +181,7 @@ fn fuzz(
// We need a shared map to store our state before a crash. // We need a shared map to store our state before a crash.
// This way, we are able to continue fuzzing afterwards. // This way, we are able to continue fuzzing afterwards.
#[cfg(target_os = "android")] let _service = StdShMemService::start().expect("Failed to start ShMem service");
AshmemService::start().expect("Failed to start Ashmem service");
let mut shmem_provider = StdShMemProvider::new()?; let mut shmem_provider = StdShMemProvider::new()?;
let (state, mut mgr) = match SimpleRestartingEventManager::launch(stats, &mut shmem_provider) { let (state, mut mgr) = match SimpleRestartingEventManager::launch(stats, &mut shmem_provider) {

View File

@ -19,7 +19,7 @@ use libafl::{
current_nanos, current_time, current_nanos, current_time,
os::dup2, os::dup2,
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
}, },
corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler}, corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler},

View File

@ -11,7 +11,7 @@ use libafl::{
launcher::Launcher, launcher::Launcher,
os::parse_core_bind_arg, os::parse_core_bind_arg,
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
}, },
corpus::{ corpus::{
@ -78,8 +78,7 @@ pub fn libafl_main() {
println!("Workdir: {:?}", workdir.to_string_lossy().to_string()); println!("Workdir: {:?}", workdir.to_string_lossy().to_string());
#[cfg(target_os = "android")] let _service = StdShMemService::start().expect("Failed to start ShMem service");
AshmemService::start().expect("Failed to start Ashmem service");
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let stats = MultiStats::new(|s| println!("{}", s)); let stats = MultiStats::new(|s| println!("{}", s));

View File

@ -13,7 +13,7 @@ use libafl::{
launcher::Launcher, launcher::Launcher,
os::parse_core_bind_arg, os::parse_core_bind_arg,
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
}, },
corpus::{ corpus::{
@ -54,8 +54,7 @@ pub fn libafl_main() {
env::current_dir().unwrap().to_string_lossy().to_string() env::current_dir().unwrap().to_string_lossy().to_string()
); );
#[cfg(target_os = "android")] let _service = StdShMemService::start().expect("Failed to start ShMem service");
AshmemService::start().expect("Failed to start Ashmem service");
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let stats = MultiStats::new(|s| println!("{}", s)); let stats = MultiStats::new(|s| println!("{}", s));

View File

@ -21,6 +21,7 @@ fxhash = "0.2.1" # yet another hash
xxhash-rust = { version = "0.8.2", features = ["xxh3"] } # xxh3 hashing for rust xxhash-rust = { version = "0.8.2", features = ["xxh3"] } # xxh3 hashing for rust
serde_json = "1.0.60" serde_json = "1.0.60"
num_cpus = "1.0" # cpu count, for llmp example num_cpus = "1.0" # cpu count, for llmp example
serial_test = "0.5"
[[bench]] [[bench]]
name = "rand_speeds" name = "rand_speeds"

View File

@ -117,6 +117,8 @@ fn main() {
#[cfg(unix)] #[cfg(unix)]
fn main() { fn main() {
use libafl::bolts::shmem::StdShMemService;
/* The main node has a broker, and a few worker threads */ /* The main node has a broker, and a few worker threads */
let mode = std::env::args() let mode = std::env::args()
@ -137,6 +139,9 @@ fn main() {
match mode.as_str() { match mode.as_str() {
"broker" => { "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(); let mut broker = llmp::LlmpBroker::new(StdShMemProvider::new().unwrap()).unwrap();
broker.launch_tcp_listener_on(port).unwrap(); broker.launch_tcp_listener_on(port).unwrap();
broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5))) broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5)))

View File

@ -2650,6 +2650,8 @@ mod tests {
use std::{thread::sleep, time::Duration}; use std::{thread::sleep, time::Duration};
use serial_test::serial;
use super::{ use super::{
LlmpClient, LlmpClient,
LlmpConnection::{self, IsBroker, IsClient}, LlmpConnection::{self, IsBroker, IsClient},
@ -2657,10 +2659,14 @@ mod tests {
Tag, Tag,
}; };
use crate::bolts::shmem::{ShMemProvider, StdShMemProvider}; use crate::bolts::shmem::{ShMemProvider, StdShMemProvider, StdShMemService};
#[test] #[test]
#[serial]
pub fn llmp_connection() { pub fn llmp_connection() {
#[allow(unused_variables)]
let service = StdShMemService::start().unwrap();
let shmem_provider = StdShMemProvider::new().unwrap(); let shmem_provider = StdShMemProvider::new().unwrap();
let mut broker = match LlmpConnection::on_port(shmem_provider.clone(), 1337).unwrap() { let mut broker = match LlmpConnection::on_port(shmem_provider.clone(), 1337).unwrap() {
IsClient { client: _ } => panic!("Could not bind to port as broker"), IsClient { client: _ } => panic!("Could not bind to port as broker"),

View File

@ -7,7 +7,7 @@ use crate::Error;
use std::{env, process::Command}; use std::{env, process::Command};
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std"))]
pub mod ashmem_server; pub mod unix_shmem_server;
#[cfg(unix)] #[cfg(unix)]
pub mod unix_signals; pub mod unix_signals;

View File

@ -1,14 +1,12 @@
/*! /*!
On Android, we can only share maps between processes by serializing fds over sockets. 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 `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. and forwards them over unix domain sockets.
*/ */
use crate::{ use crate::{
bolts::shmem::{ bolts::shmem::{ShMem, ShMemDescription, ShMemId, ShMemProvider},
unix_shmem::ashmem::{AshmemShMem, AshmemShMemProvider},
ShMem, ShMemDescription, ShMemId, ShMemProvider,
},
Error, Error,
}; };
use core::mem::ManuallyDrop; use core::mem::ManuallyDrop;
@ -16,9 +14,12 @@ use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
cell::RefCell, cell::RefCell,
fs,
io::{Read, Write}, io::{Read, Write},
marker::PhantomData,
rc::{Rc, Weak}, rc::{Rc, Weak},
sync::{Arc, Condvar, Mutex}, sync::{Arc, Condvar, Mutex},
thread::JoinHandle,
}; };
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
@ -36,25 +37,36 @@ use std::{
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std"))]
use uds::{UnixListenerExt, UnixSocketAddr, UnixStreamExt}; 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. /// Hands out served shared maps, as used on Android.
#[derive(Debug)] #[derive(Debug)]
pub struct ServedShMemProvider { pub struct ServedShMemProvider<SP> {
stream: UnixStream, stream: UnixStream,
inner: AshmemShMemProvider, inner: SP,
id: i32, id: i32,
} }
/// [`ShMem`] that got served from a [`AshmemService`] via domain sockets and can now be used in this program. /// [`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. /// It works around Android's lack of "proper" shared maps.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ServedShMem { pub struct ServedShMem<SH>
inner: ManuallyDrop<AshmemShMem>, where
SH: ShMem,
{
inner: ManuallyDrop<SH>,
server_fd: i32, server_fd: i32,
} }
impl ShMem for ServedShMem { impl<SH> ShMem for ServedShMem<SH>
where
SH: ShMem,
{
fn id(&self) -> ShMemId { fn id(&self) -> ShMemId {
let client_id = self.inner.id(); let client_id = self.inner.id();
ShMemId::from_string(&format!("{}:{}", self.server_fd, client_id.to_string())) ShMemId::from_string(&format!("{}:{}", self.server_fd, client_id.to_string()))
@ -73,10 +85,10 @@ impl ShMem for ServedShMem {
} }
} }
impl ServedShMemProvider { impl<SP> ServedShMemProvider<SP> {
/// Send a request to the server, and wait for a response /// Send a request to the server, and wait for a response
#[allow(clippy::similar_names)] // id and fd #[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 body = postcard::to_allocvec(&request)?;
let header = (body.len() as u32).to_be_bytes(); let header = (body.len() as u32).to_be_bytes();
@ -99,34 +111,43 @@ impl ServedShMemProvider {
} }
} }
impl Default for ServedShMemProvider { impl<SP> Default for ServedShMemProvider<SP>
where
SP: ShMemProvider,
{
fn default() -> Self { fn default() -> Self {
Self::new().unwrap() Self::new().unwrap()
} }
} }
impl Clone for ServedShMemProvider { impl<SP> Clone for ServedShMemProvider<SP>
where
SP: ShMemProvider,
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self::new().unwrap() Self::new().unwrap()
} }
} }
impl ShMemProvider for ServedShMemProvider { impl<SP> ShMemProvider for ServedShMemProvider<SP>
type Mem = ServedShMem; where
SP: ShMemProvider,
{
type Mem = ServedShMem<SP::Mem>;
/// Connect to the server and return a new [`ServedShMemProvider`] /// Connect to the server and return a new [`ServedShMemProvider`]
fn new() -> Result<Self, Error> { fn new() -> Result<Self, Error> {
let mut res = Self { let mut res = Self {
stream: UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME)?)?, stream: UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(UNIX_SERVER_NAME)?)?,
inner: AshmemShMemProvider::new()?, inner: SP::new()?,
id: -1, id: -1,
}; };
let (id, _) = res.send_receive(AshmemRequest::Hello(None))?; let (id, _) = res.send_receive(ServedShMemRequest::Hello(None))?;
res.id = id; res.id = id;
Ok(res) Ok(res)
} }
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, crate::Error> { 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))?; let (server_fd, client_fd) = self.send_receive(ServedShMemRequest::NewMap(map_size))?;
Ok(ServedShMem { Ok(ServedShMem {
inner: ManuallyDrop::new( inner: ManuallyDrop::new(
@ -140,7 +161,7 @@ impl ShMemProvider for ServedShMemProvider {
fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result<Self::Mem, Error> { fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result<Self::Mem, Error> {
let parts = id.as_str().split(':').collect::<Vec<&str>>(); let parts = id.as_str().split(':').collect::<Vec<&str>>();
let server_id_str = parts.get(0).unwrap(); 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), ShMemDescription::from_string_and_size(server_id_str, size),
))?; ))?;
Ok(ServedShMem { Ok(ServedShMem {
@ -156,8 +177,8 @@ impl ShMemProvider for ServedShMemProvider {
if is_child { if is_child {
// After fork, the child needs to reconnect as to not share the fds with the parent. // After fork, the child needs to reconnect as to not share the fds with the parent.
self.stream = self.stream =
UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(ASHMEM_SERVER_NAME)?)?; UnixStream::connect_to_unix_addr(&UnixSocketAddr::new(UNIX_SERVER_NAME)?)?;
let (id, _) = self.send_receive(AshmemRequest::Hello(Some(self.id)))?; let (id, _) = self.send_receive(ServedShMemRequest::Hello(Some(self.id)))?;
self.id = id; self.id = id;
} }
Ok(()) Ok(())
@ -165,8 +186,8 @@ impl ShMemProvider for ServedShMemProvider {
fn release_map(&mut self, map: &mut Self::Mem) { fn release_map(&mut self, map: &mut Self::Mem) {
let (refcount, _) = self let (refcount, _) = self
.send_receive(AshmemRequest::Deregister(map.server_fd)) .send_receive(ServedShMemRequest::Deregister(map.server_fd))
.expect("Could not communicate with AshMem server!"); .expect("Could not communicate with ServedShMem server!");
if refcount == 1 { if refcount == 1 {
unsafe { unsafe {
ManuallyDrop::drop(&mut map.inner); 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 /// A request sent to the [`ShMem`] server to receive a fd to a shared map
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum AshmemRequest { pub enum ServedShMemRequest {
/// Register a new map with a given size. /// Register a new map with a given size.
NewMap(usize), NewMap(usize),
/// Another client already has a map with this description mapped. /// 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 /// A message that tells us hello, and optionally which other client we were created from, we
/// return a client id. /// return a client id.
Hello(Option<i32>), Hello(Option<i32>),
/// The ShMem Service should exit. This is sually sent internally on `drop`, but feel free to do whatever with it?
Exit,
} }
#[derive(Debug)] #[derive(Debug)]
struct AshmemClient { struct SharedShMemClient<SH>
where
SH: ShMem,
{
stream: UnixStream, stream: UnixStream,
maps: HashMap<i32, Vec<Rc<RefCell<AshmemShMem>>>>, maps: HashMap<i32, Vec<Rc<RefCell<SH>>>>,
} }
impl AshmemClient { impl<SH> SharedShMemClient<SH>
where
SH: ShMem,
{
fn new(stream: UnixStream) -> Self { fn new(stream: UnixStream) -> Self {
Self { Self {
stream, stream,
@ -207,37 +236,134 @@ impl AshmemClient {
/// The [`AshmemService`] is a service handing out [`ShMem`] pages via unix domain sockets. /// The [`AshmemService`] is a service handing out [`ShMem`] pages via unix domain sockets.
/// It is mainly used and needed on Android. /// It is mainly used and needed on Android.
#[derive(Debug)] #[derive(Debug)]
pub struct AshmemService { pub struct ShMemService<SP>
provider: AshmemShMemProvider, where
clients: HashMap<RawFd, AshmemClient>, SP: ShMemProvider,
all_maps: HashMap<i32, Weak<RefCell<AshmemShMem>>>, {
join_handle: Option<JoinHandle<Result<(), Error>>>,
phantom: PhantomData<*const SP>,
} }
#[derive(Debug)] #[derive(Debug)]
enum AshmemResponse { enum ServedShMemResponse<SP>
Mapping(Rc<RefCell<AshmemShMem>>), where
SP: ShMemProvider,
{
Mapping(Rc<RefCell<SP::Mem>>),
Id(i32), Id(i32),
RefCount(u32), RefCount(u32),
} }
impl AshmemService { impl<SP> ShMemService<SP>
/// Create a new [`AshMem`] service where
SP: ShMemProvider,
{
/// Create a new [`ShMemService`], then listen and service incoming connections in a new thread.
pub fn start() -> Result<Self, Error> {
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::<SP>::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<SP> Drop for ShMemService<SP>
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<SP>
where
SP: ShMemProvider,
{
provider: SP,
clients: HashMap<RawFd, SharedShMemClient<SP::Mem>>,
all_maps: HashMap<i32, Weak<RefCell<SP::Mem>>>,
}
impl<SP> ServedShMemServiceWorker<SP>
where
SP: ShMemProvider,
{
/// Create a new [`ShMemService`]
fn new() -> Result<Self, Error> { fn new() -> Result<Self, Error> {
Ok(AshmemService { Ok(Self {
provider: AshmemShMemProvider::new()?, provider: SP::new()?,
clients: HashMap::new(), clients: HashMap::new(),
all_maps: HashMap::new(), all_maps: HashMap::new(),
}) })
} }
/// Read and handle the client request, send the answer over unix fd. /// Read and handle the client request, send the answer over unix fd.
fn handle_request(&mut self, client_id: RawFd) -> Result<AshmemResponse, Error> { fn handle_request(&mut self, client_id: RawFd) -> Result<ServedShMemResponse<SP>, Error> {
let request = self.read_request(client_id)?; let request = self.read_request(client_id)?;
//println!("got ashmem client: {}, request:{:?}", client_id, request); //println!("got ashmem client: {}, request:{:?}", client_id, request);
// Handle the client request // Handle the client request
let response = match request { let response = match request {
AshmemRequest::Hello(other_id) => { ServedShMemRequest::Hello(other_id) => {
if let Some(other_id) = other_id { if let Some(other_id) = other_id {
if other_id != client_id { if other_id != client_id {
// remove temporarily // remove temporarily
@ -249,21 +375,21 @@ impl AshmemService {
self.clients.insert(other_id, other_client.unwrap()); 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 new_map = self.provider.new_map(map_size)?;
let description = new_map.description(); let description = new_map.description();
let new_rc = Rc::new(RefCell::new(new_map)); let new_rc = Rc::new(RefCell::new(new_map));
self.all_maps self.all_maps
.insert(description.id.into(), Rc::downgrade(&new_rc)); .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 client = self.clients.get_mut(&client_id).unwrap();
let description_id: i32 = description.id.into(); let description_id: i32 = description.id.into();
if client.maps.contains_key(&description_id) { if client.maps.contains_key(&description_id) {
Ok(AshmemResponse::Mapping( Ok(ServedShMemResponse::Mapping(
client client
.maps .maps
.get_mut(&description_id) .get_mut(&description_id)
@ -275,7 +401,7 @@ impl AshmemService {
.clone(), .clone(),
)) ))
} else { } else {
Ok(AshmemResponse::Mapping( Ok(ServedShMemResponse::Mapping(
self.all_maps self.all_maps
.get_mut(&description_id) .get_mut(&description_id)
.unwrap() .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 client = self.clients.get_mut(&client_id).unwrap();
let maps = client.maps.entry(map_id).or_default(); let maps = client.maps.entry(map_id).or_default();
if maps.is_empty() { if maps.is_empty() {
Ok(AshmemResponse::RefCount(0u32)) Ok(ServedShMemResponse::RefCount(0u32))
} else { } else {
Ok(AshmemResponse::RefCount( Ok(ServedShMemResponse::RefCount(
Rc::strong_count(&maps.pop().unwrap()) as u32, 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); //println!("send ashmem client: {}, response: {:?}", client_id, &response);
response response
} }
fn read_request(&mut self, client_id: RawFd) -> Result<AshmemRequest, Error> { fn read_request(&mut self, client_id: RawFd) -> Result<ServedShMemRequest, Error> {
let client = self.clients.get_mut(&client_id).unwrap(); let client = self.clients.get_mut(&client_id).unwrap();
// Always receive one be u32 of size, then the command. // Always receive one be u32 of size, then the command.
@ -315,7 +446,7 @@ impl AshmemService {
.stream .stream
.read_exact(&mut bytes) .read_exact(&mut bytes)
.expect("Failed to read message body"); .expect("Failed to read message body");
let request: AshmemRequest = postcard::from_bytes(&bytes)?; let request: ServedShMemRequest = postcard::from_bytes(&bytes)?;
Ok(request) Ok(request)
} }
@ -323,7 +454,7 @@ impl AshmemService {
let response = self.handle_request(client_id)?; let response = self.handle_request(client_id)?;
match response { match response {
AshmemResponse::Mapping(mapping) => { ServedShMemResponse::Mapping(mapping) => {
let id = mapping.borrow().id(); let id = mapping.borrow().id();
let server_fd: i32 = id.to_string().parse().unwrap(); let server_fd: i32 = id.to_string().parse().unwrap();
let client = self.clients.get_mut(&client_id).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])?; .send_fds(id.to_string().as_bytes(), &[server_fd])?;
client.maps.entry(server_fd).or_default().push(mapping); client.maps.entry(server_fd).or_default().push(mapping);
} }
AshmemResponse::Id(id) => { ServedShMemResponse::Id(id) => {
let client = self.clients.get_mut(&client_id).unwrap(); let client = self.clients.get_mut(&client_id).unwrap();
client.stream.send_fds(id.to_string().as_bytes(), &[])?; client.stream.send_fds(id.to_string().as_bytes(), &[])?;
} }
AshmemResponse::RefCount(refcount) => { ServedShMemResponse::RefCount(refcount) => {
let client = self.clients.get_mut(&client_id).unwrap(); let client = self.clients.get_mut(&client_id).unwrap();
client client
.stream .stream
@ -346,23 +477,6 @@ impl AshmemService {
Ok(()) Ok(())
} }
/// Create a new [`AshmemService`], then listen and service incoming connections in a new thread.
pub fn start() -> Result<thread::JoinHandle<Result<(), Error>>, 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 /// Listen on a filename (or abstract name) for new connections and serve them. This function
/// should not return. /// should not return.
fn listen( fn listen(
@ -378,10 +492,13 @@ impl AshmemService {
let (lock, cvar) = &**syncpair; let (lock, cvar) = &**syncpair;
*lock.lock().unwrap() = true; *lock.lock().unwrap() = true;
cvar.notify_one(); cvar.notify_one();
println!("Error in ShMem Worker");
return Err(Error::Unknown( return Err(Error::Unknown(
"The server appears to already be running. We are probably a client".to_string(), "The server appears to already be running. We are probably a client".to_string(),
)); ));
}; };
let mut poll_fds: Vec<PollFd> = vec![PollFd::new( let mut poll_fds: Vec<PollFd> = vec![PollFd::new(
listener.as_raw_fd(), listener.as_raw_fd(),
PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND, PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND,
@ -418,7 +535,7 @@ impl AshmemService {
} }
}; };
} else { } else {
let (stream, addr) = match listener.accept_unix_addr() { let (stream, _addr) = match listener.accept_unix_addr() {
Ok(stream_val) => stream_val, Ok(stream_val) => stream_val,
Err(e) => { Err(e) => {
println!("Error accepting client: {:?}", 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( let pollfd = PollFd::new(
stream.as_raw_fd(), stream.as_raw_fd(),
PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND, PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND,
); );
poll_fds.push(pollfd); poll_fds.push(pollfd);
let client = AshmemClient::new(stream); let client = SharedShMemClient::new(stream);
let client_id = client.stream.as_raw_fd(); let client_id = client.stream.as_raw_fd();
self.clients.insert(client_id, client); self.clients.insert(client_id, client);
match self.handle_client(client_id) { match self.handle_client(client_id) {
Ok(()) => (), Ok(()) => (),
Err(Error::ShuttingDown) => {
println!("Shutting down");
return Ok(());
}
Err(e) => { Err(e) => {
dbg!("Ignoring failed read from client", e); dbg!("Ignoring failed read from client", e);
} }
@ -449,3 +570,14 @@ impl AshmemService {
} }
} }
} }
impl<SP> Drop for ServedShMemServiceWorker<SP>
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));
}
}

View File

@ -7,38 +7,58 @@ use core::{
fmt::{self, Debug, Display}, fmt::{self, Debug, Display},
mem::ManuallyDrop, mem::ManuallyDrop,
}; };
#[cfg(all(feature = "std", unix, not(target_os = "android")))]
pub use unix_shmem::{MmapShMem, MmapShMemProvider};
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
pub use unix_shmem::{UnixShMem, UnixShMemProvider}; pub use unix_shmem::{UnixShMem, UnixShMemProvider};
/// The default [`ShMemProvider`] for this os.
use crate::Error;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
pub type OsShMemProvider = UnixShMemProvider; pub use crate::bolts::os::unix_shmem_server::{ServedShMemProvider, ShMemService};
/// The default [`ShMem`] for this os.
#[cfg(all(feature = "std", unix))]
pub type OsShMem = UnixShMem;
#[cfg(all(windows, feature = "std"))] #[cfg(all(windows, feature = "std"))]
pub use win32_shmem::{Win32ShMem, Win32ShMemProvider}; pub use win32_shmem::{Win32ShMem, Win32ShMemProvider};
#[cfg(all(windows, feature = "std"))] #[cfg(all(windows, feature = "std"))]
pub type OsShMemProvider = Win32ShMemProvider; pub type StdShMemProvider = Win32ShMemProvider;
#[cfg(all(windows, feature = "std"))] #[cfg(all(windows, feature = "std"))]
pub type OsShMem = Win32ShMem; pub type StdShMem = Win32ShMem;
use crate::Error;
#[cfg(all(target_os = "android", feature = "std"))] #[cfg(all(target_os = "android", feature = "std"))]
use crate::bolts::os::ashmem_server::ServedShMemProvider; pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider<AshmemShMemProvider>>;
#[cfg(all(target_os = "android", feature = "std"))] #[cfg(all(target_os = "android", feature = "std"))]
pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider>; pub type StdShMem = RcShMem<ServedShMem<AshmemShMem>>;
#[cfg(all(target_os = "android", feature = "std"))] #[cfg(all(target_os = "android", feature = "std"))]
pub type StdShMem = RcShMem<ServedShMemProvider>; pub type StdShMemService = ShMemService<AshmemShMemProvider>;
#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))]
pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider<MmapShMemProvider>>;
#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))]
pub type StdShMem = RcShMem<ServedShMem<MmapShMem>>;
#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))]
pub type StdShMemService = ShMemService<MmapShMemProvider>;
/// The default [`ShMemProvider`] for this os. /// The default [`ShMemProvider`] for this os.
#[cfg(all(feature = "std", not(target_os = "android")))] #[cfg(all(
pub type StdShMemProvider = OsShMemProvider; feature = "std",
/// The default [`ShMem`] for this os. unix,
#[cfg(all(feature = "std", not(target_os = "android")))] not(any(target_os = "android", target_os = "ios", target_os = "macos"))
pub type StdShMem = OsShMem; ))]
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}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -47,6 +67,12 @@ use std::{
env, 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"))] #[cfg(all(unix, feature = "std"))]
use crate::bolts::os::pipes::Pipe; use crate::bolts::os::pipes::Pipe;
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std"))]
@ -436,14 +462,13 @@ where
/// A Unix sharedmem implementation. /// A Unix sharedmem implementation.
/// ///
/// On Android, this is partially reused to wrap [`unix_shmem::ashmem::AshmemShMem`], /// 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. /// Is needed on top.
#[cfg(all(unix, feature = "std"))] #[cfg(all(unix, feature = "std"))]
pub mod unix_shmem { pub mod unix_shmem {
/// Shared memory provider for Android, allocating and forwarding maps over unix domain sockets. /// Shared memory provider for Android, allocating and forwarding maps over unix domain sockets.
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub type UnixShMemProvider = ashmem::AshmemShMemProvider; pub type UnixShMemProvider = ashmem::ServedShMemProvider;
/// Shared memory for Android /// Shared memory for Android
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub type UnixShMem = ashmem::AshmemShMem; pub type UnixShMem = ashmem::AshmemShMem;
@ -454,10 +479,22 @@ pub mod unix_shmem {
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
pub type UnixShMem = ashmem::AshmemShMem; 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")))] #[cfg(all(unix, feature = "std", not(target_os = "android")))]
mod default { 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::{ use crate::{
bolts::shmem::{ShMem, ShMemId, ShMemProvider}, 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; 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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
Ok(Self { current_map_id: 0 })
}
fn new_map(&mut self, map_size: usize) -> Result<Self::Mem, Error> {
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<Self::Mem, Error> {
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 /// The default sharedmap impl for unix using shmctl & shmget
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CommonUnixShMem { pub struct CommonUnixShMem {
@ -514,7 +742,11 @@ pub mod unix_shmem {
/// Create a new shared memory mapping, using shmget/shmat /// Create a new shared memory mapping, using shmget/shmat
pub fn new(map_size: usize) -> Result<Self, Error> { pub fn new(map_size: usize) -> Result<Self, Error> {
unsafe { 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 { 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))); 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; let map = shmat(os_id, ptr::null(), 0) as *mut c_uchar;
if map as c_int == -1 || map.is_null() { 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( return Err(Error::Unknown(
"Failed to map the shared mapping".to_string(), "Failed to map the shared mapping".to_string(),
)); ));
@ -543,7 +775,7 @@ pub mod unix_shmem {
let id_int: i32 = id.into(); let id_int: i32 = id.into();
let map = shmat(id_int, ptr::null(), 0) as *mut c_uchar; 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::<c_uchar>().wrapping_sub(1) {
return Err(Error::Unknown( return Err(Error::Unknown(
"Failed to map the shared mapping".to_string(), "Failed to map the shared mapping".to_string(),
)); ));
@ -579,7 +811,7 @@ pub mod unix_shmem {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let id_int: i32 = self.id.into(); 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<Self, Error> {
Ok(Self {})
}
}
#[cfg(feature = "std")]
/// A cursor around [`ShMem`] that immitates [`std::io::Cursor`]. Notably, this implements [`Write`] for [`ShMem`] in std environments. /// A cursor around [`ShMem`] that immitates [`std::io::Cursor`]. Notably, this implements [`Write`] for [`ShMem`] in std environments.
pub struct ShMemCursor<T: ShMem> { pub struct ShMemCursor<T: ShMem> {
inner: T, inner: T,
pos: usize, pos: usize,
} }
#[cfg(feature = "std")]
impl<T: ShMem> ShMemCursor<T> { impl<T: ShMem> ShMemCursor<T> {
pub fn new(shmem: T) -> Self { pub fn new(shmem: T) -> Self {
Self { Self {
@ -1083,3 +1331,22 @@ impl<T: ShMem> std::io::Seek for ShMemCursor<T> {
Ok(effective_new_pos) 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);
}
}

View File

@ -111,7 +111,8 @@ where
if size_of::<StateShMemContent>() + serialized.len() > self.shmem.len() { if size_of::<StateShMemContent>() + serialized.len() > self.shmem.len() {
// generate a filename // generate a filename
let mut hasher = AHasher::new_with_keys(0, 0); 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 filename = format!("{:016x}.libafl_state", hasher.finish());
let tmpfile = temp_dir().join(&filename); let tmpfile = temp_dir().join(&filename);
@ -238,15 +239,20 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serial_test::serial;
use crate::bolts::{ use crate::bolts::{
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
staterestore::StateRestorer, staterestore::StateRestorer,
}; };
#[test] #[test]
#[serial]
fn test_state_restore() { fn test_state_restore() {
const TESTMAP_SIZE: usize = 1024; const TESTMAP_SIZE: usize = 1024;
let _service = StdShMemService::start().unwrap();
let mut shmem_provider = StdShMemProvider::new().unwrap(); let mut shmem_provider = StdShMemProvider::new().unwrap();
let shmem = shmem_provider.new_map(TESTMAP_SIZE).unwrap(); let shmem = shmem_provider.new_map(TESTMAP_SIZE).unwrap();
let mut state_restorer = StateRestorer::<StdShMemProvider>::new(shmem); let mut state_restorer = StateRestorer::<StdShMemProvider>::new(shmem);

View File

@ -15,7 +15,7 @@ use std::net::{SocketAddr, ToSocketAddrs};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use crate::bolts::{ use crate::bolts::{
llmp::{LlmpClient, LlmpConnection}, llmp::{LlmpClient, LlmpConnection},
shmem::StdShMemProvider, shmem::{StdShMemProvider, StdShMemService},
staterestore::StateRestorer, staterestore::StateRestorer,
}; };
@ -49,7 +49,7 @@ use crate::bolts::os::startable_self;
use crate::bolts::os::{fork, ForkResult}; use crate::bolts::os::{fork, ForkResult};
#[cfg(all(target_os = "android", feature = "std"))] #[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")] #[cfg(feature = "std")]
use typed_builder::TypedBuilder; use typed_builder::TypedBuilder;
@ -689,8 +689,7 @@ where
OT: ObserversTuple<I, S> + serde::de::DeserializeOwned, OT: ObserversTuple<I, S> + serde::de::DeserializeOwned,
S: DeserializeOwned, S: DeserializeOwned,
{ {
#[cfg(target_os = "android")] let _service = StdShMemService::start().expect("Error starting ShMem Service");
AshmemService::start().expect("Error starting Ashmem Service");
RestartingMgr::builder() RestartingMgr::builder()
.shmem_provider(StdShMemProvider::new()?) .shmem_provider(StdShMemProvider::new()?)
@ -929,11 +928,13 @@ where
#[cfg(test)] #[cfg(test)]
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod tests { mod tests {
use serial_test::serial;
use crate::{ use crate::{
bolts::{ bolts::{
llmp::{LlmpClient, LlmpSharedMap}, llmp::{LlmpClient, LlmpSharedMap},
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
staterestore::StateRestorer, staterestore::StateRestorer,
tuples::tuple_list, tuples::tuple_list,
}, },
@ -949,7 +950,10 @@ mod tests {
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
#[test] #[test]
#[serial]
fn test_mgr_state_restore() { fn test_mgr_state_restore() {
let _service = StdShMemService::start().unwrap();
let rand = StdRand::with_seed(0); let rand = StdRand::with_seed(0);
let mut corpus = InMemoryCorpus::<BytesInput>::new(); let mut corpus = InMemoryCorpus::<BytesInput>::new();

View File

@ -3,6 +3,7 @@ use core::marker::PhantomData;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::{process::Child, time::Duration}; use std::{process::Child, time::Duration};
#[cfg(feature = "std")]
use crate::{ use crate::{
executors::{Executor, ExitKind, HasObservers}, executors::{Executor, ExitKind, HasObservers},
inputs::Input, inputs::Input,

View File

@ -614,9 +614,11 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serial_test::serial;
use crate::{ use crate::{
bolts::{ bolts::{
shmem::{ShMem, ShMemProvider, StdShMemProvider}, shmem::{ShMem, ShMemProvider, StdShMemProvider, StdShMemService},
tuples::tuple_list, tuples::tuple_list,
}, },
executors::ForkserverExecutor, executors::ForkserverExecutor,
@ -625,11 +627,14 @@ mod tests {
Error, Error,
}; };
#[test] #[test]
#[serial]
fn test_forkserver() { fn test_forkserver() {
const MAP_SIZE: usize = 65536; const MAP_SIZE: usize = 65536;
let bin = "echo"; let bin = "echo";
let args = vec![String::from("@@")]; let args = vec![String::from("@@")];
let _service = StdShMemService::start().unwrap();
let mut shmem = StdShMemProvider::new() let mut shmem = StdShMemProvider::new()
.unwrap() .unwrap()
.new_map(MAP_SIZE as usize) .new_map(MAP_SIZE as usize)

View File

@ -19,7 +19,9 @@ pub use shadow::ShadowExecutor;
pub mod with_observers; pub mod with_observers;
pub use with_observers::WithObservers; pub use with_observers::WithObservers;
#[cfg(feature = "std")]
pub mod command; pub mod command;
#[cfg(feature = "std")]
pub use command::CommandExecutor; pub use command::CommandExecutor;
use crate::{ use crate::{

View File

@ -107,7 +107,12 @@ impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> {
self.asan_runtime.register_thread(); self.asan_runtime.register_thread();
} }
#[cfg(not(target_arch = "aarch64"))]
fn pre_exec<I: Input + HasTargetBytes>(&mut self, _input: &I) {}
#[cfg(target_arch = "aarch64")]
fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I) { fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I) {
#[cfg(target_arch = "aarch64")]
let target_bytes = input.target_bytes(); let target_bytes = input.target_bytes();
let slice = target_bytes.as_slice(); let slice = target_bytes.as_slice();
//println!("target_bytes: {:#x}: {:02x?}", slice.as_ptr() as usize, slice); //println!("target_bytes: {:#x}: {:02x?}", slice.as_ptr() as usize, slice);

View File

@ -71,7 +71,7 @@ impl<'a, H> InMemoryBytesCoverageSugar<'a, H>
where where
H: FnMut(&[u8]), H: FnMut(&[u8]),
{ {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines, clippy::similar_names)]
pub fn run(&mut self) { pub fn run(&mut self) {
let conf = self let conf = self
.configuration .configuration

View File

@ -7,7 +7,7 @@ use libafl::{
current_nanos, current_nanos,
launcher::Launcher, launcher::Launcher,
rands::StdRand, rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider, StdShMemService},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
}, },
corpus::{ corpus::{

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
echo "Warning: this script is not a proper fix to do LLMP fuzzing." \ 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.shmmax=524288000
sudo sysctl -w kern.sysv.shmmin=1 sudo sysctl -w kern.sysv.shmmin=1