Skip to content

Commit 761a77f

Browse files
authored
libafl_libfuzzer fixes for port and fd allocation (#1525)
* better port and fd handling * fix multitude of CI failures
1 parent 0e149af commit 761a77f

File tree

4 files changed

+141
-62
lines changed

4 files changed

+141
-62
lines changed

libafl/src/monitors/tui/mod.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use alloc::{boxed::Box, string::ToString};
44
use std::{
55
collections::VecDeque,
6-
fmt::Write,
7-
io::{self, BufRead},
6+
fmt::Write as _,
7+
io::{self, BufRead, Write},
88
panic,
99
string::String,
1010
sync::{Arc, RwLock},
@@ -434,7 +434,33 @@ impl TuiMonitor {
434434
#[must_use]
435435
pub fn with_time(tui_ui: TuiUI, start_time: Duration) -> Self {
436436
let context = Arc::new(RwLock::new(TuiContext::new(start_time)));
437-
run_tui_thread(context.clone(), Duration::from_millis(250), tui_ui);
437+
438+
enable_raw_mode().unwrap();
439+
#[cfg(unix)]
440+
{
441+
use std::{
442+
fs::File,
443+
os::fd::{AsRawFd, FromRawFd},
444+
};
445+
446+
let stdout = unsafe { libc::dup(io::stdout().as_raw_fd()) };
447+
let stdout = unsafe { File::from_raw_fd(stdout) };
448+
run_tui_thread(
449+
context.clone(),
450+
Duration::from_millis(250),
451+
tui_ui,
452+
move || stdout.try_clone().unwrap(),
453+
);
454+
}
455+
#[cfg(not(unix))]
456+
{
457+
run_tui_thread(
458+
context.clone(),
459+
Duration::from_millis(250),
460+
tui_ui,
461+
io::stdout,
462+
);
463+
}
438464
Self {
439465
context,
440466
start_time,
@@ -528,11 +554,15 @@ impl TuiMonitor {
528554
}
529555
}
530556

531-
fn run_tui_thread(context: Arc<RwLock<TuiContext>>, tick_rate: Duration, tui_ui: TuiUI) {
557+
fn run_tui_thread<W: Write + Send + Sync + 'static>(
558+
context: Arc<RwLock<TuiContext>>,
559+
tick_rate: Duration,
560+
tui_ui: TuiUI,
561+
stdout_provider: impl Send + Sync + 'static + Fn() -> W,
562+
) {
532563
thread::spawn(move || -> io::Result<()> {
533564
// setup terminal
534-
let mut stdout = io::stdout();
535-
enable_raw_mode()?;
565+
let mut stdout = stdout_provider();
536566
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
537567

538568
let backend = CrosstermBackend::new(stdout);
@@ -546,9 +576,10 @@ fn run_tui_thread(context: Arc<RwLock<TuiContext>>, tick_rate: Duration, tui_ui:
546576
// Catching panics when the main thread dies
547577
let old_hook = panic::take_hook();
548578
panic::set_hook(Box::new(move |panic_info| {
579+
let mut stdout = stdout_provider();
549580
disable_raw_mode().unwrap();
550581
execute!(
551-
io::stdout(),
582+
stdout,
552583
LeaveAlternateScreen,
553584
DisableMouseCapture,
554585
Show,

libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ path = "src/lib.rs"
3030
crate-type = ["staticlib", "rlib"]
3131

3232
[dependencies]
33-
libafl = { version = "0.11", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "errors_backtrace", "regex", "serdeany_autoreg", "tui_monitor"] }
34-
libafl_bolts = { version = "0.11", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "errors_backtrace"] }
35-
libafl_targets = { version = "0.11", features = ["sancov_8bit", "sancov_cmplog", "libfuzzer", "libfuzzer_oom", "libfuzzer_define_run_driver", "sanitizers_flags"] }
33+
libafl = { path = "../../libafl", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "regex", "errors_backtrace", "serdeany_autoreg", "tui_monitor"] }
34+
libafl_bolts = { path = "../../libafl_bolts", default-features = false, features = ["std", "derive", "llmp_compression", "rand_trait", "serdeany_autoreg", "errors_backtrace"] }
35+
libafl_targets = { path = "../../libafl_targets", features = ["sancov_8bit", "sancov_cmplog", "libfuzzer", "libfuzzer_oom", "libfuzzer_define_run_driver", "sanitizers_flags"] }
3636

3737
ahash = { version = "0.8.3", default-features = false }
3838
libc = "0.2.139"
@@ -46,10 +46,12 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv
4646
bytecount = "0.6.3"
4747

4848
# for identifying if we can grimoire-ify
49-
utf8-chars = "2.0.3"
49+
utf8-chars = "3.0.1"
50+
51+
env_logger = "0.10"
5052

5153
[build-dependencies]
52-
bindgen = "0.65.1"
54+
bindgen = "0.68.1"
5355
cc = { version = "1.0", features = ["parallel"] }
5456

5557
[workspace]

libafl_libfuzzer/libafl_libfuzzer_runtime/src/fuzz.rs

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
use core::ffi::c_int;
2+
#[cfg(unix)]
3+
use std::io::Write;
24
use std::{
35
fmt::Debug,
46
fs::File,
57
net::TcpListener,
8+
str::FromStr,
69
time::{SystemTime, UNIX_EPOCH},
710
};
8-
#[cfg(unix)]
9-
use std::{
10-
io::Write,
11-
os::fd::{AsRawFd, FromRawFd, IntoRawFd},
12-
};
1311

1412
use libafl::{
1513
corpus::Corpus,
@@ -37,6 +35,31 @@ use libafl_bolts::{
3735

3836
use crate::{feedbacks::LibfuzzerCrashCauseMetadata, fuzz_with, options::LibfuzzerOptions};
3937

38+
fn destroy_output_fds(options: &LibfuzzerOptions) {
39+
#[cfg(unix)]
40+
{
41+
use std::os::fd::AsRawFd;
42+
43+
if options.tui() {
44+
let file_null = File::open("/dev/null").unwrap();
45+
unsafe {
46+
libc::dup2(file_null.as_raw_fd(), 1);
47+
libc::dup2(file_null.as_raw_fd(), 2);
48+
}
49+
} else if options.close_fd_mask() != 0 {
50+
let file_null = File::open("/dev/null").unwrap();
51+
unsafe {
52+
if options.close_fd_mask() & 1 != 0 {
53+
libc::dup2(file_null.as_raw_fd(), 1);
54+
}
55+
if options.close_fd_mask() & 2 != 0 {
56+
libc::dup2(file_null.as_raw_fd(), 2);
57+
}
58+
}
59+
}
60+
}
61+
}
62+
4063
fn do_fuzz<F, ST, E, S, EM>(
4164
options: &LibfuzzerOptions,
4265
fuzzer: &mut F,
@@ -93,6 +116,7 @@ fn fuzz_single_forking<M>(
93116
where
94117
M: Monitor + Debug,
95118
{
119+
destroy_output_fds(options);
96120
fuzz_with!(options, harness, do_fuzz, |fuzz_single| {
97121
let (state, mgr): (
98122
Option<StdState<_, _, _, _>>,
@@ -109,24 +133,13 @@ where
109133
}
110134
},
111135
};
112-
#[cfg(unix)]
113-
{
114-
if options.close_fd_mask() != 0 {
115-
let file_null = File::open("/dev/null")?;
116-
unsafe {
117-
if options.close_fd_mask() & 1 != 0 {
118-
libc::dup2(file_null.as_raw_fd(), 1);
119-
}
120-
if options.close_fd_mask() & 2 != 0 {
121-
libc::dup2(file_null.as_raw_fd(), 2);
122-
}
123-
}
124-
}
125-
}
126136
crate::start_fuzzing_single(fuzz_single, state, mgr)
127137
})
128138
}
129139

140+
/// Communicate the selected port to subprocesses
141+
const PORT_PROVIDER_VAR: &str = "_LIBAFL_LIBFUZZER_FORK_PORT";
142+
130143
fn fuzz_many_forking<M>(
131144
options: &LibfuzzerOptions,
132145
harness: &extern "C" fn(*const u8, usize) -> c_int,
@@ -137,12 +150,19 @@ fn fuzz_many_forking<M>(
137150
where
138151
M: Monitor + Clone + Debug,
139152
{
153+
destroy_output_fds(options);
154+
let broker_port = std::env::var(PORT_PROVIDER_VAR)
155+
.map_err(Error::from)
156+
.and_then(|s| u16::from_str(&s).map_err(Error::from))
157+
.or_else(|_| {
158+
TcpListener::bind("127.0.0.1:0").map(|sock| {
159+
let port = sock.local_addr().unwrap().port();
160+
std::env::set_var(PORT_PROVIDER_VAR, port.to_string());
161+
port
162+
})
163+
})?;
140164
fuzz_with!(options, harness, do_fuzz, |mut run_client| {
141165
let cores = Cores::from((0..forks).collect::<Vec<_>>());
142-
let broker_port = TcpListener::bind("127.0.0.1:0")?
143-
.local_addr()
144-
.unwrap()
145-
.port();
146166

147167
match Launcher::builder()
148168
.shmem_provider(shmem_provider)
@@ -164,6 +184,26 @@ where
164184
})
165185
}
166186

187+
fn create_monitor_closure() -> impl Fn(String) + Clone {
188+
#[cfg(unix)]
189+
let stderr_fd =
190+
std::os::fd::RawFd::from_str(&std::env::var(crate::STDERR_FD_VAR).unwrap()).unwrap(); // set in main
191+
move |s| {
192+
#[cfg(unix)]
193+
{
194+
use std::os::fd::FromRawFd;
195+
196+
// unfortunate requirement to meet Clone... thankfully, this does not
197+
// generate effectively any overhead (no allocations, calls get merged)
198+
let mut stderr = unsafe { File::from_raw_fd(stderr_fd) };
199+
writeln!(stderr, "{s}").expect("Could not write to stderr???");
200+
std::mem::forget(stderr); // do not close the descriptor!
201+
}
202+
#[cfg(not(unix))]
203+
eprintln!("{s}");
204+
}
205+
}
206+
167207
pub fn fuzz(
168208
options: &LibfuzzerOptions,
169209
harness: &extern "C" fn(*const u8, usize) -> c_int,
@@ -174,37 +214,14 @@ pub fn fuzz(
174214
let monitor = TuiMonitor::new(TuiUI::new(options.fuzzer_name().to_string(), true));
175215
fuzz_many_forking(options, harness, shmem_provider, forks, monitor)
176216
} else if forks == 1 {
177-
#[cfg(unix)]
178-
let mut stderr = unsafe {
179-
let new_fd = libc::dup(std::io::stderr().as_raw_fd());
180-
File::from_raw_fd(new_fd)
181-
};
182217
let monitor = MultiMonitor::with_time(
183-
move |s| {
184-
#[cfg(unix)]
185-
writeln!(stderr, "{s}").expect("Could not write to stderr???");
186-
#[cfg(not(unix))]
187-
eprintln!("{s}");
188-
},
218+
create_monitor_closure(),
189219
SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
190220
);
191221
fuzz_single_forking(options, harness, shmem_provider, monitor)
192222
} else {
193-
#[cfg(unix)]
194-
let stderr_fd = unsafe { libc::dup(std::io::stderr().as_raw_fd()) };
195223
let monitor = MultiMonitor::with_time(
196-
move |s| {
197-
#[cfg(unix)]
198-
{
199-
// unfortunate requirement to meet Clone... thankfully, this does not
200-
// generate effectively any overhead (no allocations, calls get merged)
201-
let mut stderr = unsafe { File::from_raw_fd(stderr_fd) };
202-
writeln!(stderr, "{s}").expect("Could not write to stderr???");
203-
let _ = stderr.into_raw_fd(); // discard the file without closing
204-
}
205-
#[cfg(not(unix))]
206-
eprintln!("{s}");
207-
},
224+
create_monitor_closure(),
208225
SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
209226
);
210227
fuzz_many_forking(options, harness, shmem_provider, forks, monitor)
@@ -216,8 +233,9 @@ pub fn fuzz(
216233
let monitor = TuiMonitor::new(TuiUI::new(options.fuzzer_name().to_string(), true));
217234
fuzz_many_forking(options, harness, shmem_provider, 1, monitor)
218235
} else {
236+
destroy_output_fds(options);
219237
fuzz_with!(options, harness, do_fuzz, |fuzz_single| {
220-
let mgr = SimpleEventManager::new(SimpleMonitor::new(|s| eprintln!("{s}")));
238+
let mgr = SimpleEventManager::new(SimpleMonitor::new(create_monitor_closure()));
221239
crate::start_fuzzing_single(fuzz_single, None, mgr)
222240
})
223241
}

libafl_libfuzzer/libafl_libfuzzer_runtime/src/lib.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@
7171
#![allow(clippy::borrow_deref_ref)]
7272

7373
use core::ffi::{c_char, c_int, CStr};
74+
use std::{fs::File, io::stderr, os::fd::RawFd};
7475

76+
use env_logger::Target;
7577
use libafl::{
7678
inputs::{BytesInput, HasTargetBytes, Input},
7779
Error,
@@ -207,7 +209,7 @@ macro_rules! fuzz_with {
207209
let backtrace_observer = BacktraceObserver::new(
208210
"BacktraceObserver",
209211
unsafe { &mut BACKTRACE },
210-
libafl::observers::HarnessType::InProcess,
212+
if $options.forks().is_some() || $options.tui() { libafl::observers::HarnessType::Child } else { libafl::observers::HarnessType::InProcess }
211213
);
212214

213215
// New maximization map feedback linked to the edges observer
@@ -528,6 +530,9 @@ extern "C" {
528530
fn libafl_targets_libfuzzer_init(argc: *mut c_int, argv: *mut *mut *const c_char) -> i32;
529531
}
530532

533+
/// Communicate the stderr duplicated fd to subprocesses
534+
pub const STDERR_FD_VAR: &str = "_LIBAFL_LIBFUZZER_STDERR_FD";
535+
531536
/// A method to start the fuzzer at a later point in time from a library.
532537
/// To quote the `libfuzzer` docs:
533538
/// > 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(
547552
.as_ref()
548553
.expect("Illegal harness provided to libafl.");
549554

555+
// early duplicate the stderr fd so we can close it later for the target
556+
#[cfg(unix)]
557+
{
558+
use std::{
559+
os::fd::{AsRawFd, FromRawFd},
560+
str::FromStr,
561+
};
562+
563+
let stderr_fd = std::env::var(STDERR_FD_VAR)
564+
.map_err(Error::from)
565+
.and_then(|s| RawFd::from_str(&s).map_err(Error::from))
566+
.unwrap_or_else(|_| {
567+
let stderr = libc::dup(stderr().as_raw_fd());
568+
std::env::set_var(STDERR_FD_VAR, stderr.to_string());
569+
stderr
570+
});
571+
let stderr = File::from_raw_fd(stderr_fd);
572+
env_logger::builder()
573+
.parse_default_env()
574+
.target(Target::Pipe(Box::new(stderr)))
575+
.init();
576+
}
577+
550578
// it appears that no one, not even libfuzzer, uses this return value
551579
// https://github.com/llvm/llvm-project/blob/llvmorg-15.0.7/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L648
552580
libafl_targets_libfuzzer_init(argc, argv);

0 commit comments

Comments
 (0)