diff --git a/libafl/src/monitors/tui/mod.rs b/libafl/src/monitors/tui/mod.rs index 6e058e8a4b..689a7530eb 100644 --- a/libafl/src/monitors/tui/mod.rs +++ b/libafl/src/monitors/tui/mod.rs @@ -3,8 +3,8 @@ use alloc::{boxed::Box, string::ToString}; use std::{ collections::VecDeque, - fmt::Write, - io::{self, BufRead}, + fmt::Write as _, + io::{self, BufRead, Write}, panic, string::String, sync::{Arc, RwLock}, @@ -434,7 +434,33 @@ impl TuiMonitor { #[must_use] pub fn with_time(tui_ui: TuiUI, start_time: Duration) -> Self { let context = Arc::new(RwLock::new(TuiContext::new(start_time))); - run_tui_thread(context.clone(), Duration::from_millis(250), tui_ui); + + enable_raw_mode().unwrap(); + #[cfg(unix)] + { + use std::{ + fs::File, + os::fd::{AsRawFd, FromRawFd}, + }; + + let stdout = unsafe { libc::dup(io::stdout().as_raw_fd()) }; + let stdout = unsafe { File::from_raw_fd(stdout) }; + run_tui_thread( + context.clone(), + Duration::from_millis(250), + tui_ui, + move || stdout.try_clone().unwrap(), + ); + } + #[cfg(not(unix))] + { + run_tui_thread( + context.clone(), + Duration::from_millis(250), + tui_ui, + io::stdout, + ); + } Self { context, start_time, @@ -528,11 +554,15 @@ impl TuiMonitor { } } -fn run_tui_thread(context: Arc>, tick_rate: Duration, tui_ui: TuiUI) { +fn run_tui_thread( + context: Arc>, + tick_rate: Duration, + tui_ui: TuiUI, + stdout_provider: impl Send + Sync + 'static + Fn() -> W, +) { thread::spawn(move || -> io::Result<()> { // setup terminal - let mut stdout = io::stdout(); - enable_raw_mode()?; + let mut stdout = stdout_provider(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); @@ -546,9 +576,10 @@ fn run_tui_thread(context: Arc>, tick_rate: Duration, tui_ui: // Catching panics when the main thread dies let old_hook = panic::take_hook(); panic::set_hook(Box::new(move |panic_info| { + let mut stdout = stdout_provider(); disable_raw_mode().unwrap(); execute!( - io::stdout(), + stdout, LeaveAlternateScreen, DisableMouseCapture, Show, diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml b/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml index c27fd2e708..160c926c33 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml @@ -30,9 +30,9 @@ path = "src/lib.rs" crate-type = ["staticlib", "rlib"] [dependencies] -libafl = { version = "0.11", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "errors_backtrace", "regex", "serdeany_autoreg", "tui_monitor"] } -libafl_bolts = { version = "0.11", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "errors_backtrace"] } -libafl_targets = { version = "0.11", features = ["sancov_8bit", "sancov_cmplog", "libfuzzer", "libfuzzer_oom", "libfuzzer_define_run_driver", "sanitizers_flags"] } +libafl = { path = "../../libafl", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "regex", "errors_backtrace", "serdeany_autoreg", "tui_monitor"] } +libafl_bolts = { path = "../../libafl_bolts", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "serdeany_autoreg", "errors_backtrace"] } +libafl_targets = { path = "../../libafl_targets", features = ["sancov_8bit", "sancov_cmplog", "libfuzzer", "libfuzzer_oom", "libfuzzer_define_run_driver", "sanitizers_flags"] } ahash = { version = "0.8.3", default-features = false } libc = "0.2.139" @@ -46,10 +46,12 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv bytecount = "0.6.3" # for identifying if we can grimoire-ify -utf8-chars = "2.0.3" +utf8-chars = "3.0.1" + +env_logger = "0.10" [build-dependencies] -bindgen = "0.65.1" +bindgen = "0.68.1" cc = { version = "1.0", features = ["parallel"] } [workspace] diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/fuzz.rs b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/fuzz.rs index b49bffd751..89a575a862 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/fuzz.rs +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/fuzz.rs @@ -1,15 +1,13 @@ use core::ffi::c_int; +#[cfg(unix)] +use std::io::Write; use std::{ fmt::Debug, fs::File, net::TcpListener, + str::FromStr, time::{SystemTime, UNIX_EPOCH}, }; -#[cfg(unix)] -use std::{ - io::Write, - os::fd::{AsRawFd, FromRawFd, IntoRawFd}, -}; use libafl::{ corpus::Corpus, @@ -37,6 +35,31 @@ use libafl_bolts::{ use crate::{feedbacks::LibfuzzerCrashCauseMetadata, fuzz_with, options::LibfuzzerOptions}; +fn destroy_output_fds(options: &LibfuzzerOptions) { + #[cfg(unix)] + { + use std::os::fd::AsRawFd; + + if options.tui() { + let file_null = File::open("/dev/null").unwrap(); + unsafe { + libc::dup2(file_null.as_raw_fd(), 1); + libc::dup2(file_null.as_raw_fd(), 2); + } + } else if options.close_fd_mask() != 0 { + let file_null = File::open("/dev/null").unwrap(); + unsafe { + if options.close_fd_mask() & 1 != 0 { + libc::dup2(file_null.as_raw_fd(), 1); + } + if options.close_fd_mask() & 2 != 0 { + libc::dup2(file_null.as_raw_fd(), 2); + } + } + } + } +} + fn do_fuzz( options: &LibfuzzerOptions, fuzzer: &mut F, @@ -93,6 +116,7 @@ fn fuzz_single_forking( where M: Monitor + Debug, { + destroy_output_fds(options); fuzz_with!(options, harness, do_fuzz, |fuzz_single| { let (state, mgr): ( Option>, @@ -109,24 +133,13 @@ where } }, }; - #[cfg(unix)] - { - if options.close_fd_mask() != 0 { - let file_null = File::open("/dev/null")?; - unsafe { - if options.close_fd_mask() & 1 != 0 { - libc::dup2(file_null.as_raw_fd(), 1); - } - if options.close_fd_mask() & 2 != 0 { - libc::dup2(file_null.as_raw_fd(), 2); - } - } - } - } crate::start_fuzzing_single(fuzz_single, state, mgr) }) } +/// Communicate the selected port to subprocesses +const PORT_PROVIDER_VAR: &str = "_LIBAFL_LIBFUZZER_FORK_PORT"; + fn fuzz_many_forking( options: &LibfuzzerOptions, harness: &extern "C" fn(*const u8, usize) -> c_int, @@ -137,12 +150,19 @@ fn fuzz_many_forking( where M: Monitor + Clone + Debug, { + destroy_output_fds(options); + let broker_port = std::env::var(PORT_PROVIDER_VAR) + .map_err(Error::from) + .and_then(|s| u16::from_str(&s).map_err(Error::from)) + .or_else(|_| { + TcpListener::bind("127.0.0.1:0").map(|sock| { + let port = sock.local_addr().unwrap().port(); + std::env::set_var(PORT_PROVIDER_VAR, port.to_string()); + port + }) + })?; fuzz_with!(options, harness, do_fuzz, |mut run_client| { let cores = Cores::from((0..forks).collect::>()); - let broker_port = TcpListener::bind("127.0.0.1:0")? - .local_addr() - .unwrap() - .port(); match Launcher::builder() .shmem_provider(shmem_provider) @@ -164,6 +184,26 @@ where }) } +fn create_monitor_closure() -> impl Fn(String) + Clone { + #[cfg(unix)] + let stderr_fd = + std::os::fd::RawFd::from_str(&std::env::var(crate::STDERR_FD_VAR).unwrap()).unwrap(); // set in main + move |s| { + #[cfg(unix)] + { + use std::os::fd::FromRawFd; + + // unfortunate requirement to meet Clone... thankfully, this does not + // generate effectively any overhead (no allocations, calls get merged) + let mut stderr = unsafe { File::from_raw_fd(stderr_fd) }; + writeln!(stderr, "{s}").expect("Could not write to stderr???"); + std::mem::forget(stderr); // do not close the descriptor! + } + #[cfg(not(unix))] + eprintln!("{s}"); + } +} + pub fn fuzz( options: &LibfuzzerOptions, harness: &extern "C" fn(*const u8, usize) -> c_int, @@ -174,37 +214,14 @@ pub fn fuzz( let monitor = TuiMonitor::new(TuiUI::new(options.fuzzer_name().to_string(), true)); fuzz_many_forking(options, harness, shmem_provider, forks, monitor) } else if forks == 1 { - #[cfg(unix)] - let mut stderr = unsafe { - let new_fd = libc::dup(std::io::stderr().as_raw_fd()); - File::from_raw_fd(new_fd) - }; let monitor = MultiMonitor::with_time( - move |s| { - #[cfg(unix)] - writeln!(stderr, "{s}").expect("Could not write to stderr???"); - #[cfg(not(unix))] - eprintln!("{s}"); - }, + create_monitor_closure(), SystemTime::now().duration_since(UNIX_EPOCH).unwrap(), ); fuzz_single_forking(options, harness, shmem_provider, monitor) } else { - #[cfg(unix)] - let stderr_fd = unsafe { libc::dup(std::io::stderr().as_raw_fd()) }; let monitor = MultiMonitor::with_time( - move |s| { - #[cfg(unix)] - { - // unfortunate requirement to meet Clone... thankfully, this does not - // generate effectively any overhead (no allocations, calls get merged) - let mut stderr = unsafe { File::from_raw_fd(stderr_fd) }; - writeln!(stderr, "{s}").expect("Could not write to stderr???"); - let _ = stderr.into_raw_fd(); // discard the file without closing - } - #[cfg(not(unix))] - eprintln!("{s}"); - }, + create_monitor_closure(), SystemTime::now().duration_since(UNIX_EPOCH).unwrap(), ); fuzz_many_forking(options, harness, shmem_provider, forks, monitor) @@ -216,8 +233,9 @@ pub fn fuzz( let monitor = TuiMonitor::new(TuiUI::new(options.fuzzer_name().to_string(), true)); fuzz_many_forking(options, harness, shmem_provider, 1, monitor) } else { + destroy_output_fds(options); fuzz_with!(options, harness, do_fuzz, |fuzz_single| { - let mgr = SimpleEventManager::new(SimpleMonitor::new(|s| eprintln!("{s}"))); + let mgr = SimpleEventManager::new(SimpleMonitor::new(create_monitor_closure())); crate::start_fuzzing_single(fuzz_single, None, mgr) }) } diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/lib.rs b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/lib.rs index 6e5850d17f..155a9d2a3f 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/lib.rs +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/lib.rs @@ -71,7 +71,9 @@ #![allow(clippy::borrow_deref_ref)] use core::ffi::{c_char, c_int, CStr}; +use std::{fs::File, io::stderr, os::fd::RawFd}; +use env_logger::Target; use libafl::{ inputs::{BytesInput, HasTargetBytes, Input}, Error, @@ -207,7 +209,7 @@ macro_rules! fuzz_with { let backtrace_observer = BacktraceObserver::new( "BacktraceObserver", unsafe { &mut BACKTRACE }, - libafl::observers::HarnessType::InProcess, + if $options.forks().is_some() || $options.tui() { libafl::observers::HarnessType::Child } else { libafl::observers::HarnessType::InProcess } ); // New maximization map feedback linked to the edges observer @@ -528,6 +530,9 @@ extern "C" { fn libafl_targets_libfuzzer_init(argc: *mut c_int, argv: *mut *mut *const c_char) -> i32; } +/// Communicate the stderr duplicated fd to subprocesses +pub const STDERR_FD_VAR: &str = "_LIBAFL_LIBFUZZER_STDERR_FD"; + /// A method to start the fuzzer at a later point in time from a library. /// To quote the `libfuzzer` docs: /// > when it’s ready to start fuzzing, it can call `LLVMFuzzerRunDriver`, passing in the program arguments and a callback. This callback is invoked just like `LLVMFuzzerTestOneInput`, and has the same signature. @@ -547,6 +552,29 @@ pub unsafe extern "C" fn LLVMFuzzerRunDriver( .as_ref() .expect("Illegal harness provided to libafl."); + // early duplicate the stderr fd so we can close it later for the target + #[cfg(unix)] + { + use std::{ + os::fd::{AsRawFd, FromRawFd}, + str::FromStr, + }; + + let stderr_fd = std::env::var(STDERR_FD_VAR) + .map_err(Error::from) + .and_then(|s| RawFd::from_str(&s).map_err(Error::from)) + .unwrap_or_else(|_| { + let stderr = libc::dup(stderr().as_raw_fd()); + std::env::set_var(STDERR_FD_VAR, stderr.to_string()); + stderr + }); + let stderr = File::from_raw_fd(stderr_fd); + env_logger::builder() + .parse_default_env() + .target(Target::Pipe(Box::new(stderr))) + .init(); + } + // it appears that no one, not even libfuzzer, uses this return value // https://github.com/llvm/llvm-project/blob/llvmorg-15.0.7/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L648 libafl_targets_libfuzzer_init(argc, argv);