Forkserver Shared Memory Testcase (#265)

* working on shmem testcase fuzzing

* fmt & clippy

* write_to_testcase

* write input size

* max os fixes

* RcShMemProvider?

* ServedShMemProvider?

* revert changes

* RcShMem<ServedShMemProvider<MmapShMemProvider>>?

* ShMem change for android? (not tested at all)

* harness

* shmem testcase fuzzing for timeoutforkserver

* update harness

* remove .o

* pselect instead of select

* clippy
This commit is contained in:
Toka 2021-08-17 08:44:17 +09:00 committed by GitHub
parent 15c6e6b73b
commit d7ec395010
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 33 deletions

View File

@ -99,8 +99,9 @@ pub fn main() {
// Create the executor for the forkserver // Create the executor for the forkserver
let mut executor = TimeoutForkserverExecutor::new( let mut executor = TimeoutForkserverExecutor::new(
ForkserverExecutor::new( ForkserverExecutor::new(
"../../libafl_tests/src/forkserver_test.o".to_string(), "../../libafl_tests/src/forkserver_test".to_string(),
&[], &[],
true,
tuple_list!(edges_observer, time_observer), tuple_list!(edges_observer, time_observer),
) )
.unwrap(), .unwrap(),

View File

@ -28,14 +28,14 @@ pub type StdShMem = Win32ShMem;
#[cfg(all(target_os = "android", feature = "std"))] #[cfg(all(target_os = "android", feature = "std"))]
pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider<AshmemShMemProvider>>; pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider<AshmemShMemProvider>>;
#[cfg(all(target_os = "android", feature = "std"))] #[cfg(all(target_os = "android", feature = "std"))]
pub type StdShMem = RcShMem<ServedShMem<AshmemShMem>>; pub type StdShMem = RcShMem<ServedShMemProvider<AshmemShMemProvider>>;
#[cfg(all(target_os = "android", feature = "std"))] #[cfg(all(target_os = "android", feature = "std"))]
pub type StdShMemService = ShMemService<AshmemShMemProvider>; pub type StdShMemService = ShMemService<AshmemShMemProvider>;
#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))] #[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))]
pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider<MmapShMemProvider>>; pub type StdShMemProvider = RcShMemProvider<ServedShMemProvider<MmapShMemProvider>>;
#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))] #[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))]
pub type StdShMem = RcShMem<ServedShMem<MmapShMem>>; pub type StdShMem = RcShMem<ServedShMemProvider<MmapShMemProvider>>;
#[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))] #[cfg(all(feature = "std", any(target_os = "ios", target_os = "macos")))]
pub type StdShMemService = ShMemService<MmapShMemProvider>; pub type StdShMemService = ShMemService<MmapShMemProvider>;
@ -480,7 +480,7 @@ pub mod unix_shmem {
pub type UnixShMemProvider = default::CommonUnixShMemProvider; pub type UnixShMemProvider = default::CommonUnixShMemProvider;
/// Shared memory for Unix /// Shared memory for Unix
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
pub type UnixShMem = ashmem::AshmemShMem; pub type UnixShMem = default::CommonUnixShMem;
/// Mmap [`ShMem`] for Unix /// Mmap [`ShMem`] for Unix
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]

View File

@ -12,22 +12,32 @@ use std::{
}; };
use crate::{ use crate::{
bolts::os::{dup2, pipes::Pipe}, bolts::{
os::{dup2, pipes::Pipe},
shmem::{ShMem, ShMemProvider, StdShMem, StdShMemProvider},
},
executors::{Executor, ExitKind, HasObservers}, executors::{Executor, ExitKind, HasObservers},
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
observers::ObserversTuple, observers::ObserversTuple,
Error, Error,
}; };
use nix::{ use nix::{
sys::{ sys::{
select::{select, FdSet}, select::{pselect, FdSet},
signal::{kill, Signal}, signal::{kill, SigSet, Signal},
time::{TimeVal, TimeValLike}, time::{TimeSpec, TimeValLike},
}, },
unistd::Pid, unistd::Pid,
}; };
const FORKSRV_FD: i32 = 198; const FORKSRV_FD: i32 = 198;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_ENABLED: i32 = 0x80000001u32 as i32;
#[allow(clippy::cast_possible_wrap)]
const FS_OPT_SHDMEM_FUZZ: i32 = 0x01000000u32 as i32;
const SHMEM_FUZZ_HDR_SIZE: usize = 4;
const MAX_FILE: usize = 1024 * 1024;
// Configure the target. setlimit, setsid, pipe_stdin, I borrowed the code from Angora fuzzer // Configure the target. setlimit, setsid, pipe_stdin, I borrowed the code from Angora fuzzer
pub trait ConfigTarget { pub trait ConfigTarget {
@ -267,7 +277,7 @@ impl Forkserver {
Ok(slen) Ok(slen)
} }
pub fn read_st_timed(&mut self, timeout: &mut TimeVal) -> Result<Option<i32>, Error> { pub fn read_st_timed(&mut self, timeout: &TimeSpec) -> Result<Option<i32>, Error> {
let mut buf: [u8; 4] = [0u8; 4]; let mut buf: [u8; 4] = [0u8; 4];
let st_read = match self.st_pipe.read_end() { let st_read = match self.st_pipe.read_end() {
Some(fd) => fd, Some(fd) => fd,
@ -279,15 +289,15 @@ impl Forkserver {
} }
}; };
let mut readfds = FdSet::new(); let mut readfds = FdSet::new();
let mut copy = *timeout;
readfds.insert(st_read); readfds.insert(st_read);
// We'll pass a copied timeout to keep the original timeout intact, because select updates timeout to indicate how much time was left. See select(2) // We'll pass a copied timeout to keep the original timeout intact, because select updates timeout to indicate how much time was left. See select(2)
let sret = select( let sret = pselect(
Some(readfds.highest().unwrap() + 1), Some(readfds.highest().unwrap() + 1),
&mut readfds, &mut readfds,
None, None,
None, None,
&mut copy, Some(timeout),
Some(&SigSet::empty()),
)?; )?;
if sret > 0 { if sret > 0 {
if self.st_pipe.read_exact(&mut buf).is_ok() { if self.st_pipe.read_exact(&mut buf).is_ok() {
@ -312,18 +322,22 @@ pub trait HasForkserver {
fn out_file(&self) -> &OutFile; fn out_file(&self) -> &OutFile;
fn out_file_mut(&mut self) -> &mut OutFile; fn out_file_mut(&mut self) -> &mut OutFile;
fn map(&self) -> &Option<StdShMem>;
fn map_mut(&mut self) -> &mut Option<StdShMem>;
} }
/// The timeout forkserver executor that wraps around the standard forkserver executor and sets a timeout before each run. /// The timeout forkserver executor that wraps around the standard forkserver executor and sets a timeout before each run.
pub struct TimeoutForkserverExecutor<E> { pub struct TimeoutForkserverExecutor<E> {
executor: E, executor: E,
timeout: TimeVal, timeout: TimeSpec,
} }
impl<E> TimeoutForkserverExecutor<E> { impl<E> TimeoutForkserverExecutor<E> {
pub fn new(executor: E, exec_tmout: Duration) -> Result<Self, Error> { pub fn new(executor: E, exec_tmout: Duration) -> Result<Self, Error> {
let milli_sec = exec_tmout.as_millis() as i64; let milli_sec = exec_tmout.as_millis() as i64;
let timeout = TimeVal::milliseconds(milli_sec); let timeout = TimeSpec::milliseconds(milli_sec);
Ok(Self { executor, timeout }) Ok(Self { executor, timeout })
} }
} }
@ -345,9 +359,21 @@ where
let last_run_timed_out = self.executor.forkserver().last_run_timed_out(); let last_run_timed_out = self.executor.forkserver().last_run_timed_out();
match &mut self.executor.map_mut() {
Some(map) => {
let size = input.target_bytes().as_slice().len();
let size_in_bytes = size.to_ne_bytes();
// The first four bytes tells the size of the shmem.
map.map_mut()[..4].copy_from_slice(&size_in_bytes[..4]);
map.map_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)]
.copy_from_slice(input.target_bytes().as_slice());
}
None => {
self.executor self.executor
.out_file_mut() .out_file_mut()
.write_buf(input.target_bytes().as_slice()); .write_buf(input.target_bytes().as_slice());
}
}
let send_len = self let send_len = self
.executor .executor
@ -382,7 +408,7 @@ where
if let Some(status) = self if let Some(status) = self
.executor .executor
.forkserver_mut() .forkserver_mut()
.read_st_timed(&mut self.timeout)? .read_st_timed(&self.timeout)?
{ {
self.executor.forkserver_mut().set_status(status); self.executor.forkserver_mut().set_status(status);
if libc::WIFSIGNALED(self.executor.forkserver().status()) { if libc::WIFSIGNALED(self.executor.forkserver().status()) {
@ -411,6 +437,8 @@ where
} }
/// This [`Executor`] can run binaries compiled for AFL/AFL++ that make use of a forkserver. /// This [`Executor`] can run binaries compiled for AFL/AFL++ that make use of a forkserver.
/// Shared memory feature is also available, but you have to set things up in your code.
/// Please refer to AFL++'s docs. <https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.persistent_mode.md>
pub struct ForkserverExecutor<I, OT, S> pub struct ForkserverExecutor<I, OT, S>
where where
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
@ -421,6 +449,7 @@ where
out_file: OutFile, out_file: OutFile,
forkserver: Forkserver, forkserver: Forkserver,
observers: OT, observers: OT,
map: Option<StdShMem>,
phantom: PhantomData<(I, S)>, phantom: PhantomData<(I, S)>,
} }
@ -429,7 +458,12 @@ where
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
{ {
pub fn new(target: String, arguments: &[String], observers: OT) -> Result<Self, Error> { pub fn new(
target: String,
arguments: &[String],
use_shmem_testcase: bool,
observers: OT,
) -> Result<Self, Error> {
let mut args = Vec::<String>::new(); let mut args = Vec::<String>::new();
let mut use_stdin = true; let mut use_stdin = true;
let out_filename = ".cur_input".to_string(); let out_filename = ".cur_input".to_string();
@ -445,6 +479,18 @@ where
let out_file = OutFile::new(&out_filename)?; let out_file = OutFile::new(&out_filename)?;
let mut map = None;
if use_shmem_testcase {
// setup shared memory
let mut provider = StdShMemProvider::new()?;
let mut shmem = provider.new_map(MAX_FILE + SHMEM_FUZZ_HDR_SIZE)?;
shmem.write_to_env("__AFL_SHM_FUZZ_ID")?;
let size_in_bytes = (MAX_FILE + SHMEM_FUZZ_HDR_SIZE).to_ne_bytes();
shmem.map_mut()[..4].clone_from_slice(&size_in_bytes[..4]);
map = Some(shmem);
}
let mut forkserver = Forkserver::new( let mut forkserver = Forkserver::new(
target.clone(), target.clone(),
args.clone(), args.clone(),
@ -453,17 +499,29 @@ where
0, 0,
)?; )?;
let (rlen, _) = forkserver.read_st()?; // Initial handshake, read 4-bytes hello message from the forkserver. let (rlen, status) = forkserver.read_st()?; // Initial handshake, read 4-bytes hello message from the forkserver.
match rlen { if rlen != 4 {
4 => {
println!("All right - fork server is up.");
}
_ => {
return Err(Error::Forkserver( return Err(Error::Forkserver(
"Failed to start a forkserver".to_string(), "Failed to start a forkserver".to_string(),
)) ));
} }
println!("All right - fork server is up.");
// If forkserver is responding, we then check if there's any option enabled.
if status & FS_OPT_ENABLED == FS_OPT_ENABLED {
if (status & FS_OPT_SHDMEM_FUZZ == FS_OPT_SHDMEM_FUZZ) & use_shmem_testcase {
println!("Using SHARED MEMORY FUZZING feature.");
let send_status = FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ;
let send_len = forkserver.write_ctl(send_status)?;
if send_len != 4 {
return Err(Error::Forkserver(
"Writing to forkserver failed.".to_string(),
));
}
}
} else {
println!("Forkserver Options are not available.");
} }
Ok(Self { Ok(Self {
@ -472,6 +530,7 @@ where
out_file, out_file,
forkserver, forkserver,
observers, observers,
map,
phantom: PhantomData, phantom: PhantomData,
}) })
} }
@ -509,7 +568,19 @@ where
let mut exit_kind = ExitKind::Ok; let mut exit_kind = ExitKind::Ok;
// Write to testcase // Write to testcase
match &mut self.map {
Some(map) => {
let size = input.target_bytes().as_slice().len();
let size_in_bytes = size.to_ne_bytes();
// The first four bytes tells the size of the shmem.
map.map_mut()[..4].copy_from_slice(&size_in_bytes[..4]);
map.map_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)]
.copy_from_slice(input.target_bytes().as_slice());
}
None => {
self.out_file.write_buf(input.target_bytes().as_slice()); self.out_file.write_buf(input.target_bytes().as_slice());
}
}
let send_len = self let send_len = self
.forkserver .forkserver
@ -594,6 +665,16 @@ where
fn out_file_mut(&mut self) -> &mut OutFile { fn out_file_mut(&mut self) -> &mut OutFile {
&mut self.out_file &mut self.out_file
} }
#[inline]
fn map(&self) -> &Option<StdShMem> {
&self.map
}
#[inline]
fn map_mut(&mut self) -> &mut Option<StdShMem> {
&mut self.map
}
} }
impl<E, I, OT, S> HasObservers<I, OT, S> for TimeoutForkserverExecutor<E> impl<E, I, OT, S> HasObservers<I, OT, S> for TimeoutForkserverExecutor<E>
@ -648,6 +729,7 @@ mod tests {
let executor = ForkserverExecutor::<NopInput, _, ()>::new( let executor = ForkserverExecutor::<NopInput, _, ()>::new(
bin.to_string(), bin.to_string(),
&args, &args,
false,
tuple_list!(edges_observer), tuple_list!(edges_observer),
); );
// Since /usr/bin/echo is not a instrumented binary file, the test will just check if the forkserver has failed at the initial handshake // Since /usr/bin/echo is not a instrumented binary file, the test will just check if the forkserver has failed at the initial handshake

View File

@ -38,7 +38,7 @@ fn main() {
Command::new(afl_gcc_path) Command::new(afl_gcc_path)
.args(&["src/forkserver_test.c", "-o"]) .args(&["src/forkserver_test.c", "-o"])
.arg(&format!("{}/forkserver_test.o", "src")) .arg(&format!("{}/forkserver_test", "src"))
.status() .status()
.unwrap(); .unwrap();

View File

@ -1,6 +1,10 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
// The following line is needed for shared memeory testcase fuzzing
__AFL_FUZZ_INIT();
int main(int argc, char **argv){ int main(int argc, char **argv){
FILE* file = stdin; FILE* file = stdin;
@ -8,11 +12,18 @@ int main(int argc, char **argv){
file = fopen(argv[1], "rb"); file = fopen(argv[1], "rb");
} }
// The following three lines are for normal fuzzing.
/*
char buf[16]; char buf[16];
char* p = fgets(buf, 16, file); char* p = fgets(buf, 16, file);
buf[15] = 0; buf[15] = 0;
*/
printf("input: %s\n", p); // The following line is also needed for shared memory testcase fuzzing
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
printf("input: %s\n", buf);
if(buf[0] == 'b'){ if(buf[0] == 'b'){
if(buf[1] == 'a'){ if(buf[1] == 'a'){