Skip to content

Commit 6d5bae6

Browse files
committed
fix: making pre/post_exec to use correct observers in case of withObserver
1 parent c59e61e commit 6d5bae6

File tree

2 files changed

+117
-2
lines changed

2 files changed

+117
-2
lines changed

crates/libafl/src/executors/command.rs

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,10 @@ where
380380
T: CommandConfigurator<Child> + HasTimeout + Debug,
381381
OT: ObserversTuple<I, S>,
382382
{
383-
fn execute_input_with_command<TB: ToTargetBytesConverter<I, S>>(
383+
/// Use this when wrapping with `WithObservers` - build `CommandExecutor` with
384+
/// empty observers `()`, wrap with `WithObservers`, and let the fuzzer manage
385+
/// the observer lifecycle.
386+
fn execute_command_only<TB: ToTargetBytesConverter<I, S>>(
384387
&mut self,
385388
target_bytes_converter: &mut TB,
386389
state: &mut S,
@@ -427,6 +430,19 @@ where
427430
self.observers_mut().index_mut(&stdout_handle).observe(buf);
428431
}
429432

433+
Ok(exit_kind)
434+
}
435+
436+
fn execute_input_with_command<TB: ToTargetBytesConverter<I, S>>(
437+
&mut self,
438+
target_bytes_converter: &mut TB,
439+
state: &mut S,
440+
input: &I,
441+
) -> Result<ExitKind, Error> {
442+
self.observers_mut().pre_exec_all(state, input)?;
443+
444+
let exit_kind = self.execute_command_only(target_bytes_converter, state, input)?;
445+
430446
self.observers_mut()
431447
.post_exec_child_all(state, input, &exit_kind)?;
432448
Ok(exit_kind)
@@ -955,4 +971,99 @@ mod tests {
955971

956972
assert!(executor.observers.0.output.is_some());
957973
}
974+
975+
/// Test that WithObservers correctly calls pre_exec and post_exec on an observer list (tuple)
976+
#[test]
977+
#[cfg_attr(miri, ignore)]
978+
fn test_with_observers() {
979+
use libafl_bolts::tuples::{Handled, MatchNameRef};
980+
use tuple_list::tuple_list;
981+
982+
use crate::{
983+
executors::{HasObservers, WithObservers},
984+
observers::TimeObserver,
985+
};
986+
987+
let mut mgr: SimpleEventManager<NopInput, _, NopState<NopInput>> =
988+
SimpleEventManager::new(SimpleMonitor::new(|status| {
989+
log::info!("{status}");
990+
}));
991+
992+
// Create CommandExecutor with EMPTY observers
993+
#[cfg(windows)]
994+
let executor = CommandExecutor::builder()
995+
.program("cmd")
996+
.arg("/c")
997+
.arg("echo")
998+
.input(InputLocation::Arg { argnum: 2 });
999+
#[cfg(not(windows))]
1000+
let executor = CommandExecutor::builder()
1001+
.program("echo")
1002+
.input(InputLocation::Arg { argnum: 0 });
1003+
1004+
let executor = executor.build::<BytesInput, (), _>(()).unwrap();
1005+
1006+
// Assert that the CommandExecutor has NO observers (empty tuple)
1007+
{
1008+
use crate::executors::HasObservers;
1009+
let inner_observers: &() = &*executor.observers();
1010+
assert_eq!(
1011+
inner_observers,
1012+
&(),
1013+
"CommandExecutor should have empty observers when used with WithObservers"
1014+
);
1015+
}
1016+
1017+
let time_observer1 = TimeObserver::new("time1");
1018+
let time_observer2 = TimeObserver::new("time2");
1019+
let handle1 = time_observer1.handle();
1020+
let handle2 = time_observer2.handle();
1021+
1022+
// Verify observers start with no recorded runtime (pre_exec not yet called)
1023+
assert!(
1024+
time_observer1.last_runtime().is_none(),
1025+
"Observer 1 should have no runtime before execution"
1026+
);
1027+
assert!(
1028+
time_observer2.last_runtime().is_none(),
1029+
"Observer 2 should have no runtime before execution"
1030+
);
1031+
1032+
let observer_list = tuple_list!(time_observer1, time_observer2);
1033+
let mut executor = WithObservers::new(executor, observer_list);
1034+
1035+
let mut fuzzer: NopFuzzer = NopFuzzer::new();
1036+
let mut state = NopState::<NopInput>::new();
1037+
executor
1038+
.run_target(
1039+
&mut fuzzer,
1040+
&mut state,
1041+
&mut mgr,
1042+
&BytesInput::new(b"test".to_vec()),
1043+
)
1044+
.unwrap();
1045+
1046+
// Verify BOTH observers had pre_exec and post_exec called
1047+
let observers = executor.observers();
1048+
1049+
let time_obs1: &TimeObserver = observers.get(&handle1).as_ref().unwrap();
1050+
assert!(
1051+
time_obs1.last_runtime().is_some(),
1052+
"Observer 1: pre_exec and post_exec should have been called (last_runtime is set)"
1053+
);
1054+
1055+
let time_obs2: &TimeObserver = observers.get(&handle2).as_ref().unwrap();
1056+
assert!(
1057+
time_obs2.last_runtime().is_some(),
1058+
"Observer 2: pre_exec and post_exec should have been called (last_runtime is set)"
1059+
);
1060+
1061+
// Both observers should have recorded similar runtimes (same execution)
1062+
let runtime1 = time_obs1.last_runtime().unwrap();
1063+
let runtime2 = time_obs2.last_runtime().unwrap();
1064+
assert!(
1065+
runtime1.as_micros() > 0 || runtime2.as_micros() > 0,
1066+
"At least one observer should have recorded non-zero runtime"
1067+
);
1068+
}
9581069
}

crates/libafl/src/executors/with_observers.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct WithObservers<E, I, OT, S> {
2121
impl<E, EM, I, OT, S, Z> Executor<EM, I, S, Z> for WithObservers<E, I, OT, S>
2222
where
2323
E: Executor<EM, I, S, Z>,
24+
OT: ObserversTuple<I, S>,
2425
{
2526
fn run_target(
2627
&mut self,
@@ -29,7 +30,10 @@ where
2930
mgr: &mut EM,
3031
input: &I,
3132
) -> Result<ExitKind, Error> {
32-
self.executor.run_target(fuzzer, state, mgr, input)
33+
self.observers.pre_exec_all(state, input)?;
34+
let exit_kind = self.executor.run_target(fuzzer, state, mgr, input)?;
35+
self.observers.post_exec_all(state, input, &exit_kind)?;
36+
Ok(exit_kind)
3337
}
3438
}
3539

0 commit comments

Comments
 (0)