diff --git a/.gitignore b/.gitignore index 83080c6242..58fb8a1e6b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ vendor .DS_Store .env +*.tmp *.o *.a *.so diff --git a/libafl/src/bolts/fs.rs b/libafl/src/bolts/fs.rs index 505968a158..2064b81c47 100644 --- a/libafl/src/bolts/fs.rs +++ b/libafl/src/bolts/fs.rs @@ -1,21 +1,21 @@ //! `LibAFL` functionality for filesystem interaction +#[cfg(feature = "std")] +use alloc::borrow::ToOwned; +use alloc::rc::Rc; +use core::cell::RefCell; +#[cfg(unix)] +use std::os::unix::prelude::{AsRawFd, RawFd}; use std::{ fs::{self, remove_file, File, OpenOptions}, io::{Seek, SeekFrom, Write}, path::{Path, PathBuf}, }; -#[cfg(unix)] -use std::os::unix::prelude::{AsRawFd, RawFd}; - -#[cfg(feature = "std")] -use alloc::borrow::ToOwned; - use crate::Error; /// The default filename to use to deliver testcases to the target -pub const OUTFILE_STD: &str = ".cur_input"; +pub const INPUTFILE_STD: &str = ".cur_input"; /// Creates a `.{file_name}.tmp` file, and writes all bytes to it. /// After all bytes have been written, the tmp-file is moved to it's original `path`. @@ -47,37 +47,46 @@ where inner(path.as_ref(), bytes) } -/// An [`OutFile`] to write fuzzer input to. +/// An [`InputFile`] to write fuzzer input to. /// The target/forkserver will read from this file. #[cfg(feature = "std")] #[derive(Debug)] -pub struct OutFile { - /// The filename/path too this [`OutFile`] +pub struct InputFile { + /// The filename/path too this [`InputFile`] pub path: PathBuf, /// The underlying file that got created pub file: File, + /// The ref count for this [`InputFile`]. + /// Once it reaches 0, the underlying [`File`] will be removed. + pub rc: Rc>, } -impl Eq for OutFile {} +impl Eq for InputFile {} -impl PartialEq for OutFile { +impl PartialEq for InputFile { fn eq(&self, other: &Self) -> bool { self.path == other.path } } -impl Clone for OutFile { +impl Clone for InputFile { fn clone(&self) -> Self { + { + let mut rc = self.rc.borrow_mut(); + assert_ne!(*rc, usize::MAX, "InputFile rc overflow"); + *rc += 1; + } Self { path: self.path.clone(), file: self.file.try_clone().unwrap(), + rc: self.rc.clone(), } } } #[cfg(feature = "std")] -impl OutFile { - /// Creates a new [`OutFile`] +impl InputFile { + /// Creates a new [`InputFile`] pub fn create

(filename: P) -> Result where P: AsRef, @@ -91,6 +100,7 @@ impl OutFile { Ok(Self { path: filename.as_ref().to_owned(), file: f, + rc: Rc::new(RefCell::new(1)), }) } @@ -123,25 +133,38 @@ impl OutFile { } #[cfg(feature = "std")] -impl Drop for OutFile { +impl Drop for InputFile { fn drop(&mut self) { - // try to remove the file, but ignore errors - drop(remove_file(&self.path)); + let mut rc = self.rc.borrow_mut(); + assert_ne!(*rc, 0, "InputFile rc should never be 0"); + *rc -= 1; + if *rc == 0 { + // try to remove the file, but ignore errors + drop(remove_file(&self.path)); + } } } #[cfg(test)] mod test { - use crate::bolts::fs::write_file_atomic; + use crate::bolts::fs::{write_file_atomic, InputFile}; use std::fs; #[test] fn test_atomic_file_write() { - let path = "atomic_file_testfile"; - + let path = "test_atomic_file_write.tmp"; write_file_atomic(&path, b"test").unwrap(); let content = fs::read_to_string(&path).unwrap(); fs::remove_file(&path).unwrap(); assert_eq!(content, "test"); } + + #[test] + fn test_cloned_ref() { + let mut one = InputFile::create("test_cloned_ref.tmp").unwrap(); + let two = one.clone(); + one.write_buf("Welp".as_bytes()).unwrap(); + drop(one); + assert_eq!("Welp", fs::read_to_string(&two.path).unwrap()); + } } diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index 0e70c2e519..7b0ad8d341 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -19,7 +19,7 @@ use std::{ use crate::{ bolts::{ - fs::{OutFile, OUTFILE_STD}, + fs::{InputFile, INPUTFILE_STD}, tuples::MatchName, AsSlice, }, @@ -39,7 +39,7 @@ use super::HasObservers; /// How to deliver input to an external program /// `StdIn`: The traget reads from stdin -/// `File`: The target reads from the specified [`OutFile`] +/// `File`: The target reads from the specified [`InputFile`] #[derive(Debug, Clone, PartialEq, Eq)] enum InputLocation { /// Mutate a commandline argument to deliver an input @@ -49,11 +49,11 @@ enum InputLocation { }, /// Deliver input via `StdIn` StdIn, - /// Deliver the iniput via the specified [`OutFile`] - /// You can use specify [`OutFile::create(OUTFILE_STD)`] to use a default filename. + /// Deliver the input via the specified [`InputFile`] + /// You can use specify [`InputFile::create(INPUTFILE_STD)`] to use a default filename. File { - /// The fiel to write input to. The target should read input from this location. - out_file: OutFile, + /// The file to write input to. The target should read input from this location. + input_file: InputFile, }, } @@ -133,8 +133,8 @@ impl CommandConfigurator for StdCommandConfigurator { drop(stdin); Ok(handle) } - InputLocation::File { out_file } => { - out_file.write_buf(input.target_bytes().as_slice())?; + InputLocation::File { input_file } => { + input_file.write_buf(input.target_bytes().as_slice())?; Ok(self.command.spawn()?) } } @@ -250,7 +250,7 @@ where has_asan_observer, configurer: StdCommandConfigurator { input_location: InputLocation::File { - out_file: OutFile::create(path)?, + input_file: InputFile::create(path)?, }, command, debug_child, @@ -461,7 +461,7 @@ impl CommandExecutorBuilder { /// Uses a default filename. /// Use [`Self::arg_input_file`] to specify a custom filename. pub fn arg_input_file_std(&mut self) -> &mut Self { - self.arg_input_file(OUTFILE_STD); + self.arg_input_file(INPUTFILE_STD); self } @@ -469,9 +469,9 @@ impl CommandExecutorBuilder { /// and adds the filename as arg to at the current position. pub fn arg_input_file>(&mut self, path: P) -> &mut Self { self.arg(path.as_ref()); - let out_file_std = OutFile::create(path.as_ref()).unwrap(); + let input_file_std = InputFile::create(path.as_ref()).unwrap(); self.input(InputLocation::File { - out_file: out_file_std, + input_file: input_file_std, }); self } diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 0ca052e67a..2651008d8e 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -15,7 +15,7 @@ use std::{ use crate::{ bolts::{ - fs::{OutFile, OUTFILE_STD}, + fs::{InputFile, INPUTFILE_STD}, os::{dup2, pipes::Pipe}, shmem::{ShMem, ShMemProvider, StdShMemProvider}, AsMutSlice, AsSlice, @@ -175,7 +175,7 @@ impl Forkserver { target: OsString, args: Vec, envs: Vec<(OsString, OsString)>, - out_filefd: RawFd, + input_filefd: RawFd, use_stdin: bool, memlimit: u64, debug_output: bool, @@ -199,7 +199,7 @@ impl Forkserver { .envs(envs) .setlimit(memlimit) .setsid() - .setstdin(out_filefd, use_stdin) + .setstdin(input_filefd, use_stdin) .setpipe( st_pipe.read_end().unwrap(), st_pipe.write_end().unwrap(), @@ -337,10 +337,10 @@ pub trait HasForkserver { fn forkserver_mut(&mut self) -> &mut Forkserver; /// The file the forkserver is reading from - fn out_file(&self) -> &OutFile; + fn input_file(&self) -> &InputFile; /// The file the forkserver is reading from, mutable - fn out_file_mut(&mut self) -> &mut OutFile; + fn input_file_mut(&mut self) -> &mut InputFile; /// The map of the fuzzer fn shmem(&self) -> &Option<<::SP as ShMemProvider>::ShMem>; @@ -405,7 +405,7 @@ where } None => { self.executor - .out_file_mut() + .input_file_mut() .write_buf(input.target_bytes().as_slice())?; } } @@ -479,12 +479,12 @@ where { target: OsString, args: Vec, - out_file: OutFile, + input_file: InputFile, forkserver: Forkserver, observers: OT, map: Option, phantom: PhantomData<(I, S)>, - /// Cache that indicates if we have a asan observer registered. + /// Cache that indicates if we have a `ASan` observer registered. has_asan_observer: Option, } @@ -497,7 +497,7 @@ where f.debug_struct("ForkserverExecutor") .field("target", &self.target) .field("args", &self.args) - .field("out_file", &self.out_file) + .field("input_file", &self.input_file) .field("forkserver", &self.forkserver) .field("observers", &self.observers) .field("map", &self.map) @@ -534,9 +534,9 @@ where &self.forkserver } - /// The [`OutFile`] used by this [`Executor`]. - pub fn out_file(&self) -> &OutFile { - &self.out_file + /// The [`InputFile`] used by this [`Executor`]. + pub fn input_file(&self) -> &InputFile { + &self.input_file } } @@ -549,7 +549,7 @@ pub struct ForkserverExecutorBuilder<'a, SP> { debug_child: bool, use_stdin: bool, autotokens: Option<&'a mut Tokens>, - out_filename: Option, + input_filename: Option, shmem_provider: Option<&'a mut SP>, } @@ -565,12 +565,12 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { OT: ObserversTuple, SP: ShMemProvider, { - let out_filename = match &self.out_filename { + let input_filename = match &self.input_filename { Some(name) => name.clone(), None => OsString::from(".cur_input"), }; - let out_file = OutFile::create(&out_filename)?; + let input_file = InputFile::create(&input_filename)?; let map = match &mut self.shmem_provider { None => None, @@ -591,7 +591,7 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { t.clone(), self.arguments.clone(), self.envs.clone(), - out_file.as_raw_fd(), + input_file.as_raw_fd(), self.use_stdin, 0, self.debug_child, @@ -671,7 +671,7 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { Ok(ForkserverExecutor { target, args: self.arguments.clone(), - out_file, + input_file, forkserver, observers, map, @@ -701,7 +701,7 @@ impl<'a, SP> ForkserverExecutorBuilder<'a, SP> { if item.as_ref() == "@@" && use_stdin { use_stdin = false; res.push(OsString::from(".cur_input")); - } else if let Some(name) = &self.out_filename { + } else if let Some(name) = &self.input_filename { if name == item.as_ref() && use_stdin { use_stdin = false; res.push(name.clone()); @@ -734,7 +734,7 @@ impl<'a> ForkserverExecutorBuilder<'a, StdShMemProvider> { debug_child: false, use_stdin: true, autotokens: None, - out_filename: None, + input_filename: None, shmem_provider: None, } } @@ -806,14 +806,14 @@ impl<'a> ForkserverExecutorBuilder<'a, StdShMemProvider> { /// Place the input at this position and set the filename for the input. pub fn arg_input_file>(self, path: P) -> Self { let mut moved = self.arg(path.as_ref()); - moved.out_filename = Some(path.as_ref().as_os_str().to_os_string()); + moved.input_filename = Some(path.as_ref().as_os_str().to_os_string()); moved } #[must_use] /// Place the input at this position and set the default filename for the input. pub fn arg_input_file_std(self) -> Self { - self.arg_input_file(OUTFILE_STD) + self.arg_input_file(INPUTFILE_STD) } #[must_use] @@ -835,7 +835,7 @@ impl<'a> ForkserverExecutorBuilder<'a, StdShMemProvider> { debug_child: self.debug_child, use_stdin: self.use_stdin, autotokens: self.autotokens, - out_filename: self.out_filename, + input_filename: self.input_filename, shmem_provider: Some(shmem_provider), } } @@ -875,7 +875,7 @@ where .copy_from_slice(target_bytes.as_slice()); } None => { - self.out_file.write_buf(input.target_bytes().as_slice())?; + self.input_file.write_buf(input.target_bytes().as_slice())?; } } @@ -971,13 +971,13 @@ where } #[inline] - fn out_file(&self) -> &OutFile { - &self.out_file + fn input_file(&self) -> &InputFile { + &self.input_file } #[inline] - fn out_file_mut(&mut self) -> &mut OutFile { - &mut self.out_file + fn input_file_mut(&mut self) -> &mut InputFile { + &mut self.input_file } #[inline]