-
-
Notifications
You must be signed in to change notification settings - Fork 465
fix: making pre/post_exec to use correct observers in case of withObserver #3747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -380,15 +380,17 @@ where | |||||||||||||||||
| T: CommandConfigurator<Child> + HasTimeout + Debug, | ||||||||||||||||||
| OT: ObserversTuple<I, S>, | ||||||||||||||||||
| { | ||||||||||||||||||
| fn execute_input_with_command<TB: ToTargetBytesConverter<I, S>>( | ||||||||||||||||||
| /// Use this when wrapping with `WithObservers` - build `CommandExecutor` with | ||||||||||||||||||
| /// empty observers `()`, wrap with `WithObservers`, and let the fuzzer manage | ||||||||||||||||||
| /// the observer lifecycle. | ||||||||||||||||||
| fn execute_command_only<TB: ToTargetBytesConverter<I, S>>( | ||||||||||||||||||
| &mut self, | ||||||||||||||||||
| target_bytes_converter: &mut TB, | ||||||||||||||||||
| state: &mut S, | ||||||||||||||||||
| input: &I, | ||||||||||||||||||
| ) -> Result<ExitKind, Error> { | ||||||||||||||||||
| use wait_timeout::ChildExt; | ||||||||||||||||||
|
|
||||||||||||||||||
| self.observers_mut().pre_exec_all(state, input)?; | ||||||||||||||||||
| *state.executions_mut() += 1; | ||||||||||||||||||
| let mut child = self | ||||||||||||||||||
| .configurator | ||||||||||||||||||
|
|
@@ -427,6 +429,19 @@ where | |||||||||||||||||
| self.observers_mut().index_mut(&stdout_handle).observe(buf); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| Ok(exit_kind) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn execute_input_with_command<TB: ToTargetBytesConverter<I, S>>( | ||||||||||||||||||
| &mut self, | ||||||||||||||||||
| target_bytes_converter: &mut TB, | ||||||||||||||||||
| state: &mut S, | ||||||||||||||||||
| input: &I, | ||||||||||||||||||
| ) -> Result<ExitKind, Error> { | ||||||||||||||||||
| self.observers_mut().pre_exec_all(state, input)?; | ||||||||||||||||||
|
|
||||||||||||||||||
| let exit_kind = self.execute_command_only(target_bytes_converter, state, input)?; | ||||||||||||||||||
|
|
||||||||||||||||||
| self.observers_mut() | ||||||||||||||||||
| .post_exec_child_all(state, input, &exit_kind)?; | ||||||||||||||||||
| Ok(exit_kind) | ||||||||||||||||||
|
|
@@ -955,4 +970,99 @@ mod tests { | |||||||||||||||||
|
|
||||||||||||||||||
| assert!(executor.observers.0.output.is_some()); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Test that WithObservers correctly calls pre_exec and post_exec on an observer list (tuple) | ||||||||||||||||||
| #[test] | ||||||||||||||||||
| #[cfg_attr(miri, ignore)] | ||||||||||||||||||
| fn test_with_observers() { | ||||||||||||||||||
| use libafl_bolts::tuples::{Handled, MatchNameRef}; | ||||||||||||||||||
| use tuple_list::tuple_list; | ||||||||||||||||||
|
|
||||||||||||||||||
| use crate::{ | ||||||||||||||||||
| executors::{HasObservers, WithObservers}, | ||||||||||||||||||
| observers::TimeObserver, | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| let mut mgr: SimpleEventManager<NopInput, _, NopState<NopInput>> = | ||||||||||||||||||
| SimpleEventManager::new(SimpleMonitor::new(|status| { | ||||||||||||||||||
| log::info!("{status}"); | ||||||||||||||||||
| })); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Create CommandExecutor with EMPTY observers | ||||||||||||||||||
| #[cfg(windows)] | ||||||||||||||||||
| let executor = CommandExecutor::builder() | ||||||||||||||||||
| .program("cmd") | ||||||||||||||||||
| .arg("/c") | ||||||||||||||||||
| .arg("echo") | ||||||||||||||||||
| .input(InputLocation::Arg { argnum: 2 }); | ||||||||||||||||||
| #[cfg(not(windows))] | ||||||||||||||||||
| let executor = CommandExecutor::builder() | ||||||||||||||||||
| .program("echo") | ||||||||||||||||||
| .input(InputLocation::Arg { argnum: 0 }); | ||||||||||||||||||
|
|
||||||||||||||||||
| let executor = executor.build::<BytesInput, (), _>(()).unwrap(); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Assert that the CommandExecutor has NO observers (empty tuple) | ||||||||||||||||||
| { | ||||||||||||||||||
| use crate::executors::HasObservers; | ||||||||||||||||||
|
||||||||||||||||||
| use crate::executors::HasObservers; |
Copilot
AI
Mar 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assertion that at least one runtime has as_micros() > 0 can be flaky on fast systems/VMs where the echo process may complete within the same microsecond, yielding a Duration that rounds down to 0µs. Since the test already asserts both observers have last_runtime().is_some(), consider dropping the non-zero micros assertion or using a weaker check (e.g., as_nanos() > 0 or simply validating both are Some).
| // Both observers should have recorded similar runtimes (same execution) | |
| let runtime1 = time_obs1.last_runtime().unwrap(); | |
| let runtime2 = time_obs2.last_runtime().unwrap(); | |
| assert!( | |
| runtime1.as_micros() > 0 || runtime2.as_micros() > 0, | |
| "At least one observer should have recorded non-zero runtime" | |
| ); |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,7 @@ pub struct WithObservers<E, I, OT, S> { | |||||||||||
| impl<E, EM, I, OT, S, Z> Executor<EM, I, S, Z> for WithObservers<E, I, OT, S> | ||||||||||||
| where | ||||||||||||
| E: Executor<EM, I, S, Z>, | ||||||||||||
| OT: ObserversTuple<I, S>, | ||||||||||||
| { | ||||||||||||
| fn run_target( | ||||||||||||
| &mut self, | ||||||||||||
|
|
@@ -29,7 +30,10 @@ where | |||||||||||
| mgr: &mut EM, | ||||||||||||
| input: &I, | ||||||||||||
| ) -> Result<ExitKind, Error> { | ||||||||||||
| self.executor.run_target(fuzzer, state, mgr, input) | ||||||||||||
| self.observers.pre_exec_all(state, input)?; | ||||||||||||
| let exit_kind = self.executor.run_target(fuzzer, state, mgr, input)?; | ||||||||||||
| self.observers.post_exec_all(state, input, &exit_kind)?; | ||||||||||||
| Ok(exit_kind) | ||||||||||||
|
Comment on lines
+33
to
+36
|
||||||||||||
| self.observers.pre_exec_all(state, input)?; | |
| let exit_kind = self.executor.run_target(fuzzer, state, mgr, input)?; | |
| self.observers.post_exec_all(state, input, &exit_kind)?; | |
| Ok(exit_kind) | |
| self.executor.run_target(fuzzer, state, mgr, input) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
execute_input_with_commandcallsself.observers_mut().pre_exec_all(...)and then callsexecute_command_only, which currently also callspre_exec_all(...). This results in a guaranteed doublepre_execfor every command execution (even withoutWithObservers), which can corrupt observer state and adds overhead. Makeexecute_command_onlytruly “command only” by removing observer lifecycle calls from it, and keeppre_exec_allin only one place (either the caller or the callee, but not both).