Ashmem server for Android (#50)

* ashmem, initial commit

* ashmem

* ashmem_service: server side ready

* ashmem_service: client side ready. Ready for integration

* ashmem_service: changes to UnixShMem to make it 'threadable'

* ashmem_service: format

* ashmem_service: Undo changes to UnixShMem, make the thread own the AshmemService instead; Fix protocol bug

* ashmem_service: actually fix the protocol issue; clippy warnings

* no-std fixes

* fmt

Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
s1341 2021-04-10 11:33:11 +03:00 committed by GitHub
parent 77cbb45b7c
commit f4d5c045b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 281 additions and 9 deletions

View File

@ -452,7 +452,7 @@ where
{ {
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
pub fn on_domain_socket(filename: &str) -> Result<Self, Error> { pub fn on_domain_socket(filename: &str) -> Result<Self, Error> {
match UnixListener::bind_unix_addr(&UnixSocketAddr::new(filename).unwrap()) { match UnixListener::bind_unix_addr(&UnixSocketAddr::new(filename)?) {
Ok(listener) => { Ok(listener) => {
dbg!("We're the broker"); dbg!("We're the broker");
let mut broker = LlmpBroker::new()?; let mut broker = LlmpBroker::new()?;
@ -732,7 +732,7 @@ where
#[cfg(feature = "std")] #[cfg(feature = "std")]
return None; return None;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
panic!(&format!("Unexpected error allocing new msg {:?}", e)); panic!("Unexpected error allocing new msg {:?}", e);
} }
}; };
(*ret).message_id = (*last_msg).message_id + 1 (*ret).message_id = (*last_msg).message_id + 1

View File

@ -0,0 +1,268 @@
/*!
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,
and forwards them over unix domain sockets.
*/
use crate::{
bolts::shmem::{ShMem, ShMemDescription, UnixShMem},
Error,
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::io::{Read, Write};
#[cfg(all(feature = "std", unix))]
use nix::poll::{poll, PollFd, PollFlags};
#[cfg(all(feature = "std", unix))]
use std::{
os::unix::{
net::{UnixListener, UnixStream},
{io::AsRawFd, prelude::RawFd},
},
thread,
};
#[cfg(all(unix, feature = "std"))]
use uds::{UnixListenerExt, UnixSocketAddr, UnixStreamExt};
#[derive(Debug)]
/// The Sharedmem backed by a `ShmemService`a
pub struct ServedShMem {
stream: UnixStream,
shmem: Option<UnixShMem>,
slice: Option<[u8; 20]>,
fd: Option<RawFd>,
}
const ASHMEM_SERVER_NAME: &str = "@ashmem_server";
impl ServedShMem {
/// Create a new ServedShMem and connect to the ashmem server.
pub fn connect(name: &str) -> Self {
Self {
stream: UnixStream::connect_to_unix_addr(&UnixSocketAddr::from_abstract(name).unwrap())
.expect("Failed to connect to the ashmem server"),
shmem: None,
slice: None,
fd: None,
}
}
/// Send a request to the server, and wait for a response
fn send_receive(&mut self, request: AshmemRequest) -> ([u8; 20], RawFd) {
let body = postcard::to_allocvec(&request).unwrap();
let header = (body.len() as u32).to_be_bytes();
let mut message = header.to_vec();
message.extend(body);
self.stream
.write_all(&message)
.expect("Failed to send message");
let mut shm_slice = [0u8; 20];
let mut fd_buf = [-1; 1];
self.stream
.recv_fds(&mut shm_slice, &mut fd_buf)
.expect("Did not receive a response");
(shm_slice, fd_buf[0])
}
}
impl ShMem for ServedShMem {
fn new_map(map_size: usize) -> Result<Self, crate::Error> {
let mut res = Self::connect(ASHMEM_SERVER_NAME);
let (shm_slice, fd) = res.send_receive(AshmemRequest::NewMap(map_size));
if fd == -1 {
Err(Error::IllegalState(
"Could not allocate from the ashmem server".to_string(),
))
} else {
res.slice = Some(shm_slice);
res.fd = Some(fd);
res.shmem = Some(
UnixShMem::existing_from_shm_slice(&shm_slice, map_size)
.expect("Failed to create the UnixShMem"),
);
Ok(res)
}
}
fn existing_from_shm_slice(
map_str_bytes: &[u8; 20],
map_size: usize,
) -> Result<Self, crate::Error> {
let mut res = Self::connect(ASHMEM_SERVER_NAME);
let (shm_slice, fd) = res.send_receive(AshmemRequest::ExistingMap(ShMemDescription {
size: map_size,
str_bytes: *map_str_bytes,
}));
if fd == -1 {
Err(Error::IllegalState(
"Could not allocate from the ashmem server".to_string(),
))
} else {
res.slice = Some(shm_slice);
res.fd = Some(fd);
res.shmem = Some(
UnixShMem::existing_from_shm_slice(&shm_slice, map_size)
.expect("Failed to create the UnixShMem"),
);
Ok(res)
}
}
fn shm_slice(&self) -> &[u8; 20] {
self.slice.as_ref().unwrap()
}
fn map(&self) -> &[u8] {
self.shmem.as_ref().unwrap().map()
}
fn map_mut(&mut self) -> &mut [u8] {
self.shmem.as_mut().unwrap().map_mut()
}
}
/// A request sent to the ShMem server to receive a fd to a shared map
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum AshmemRequest {
/// Register a new map with a given size.
NewMap(usize),
/// Another client already has a map with this description mapped.
ExistingMap(ShMemDescription),
/// A client tells us it unregisters the previously allocated map
Deregister(u32),
}
#[derive(Debug)]
pub struct AshmemClient {
unix_socket_file: String,
}
#[derive(Debug)]
pub struct AshmemService {
maps: HashMap<[u8; 20], UnixShMem>,
}
impl AshmemService {
/// Create a new AshMem service
#[must_use]
fn new() -> Self {
AshmemService {
maps: HashMap::new(),
}
}
/// Read and handle the client request, send the answer over unix fd.
fn handle_client(&mut self, stream: &mut UnixStream) -> Result<(), Error> {
// Always receive one be u32 of size, then the command.
let mut size_bytes = [0u8; 4];
stream.read_exact(&mut size_bytes)?;
let size = u32::from_be_bytes(size_bytes);
let mut bytes = vec![];
bytes.resize(size as usize, 0u8);
stream
.read_exact(&mut bytes)
.expect("Failed to read message body");
let request: AshmemRequest = postcard::from_bytes(&bytes)?;
// Handle the client request
let (shmem_slice, fd): ([u8; 20], RawFd) = match request {
AshmemRequest::NewMap(map_size) => match UnixShMem::new(map_size) {
Err(e) => {
println!("Error allocating shared map {:?}", e);
([0; 20], -1)
}
Ok(map) => {
let res = (*map.shm_slice(), map.shm_id);
self.maps.insert(*map.shm_slice(), map);
res
}
},
AshmemRequest::ExistingMap(description) => {
match self.maps.get(&description.str_bytes) {
None => {
println!("Error finding shared map {:?}", description);
([0; 20], -1)
}
Some(map) => (*map.shm_slice(), map.shm_id),
}
}
AshmemRequest::Deregister(_) => {
return Ok(());
}
};
stream.send_fds(&shmem_slice, &[fd])?;
Ok(())
}
/// Create a new AshmemService, then listen and service incoming connections in a new thread.
pub fn start() -> Result<thread::JoinHandle<()>, Error> {
Ok(thread::spawn(move || {
Self::new().listen(ASHMEM_SERVER_NAME).unwrap()
}))
}
/// Listen on a filename (or abstract name) for new connections and serve them. This function
/// should not return.
fn listen(&mut self, filename: &str) -> Result<(), Error> {
let listener = UnixListener::bind_unix_addr(&UnixSocketAddr::new(filename)?)?;
let mut clients: HashMap<RawFd, (UnixStream, UnixSocketAddr)> = HashMap::new();
let mut poll_fds: HashMap<RawFd, PollFd> = HashMap::new();
poll_fds.insert(
listener.as_raw_fd(),
PollFd::new(listener.as_raw_fd(), PollFlags::POLLIN),
);
loop {
let mut fds_to_poll: Vec<PollFd> = poll_fds.values().copied().collect();
let fd = match poll(&mut fds_to_poll, -1) {
Ok(fd) => fd,
Err(e) => {
println!("Error polling for activity: {:?}", e);
continue;
}
};
if fd == listener.as_raw_fd() {
let (stream, addr) = match listener.accept_unix_addr() {
Ok(stream_val) => stream_val,
Err(e) => {
println!("Error accepting client: {:?}", e);
continue;
}
};
println!("Recieved connection from {:?}", addr);
let pollfd = PollFd::new(stream.as_raw_fd(), PollFlags::POLLIN);
poll_fds.insert(stream.as_raw_fd(), pollfd);
clients
.insert(stream.as_raw_fd(), (stream, addr))
.as_ref()
.unwrap();
} else if poll_fds
.get(&fd)
.unwrap()
.revents()
.unwrap()
.contains(PollFlags::POLLHUP)
{
poll_fds.remove(&fd);
clients.remove(&fd);
} else {
let (stream, _addr) = clients.get_mut(&fd).unwrap();
match self.handle_client(stream) {
Ok(()) => (),
Err(e) => {
dbg!("Ignoring failed read from client", e);
continue;
}
};
}
}
}
}

View File

@ -1,5 +1,8 @@
//! Operating System specific abstractions //! Operating System specific abstractions
#[cfg(all(unix, feature = "std"))]
pub mod ashmem_server;
#[cfg(unix)] #[cfg(unix)]
pub mod unix_signals; pub mod unix_signals;
#[cfg(windows)] #[cfg(windows)]

View File

@ -1,4 +1,4 @@
//! The BytesInput is the "normal" input, a map of bytes, that can be sent directly to the client //! The `BytesInput` is the "normal" input, a map of bytes, that can be sent directly to the client
//! (As opposed to other, more abstract, imputs, like an Grammar-Based AST Input) //! (As opposed to other, more abstract, imputs, like an Grammar-Based AST Input)
use alloc::{borrow::ToOwned, rc::Rc, vec::Vec}; use alloc::{borrow::ToOwned, rc::Rc, vec::Vec};

View File

@ -1,5 +1,8 @@
//! Tokens are what afl calls extras or dictionaries. //! Tokens are what afl calls extras or dictionaries.
//! They may be inserted as part of mutations during fuzzing. //! They may be inserted as part of mutations during fuzzing.
use alloc::vec::Vec;
use core::marker::PhantomData;
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::{ use std::{
fs::File, fs::File,
@ -9,18 +12,16 @@ use std::{
use crate::{ use crate::{
inputs::{HasBytesVec, Input}, inputs::{HasBytesVec, Input},
mutators::{buffer_self_copy, mutations, str_decode, MutationResult, Mutator, Named}, mutators::{buffer_self_copy, mutations, MutationResult, Mutator, Named},
state::{HasMaxSize, HasMetadata, HasRand}, state::{HasMaxSize, HasMetadata, HasRand},
utils::Rand, utils::Rand,
Error, Error,
}; };
use core::marker::PhantomData;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use mutations::buffer_copy; use mutations::buffer_copy;
#[cfg(feature = "std")]
use crate::mutators::str_decode;
/// A state metadata holding a list of tokens /// A state metadata holding a list of tokens
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Tokens { pub struct Tokens {