From 7dd345d18ccdfbeb09d7b88e0afee686a8e16c40 Mon Sep 17 00:00:00 2001 From: Aarnav Date: Wed, 5 Jun 2024 16:16:30 +0200 Subject: [PATCH] Enforce max_input_size and min_input_size in Forkserver (#2273) * enforce minimum and maximum input size for Forkserver's shared memory map and file based testcases * explicit conversion to OwnedSlice * clippy * error if min_input_size > max_input_size when building forkserver --- libafl/src/executors/forkserver.rs | 68 ++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 666fca4194..dd0b628afb 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -21,6 +21,7 @@ use std::{ use libafl_bolts::{ fs::{get_unique_std_input_file, InputFile}, os::{dup2, pipes::Pipe}, + ownedref::OwnedSlice, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{Handle, Handled, MatchNameRef, Prepend, RefIndexable}, AsSlice, AsSliceMut, Truncate, @@ -102,6 +103,7 @@ fn report_error_and_exit(status: i32) -> Result<(), Error> { /// The length of header bytes which tells shmem size const SHMEM_FUZZ_HDR_SIZE: usize = 4; const MAX_INPUT_SIZE_DEFAULT: usize = 1024 * 1024; +const MIN_INPUT_SIZE_DEFAULT: usize = 1; /// The default signal to use to kill child processes const KILL_SIGNAL_DEFAULT: Signal = Signal::SIGTERM; @@ -529,6 +531,8 @@ where map: Option, phantom: PhantomData, map_size: Option, + min_input_size: usize, + max_input_size: usize, #[cfg(feature = "regex")] asan_obs: Handle, timeout: TimeSpec, @@ -614,6 +618,7 @@ pub struct ForkserverExecutorBuilder<'a, SP> { input_filename: Option, shmem_provider: Option<&'a mut SP>, max_input_size: usize, + min_input_size: usize, map_size: Option, kill_signal: Option, timeout: Option, @@ -656,6 +661,15 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { Some(t) => t.into(), None => Duration::from_millis(5000).into(), }; + if self.min_input_size > self.max_input_size { + return Err(Error::illegal_argument( + format!( + "Minimum input size ({}) must not exceed maximum input size ({})", + self.min_input_size, self.max_input_size + ) + .as_str(), + )); + } Ok(ForkserverExecutor { target, @@ -667,6 +681,8 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { map, phantom: PhantomData, map_size: self.map_size, + min_input_size: self.min_input_size, + max_input_size: self.max_input_size, timeout, asan_obs: self .asan_obs @@ -729,6 +745,8 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { map, phantom: PhantomData, map_size: self.map_size, + min_input_size: self.min_input_size, + max_input_size: self.max_input_size, timeout, asan_obs: self .asan_obs @@ -1010,6 +1028,20 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { self } + /// Set the max input size + #[must_use] + pub fn max_input_size(mut self, size: usize) -> Self { + self.max_input_size = size; + self + } + + /// Set the min input size + #[must_use] + pub fn min_input_size(mut self, size: usize) -> Self { + self.min_input_size = size; + self + } + /// Adds an environmental var to the harness's commandline #[must_use] pub fn env(mut self, key: K, val: V) -> Self @@ -1131,6 +1163,7 @@ impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> { shmem_provider: None, map_size: None, max_input_size: MAX_INPUT_SIZE_DEFAULT, + min_input_size: MIN_INPUT_SIZE_DEFAULT, kill_signal: None, timeout: None, asan_obs: None, @@ -1157,6 +1190,7 @@ impl<'a> ForkserverExecutorBuilder<'a, UnixShMemProvider> { shmem_provider: Some(shmem_provider), map_size: self.map_size, max_input_size: MAX_INPUT_SIZE_DEFAULT, + min_input_size: MIN_INPUT_SIZE_DEFAULT, kill_signal: None, timeout: None, asan_obs: None, @@ -1194,6 +1228,21 @@ where let last_run_timed_out = self.forkserver.last_run_timed_out_raw(); + let mut input_bytes = input.target_bytes(); + let mut input_size = input_bytes.as_slice().len(); + if input_size > self.max_input_size { + // Truncate like AFL++ does + input_size = self.max_input_size; + } else if input_size < self.min_input_size { + // Extend like AFL++ does + input_size = self.min_input_size; + let mut input_bytes_copy = Vec::with_capacity(input_size); + input_bytes_copy + .as_slice_mut() + .copy_from_slice(input_bytes.as_slice()); + input_bytes = OwnedSlice::from(input_bytes_copy); + } + let input_size_in_bytes = input_size.to_ne_bytes(); if self.uses_shmem_testcase { debug_assert!( self.map.is_some(), @@ -1202,21 +1251,14 @@ where // # Safety // Struct can never be created when uses_shmem_testcase is true and map is none. let map = unsafe { self.map.as_mut().unwrap_unchecked() }; - let target_bytes = input.target_bytes(); - let mut size = target_bytes.as_slice().len(); - let max_size = map.len() - SHMEM_FUZZ_HDR_SIZE; - if size > max_size { - // Truncate like AFL++ does - size = max_size; - } - let size_in_bytes = size.to_ne_bytes(); - // The first four bytes tells the size of the shmem. + // The first four bytes declares the size of the shmem. map.as_slice_mut()[..SHMEM_FUZZ_HDR_SIZE] - .copy_from_slice(&size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]); - map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)] - .copy_from_slice(&target_bytes.as_slice()[..size]); + .copy_from_slice(&input_size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]); + map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + input_size)] + .copy_from_slice(&input_bytes.as_slice()[..input_size]); } else { - self.input_file.write_buf(input.target_bytes().as_slice())?; + self.input_file + .write_buf(&input_bytes.as_slice()[..input_size])?; } let send_len = self.forkserver.write_ctl(last_run_timed_out)?;