puffin/fuzzer/
libafl_setup.rs

1use core::time::Duration;
2use std::fmt;
3use std::marker::PhantomData;
4
5use libafl::corpus::ondisk::OnDiskMetadataFormat;
6use libafl::prelude::*;
7use libafl_bolts::prelude::*;
8use log4rs::Handle;
9use serde::de::DeserializeOwned;
10use serde::Serialize;
11
12use super::harness;
13use crate::fuzzer::bit_mutations::{
14    bit_mutations_dy, havoc_mutations_dy, MakeMessage, ReadMessage,
15};
16pub(crate) use crate::fuzzer::config::FuzzerConfig;
17use crate::fuzzer::config::{FuzzingTarget, MIN_BIT_CORPUS, MIN_BIT_EXECS};
18use crate::fuzzer::feedback::{MinimizingFeedback, ObjectiveFeedback};
19use crate::fuzzer::mutations::{dy_mutations, MutationConfig};
20use crate::fuzzer::stages::FocusScheduledMutator;
21use crate::fuzzer::stats_monitor::StatsMonitor;
22use crate::fuzzer::stats_stage::{StatsStage, CORPUS_EXEC, CORPUS_EXEC_MINIMAL};
23use crate::log::{load_fuzzing_client, set_experiment_fuzzing_client};
24use crate::protocol::{build_corpus, ProtocolBehavior, ProtocolTypes};
25use crate::put::PutDescriptor;
26use crate::put_registry::PutRegistry;
27use crate::trace::{ConfigTrace, Spawner, Trace, TraceContext};
28
29pub const MAP_FEEDBACK_NAME: &str = "edges";
30const EDGES_OBSERVER_NAME: &str = "edges_observer";
31
32type ConcreteExecutor<'harness, EM, H, I, OT, S, Z> =
33    InProcessExecutor<'harness, EM, H, I, OT, S, Z>;
34
35type ConcreteState<C, R, SC, I> = StdState<C, I, R, SC>;
36
37struct RunClientBuilder<'harness, H, C, R, SC, EM, F, OF, OT, CS, PT>
38where
39    PT: ProtocolTypes + 'static,
40{
41    config: FuzzerConfig,
42
43    harness_fn: &'harness mut H,
44    existing_state: Option<ConcreteState<C, R, SC, Trace<PT>>>,
45    rand: Option<R>,
46    objective_corpus: Option<SC>,
47    corpus: Option<C>,
48    scheduler: Option<CS>,
49    event_manager: EM,
50    observers: Option<OT>,
51    feedback: Option<F>,
52    objective: Option<OF>,
53    initial_inputs: Option<Vec<(Trace<PT>, &'static str)>>,
54    phantom_data: PhantomData<PT>,
55}
56
57impl<'harness, H, C, R, SC, EM, F, OF, OT, CS, PT>
58    RunClientBuilder<'harness, H, C, R, SC, EM, F, OF, OT, CS, PT>
59where
60    C: Corpus<Trace<PT>> + Serialize + DeserializeOwned,
61    R: Rand,
62    SC: Corpus<Trace<PT>> + Serialize + DeserializeOwned,
63    H: FnMut(&Trace<PT>) -> ExitKind,
64    CS: Scheduler<Trace<PT>, ConcreteState<C, R, SC, Trace<PT>>>,
65    F: Feedback<EM, Trace<PT>, OT, ConcreteState<C, R, SC, Trace<PT>>>,
66    OF: Feedback<EM, Trace<PT>, OT, ConcreteState<C, R, SC, Trace<PT>>>,
67    OT: ObserversTuple<Trace<PT>, ConcreteState<C, R, SC, Trace<PT>>>
68        + Serialize
69        + DeserializeOwned,
70    EM: EventFirer<Trace<PT>, ConcreteState<C, R, SC, Trace<PT>>>
71        + EventRestarter<ConcreteState<C, R, SC, Trace<PT>>>
72        + SendExiting
73        + EventReceiver<Trace<PT>, ConcreteState<C, R, SC, Trace<PT>>>
74        + ProgressReporter<ConcreteState<C, R, SC, Trace<PT>>>,
75    ConcreteState<C, R, SC, Trace<PT>>: HasClientPerfMonitor + HasMetadata + HasExecutions,
76    PT: ProtocolTypes + 'static,
77{
78    fn new(
79        config: FuzzerConfig,
80        harness_fn: &'harness mut H,
81        existing_state: Option<ConcreteState<C, R, SC, Trace<PT>>>,
82        event_manager: EM,
83    ) -> Self {
84        Self {
85            config,
86            harness_fn,
87            existing_state,
88            rand: None,
89            objective_corpus: None,
90            corpus: None,
91            scheduler: None,
92            event_manager,
93            observers: None,
94            feedback: None,
95            objective: None,
96            initial_inputs: None,
97            phantom_data: Default::default(),
98        }
99    }
100
101    fn with_rand(mut self, rand: R) -> Self {
102        self.rand = Some(rand);
103        self
104    }
105
106    fn with_corpus(mut self, corpus: C) -> Self {
107        self.corpus = Some(corpus);
108        self
109    }
110
111    fn with_objective_corpus(mut self, objective_corpus: SC) -> Self {
112        self.objective_corpus = Some(objective_corpus);
113        self
114    }
115
116    fn with_scheduler(mut self, scheduler: CS) -> Self {
117        self.scheduler = Some(scheduler);
118        self
119    }
120
121    fn with_feedback(mut self, feedback: F) -> Self {
122        self.feedback = Some(feedback);
123        self
124    }
125
126    fn with_objective(mut self, objective: OF) -> Self {
127        self.objective = Some(objective);
128        self
129    }
130
131    fn with_observers(mut self, observers: OT) -> Self {
132        self.observers = Some(observers);
133        self
134    }
135
136    fn with_initial_inputs(mut self, initial_inputs: Vec<(Trace<PT>, &'static str)>) -> Self {
137        self.initial_inputs = Some(initial_inputs);
138        self
139    }
140
141    fn run_client<PB>(mut self, put_registry: &'harness PutRegistry<PB>) -> Result<(), Error>
142    where
143        PB: ProtocolBehavior<ProtocolTypes = PT> + 'static,
144    {
145        let mut feedback = self.feedback.unwrap();
146        let mut objective = self.objective.unwrap();
147
148        // If not restarting, create a State from scratch
149        let mut state = self.existing_state.unwrap_or_else(|| {
150            StdState::new(
151                self.rand.unwrap(),
152                self.corpus.unwrap(),
153                self.objective_corpus.unwrap(),
154                &mut feedback,
155                &mut objective,
156            )
157            .unwrap()
158        });
159
160        let FuzzerConfig {
161            initial_corpus_dir,
162            initial_corpus,
163            max_iters,
164            mutation_config,
165            ..
166        } = self.config;
167
168        /*
169        Standard AFL-like configuration:
170        A: Main mutator with HavocScheduledMutator
171            1. Compute the number `im` of iterations of stacked mutations: 1 << (1 + rand(0 <= r <= 7))
172            2. For each time (0..im) : Apply randomly a mutation from the given list (here havoc)
173          ==> let mutator = HavocScheduledMutator::new(...);
174        B: Main mutational stage with StdMutationalStage:
175            1. Take the scheduled input from the corpus
176            2. Pick a random iterations is between 1 and 128 (default)
177            3. For each 0..is: clone input, mutate using mutator, execute
178          ==> let mut stages = tuple_list!(StdMutationalStage::new(mutator));
179        C: We provide a list of stages: all of them are run one after the other, on the same scheduled testcase though.
180        Note: We might want to add more stages in the future, in particular https://docs.rs/libafl/0.15.0/libafl/stages/tmin/struct.StdTMinMutationalStage.html; see https://docs.rs/libafl/0.15.0/libafl/stages/index.html#modules
181        ------------
182        We adapt this to our specific setup:
183           ==> let mut stages = tuple_list!(stage_dy, stage_bit);
184        where:
185         - stage_dy is a HavocScheduledMutator stage over DY mutations, enabled when DY mutations are
186         - stage_bit is a HavocScheduledMutator with only 1 run stage over bit-level mutations, enabled
187         when bit mutations are enabled and when sufficiently many executions and corpus testcases have been done/found
188
189         We refine this initial design below with the addition of a Focused stage where the payload
190         on which we'll apply HAVOC mutations is first randomly chosen for the whole mutational stage.
191        */
192
193        // ==== DY mutational stage
194        let mutation_config_dy = MutationConfig {
195            with_focus: false,
196            ..mutation_config
197        };
198        // DY stack size: replace AFL's log-uniform 1<<(1+rand(0..7)) (mean ~36, median 16 -- tuned
199        // for flat byte inputs) with a truncated-geometric concentrated on 1-2 semantic edits
200        // (GeometricStackMutator; mean ~2, light tail to 16). For grammar/DY tree mutation, heavy
201        // stacking destroys deep structure and stores ~all edits as coverage hitchhikers.
202        // (FixedStackMutator in stages.rs is available to force an exact stack size for
203        // experiments.) Wrap the geometric stacking mutator so that, when enabled
204        // (default), every anchor picked by the mutations stacked in one stage is confined
205        // to a single random step -- avoiding the "coverage hitchhiker" where one stacked
206        // edit gains coverage while another breaks a later step yet the broken-tailed trace
207        // is still saved. Disable with `--no-step-lock`.
208        let mutator_dy = crate::fuzzer::stages::StepLockedStackMutator::new(
209            crate::fuzzer::stages::GeometricStackMutator::new(HavocScheduledMutator::new(
210                dy_mutations(mutation_config_dy, <PT>::signature(), put_registry),
211            )),
212            self.config.mutation_stage_config.step_locked_stacking,
213        );
214        // Always run DY mutations (if enabled)
215        let cb_dy = |_: &mut _, _: &mut _, _: &mut _, _: &mut _| -> Result<bool, Error> {
216            if mutation_config.with_dy {
217                log::debug!("[*] DY StdMutationalStage");
218                Ok(true)
219            } else {
220                Ok(false)
221            }
222        };
223        let stage_dy = IfStage::new(
224            cb_dy,
225            tuple_list!(StdMutationalStage::with_max_iterations(
226                mutator_dy,
227                self.config.mutation_stage_config.max_iterations_per_stage
228            )),
229        );
230
231        // ==== Bit-level mutational stage
232        let mutation_config_bit = MutationConfig {
233            with_focus: false,
234            ..mutation_config
235        };
236        let mutator_bit = HavocScheduledMutator::new(bit_mutations_dy::<
237            StdState<C, Trace<PT>, R, SC>,
238            PT,
239            PB,
240        >(mutation_config_bit, put_registry));
241        // Run bit-level muts. if bit-level enabled + already sufficiently advanced (to save a bit
242        // of time)
243        let cb_bit_level = |_: &mut _,
244                            _: &mut _,
245                            state: &mut ConcreteState<C, R, SC, Trace<PT>>,
246                            _: &mut _|
247         -> Result<bool, Error> {
248            if !mutation_config.with_bit_level {
249                return Ok(false);
250            }
251            // Return false if the campaign is not advanced enough (per client/core), except if no
252            // DY
253            if !mutation_config.with_dy
254                || (*state.executions() > MIN_BIT_EXECS && state.corpus().count() > MIN_BIT_CORPUS)
255            {
256                log::debug!("[*] BIT StdMutationalStage");
257                Ok(true)
258            } else {
259                Ok(false)
260            }
261        };
262        let mutation_config_focus = mutation_config; // focus is already sets to the wanted value
263        let mutator_bit_focus = FocusScheduledMutator::new(
264            tuple_list!(MakeMessage::new(mutation_config_focus, put_registry)),
265            havoc_mutations_dy::<StdState<C, Trace<PT>, R, SC>, PT>(mutation_config_focus),
266            tuple_list!(ReadMessage::new(mutation_config_focus, put_registry)),
267        );
268        let cb_focus_bit_level = |_: &mut _,
269                                  _: &mut _,
270                                  _: &mut ConcreteState<C, R, SC, Trace<PT>>,
271                                  _: &mut _|
272         -> Result<bool, Error> {
273            if !mutation_config.with_focus {
274                return Ok(false);
275            }
276            log::debug!("[*] BIT FocusScheduledMutator");
277            Ok(true)
278        };
279        let stage_bit = IfStage::new(
280            cb_bit_level,
281            tuple_list!(
282                StdMutationalStage::with_max_iterations(
283                    mutator_bit,
284                    self.config.mutation_stage_config.max_iterations_per_stage
285                ), // Old-style HAVOC stage
286                IfStage::new(
287                    cb_focus_bit_level,
288                    tuple_list!(StdMutationalStage::with_max_iterations(
289                        mutator_bit_focus,
290                        self.config.mutation_stage_config.max_iterations_per_stage
291                    ),)
292                ),
293            ), // Focus stage, first MakeMessage, then HAVOC, then ReadMessage
294        );
295        // A stage that only enables in introspection mode and executes each testcase in corpus
296        // prior to executing other stages on it
297        let stage_test_input = ClosureStage::new(
298            |_: &mut _,
299             _: &mut _,
300             cs: &mut ConcreteState<C, R, SC, Trace<PT>>,
301             _: &mut _|
302             -> Result<(), Error> {
303                if cfg!(feature = "introspection") {
304                    CORPUS_EXEC.increment();
305                    log::debug!("[*] Introspection stage");
306
307                    // If there is no current testcase (e.g., empty corpus / no scheduled item),
308                    // just skip.
309                    let Some(current_idx) = cs.corpus().current() else {
310                        return Ok(());
311                    };
312
313                    let mut current_testcase = cs.corpus().get(*current_idx)?.borrow_mut();
314                    // Input will already be loaded.
315                    let current_input = current_testcase.load_input(cs.corpus()).unwrap();
316                    let spawner = Spawner::new(put_registry.clone());
317                    let mut ctx = TraceContext::new_config(
318                        spawner,
319                        ConfigTrace {
320                            with_bit_level: self.config.mutation_config.with_bit_level,
321                            ..Default::default()
322                        },
323                    );
324                    let error_ok;
325                    match current_input.execute_until_step_wrap(
326                        &mut ctx,
327                        current_input.len(),
328                        &mut 0,
329                        true,
330                    ) {
331                        Ok(()) => {
332                            CORPUS_EXEC_MINIMAL.increment();
333                            return Ok(());
334                        }
335                        Err(e) => match e {
336                            crate::error::Error::Put(_) => {
337                                error_ok = true;
338                            }
339                            crate::error::Error::SecurityClaim(_) => {
340                                error_ok = true;
341                            }
342                            _ => {
343                                // Other error: not OK (no increment of CORPUS_EXEC_SUCCESS)
344                                return Ok(());
345                            }
346                        },
347                    }
348                    if error_ok {
349                        // If it failed because of the PUT or the security claim, we only consider
350                        // the testcase to be "minimal" if it failed at the very last step
351                        if ctx.executed_until == current_input.len() - 1 {
352                            CORPUS_EXEC_MINIMAL.increment();
353                            return Ok(());
354                        }
355                    }
356                }
357                Ok(())
358            },
359        );
360
361        // ==== All stages put together
362        let mut stages = tuple_list!(stage_test_input, stage_dy, stage_bit, StatsStage::new());
363
364        let mut fuzzer: StdFuzzer<CS, F, _, _, OF> =
365            StdFuzzer::new(self.scheduler.unwrap(), feedback, objective);
366
367        let mut executor: ConcreteExecutor<
368            'harness,
369            EM,
370            H,
371            Trace<PT>,
372            OT,
373            ConcreteState<C, R, SC, Trace<PT>>,
374            StdFuzzer<CS, F, _, _, OF>,
375        > = InProcessExecutor::with_timeout(
376            self.harness_fn,
377            // hint: edges_observer is expensive to serialize (only noticeable if we add all
378            // inputs to the corpus)
379            self.observers.unwrap(),
380            &mut fuzzer,
381            &mut state,
382            &mut self.event_manager,
383            Duration::new(5, 0),
384        )?;
385
386        // In case the corpus is empty (on first run), reset
387        if state.corpus().is_empty() {
388            if initial_corpus.is_none() && initial_corpus_dir.exists() {
389                state
390                    .load_initial_inputs(
391                        &mut fuzzer,
392                        &mut executor,
393                        &mut self.event_manager,
394                        &[initial_corpus_dir.clone()],
395                    )
396                    .unwrap_or_else(|err| {
397                        panic!(
398                            "Failed to load initial corpus at {:?}: {}",
399                            &initial_corpus_dir, err
400                        )
401                    });
402                log::info!("Imported {} inputs from disk.", state.corpus().count());
403            } else {
404                if initial_corpus.is_some() {
405                    log::info!(
406                        "--corpus given: using the requested seed corpus instead of {:?}.",
407                        &initial_corpus_dir
408                    );
409                } else {
410                    log::info!("Initial seed corpus not found. Using embedded `default` seeds.");
411                }
412
413                for (seed, name) in self.initial_inputs.unwrap() {
414                    log::info!("Using seed {}", name);
415                    fuzzer
416                        .add_input(&mut state, &mut executor, &mut self.event_manager, seed)
417                        .expect("Failed to add input");
418                }
419            }
420        }
421
422        if let Some(max_iters) = max_iters {
423            fuzzer.fuzz_loop_for(
424                &mut stages,
425                &mut executor,
426                &mut state,
427                &mut self.event_manager,
428                max_iters,
429            )?;
430        } else {
431            fuzzer.fuzz_loop(
432                &mut stages,
433                &mut executor,
434                &mut state,
435                &mut self.event_manager,
436            )?;
437        }
438        Ok(())
439    }
440}
441
442type EdgesObserver = HitcountsMapObserver<StdMapObserver<'static, u8, false>>;
443type EdgesTracking = ExplicitTracking<EdgesObserver, true, false>;
444
445type ConcreteObservers<'a> = (EdgesTracking, (TimeObserver, ()));
446
447type ConcreteFeedback<'a> =
448    CombinedFeedback<MaxMapFeedback<EdgesTracking, EdgesObserver>, TimeFeedback, LogicEagerOr>;
449
450impl<'harness, 'a, H, SC, C, R, EM, OF, CS, PT>
451    RunClientBuilder<
452        'harness,
453        H,
454        C,
455        R,
456        SC,
457        EM,
458        CombinedFeedback<
459            MinimizingFeedback<
460                StdState<
461                    CachedOnDiskCorpus<Trace<PT>>,
462                    Trace<PT>,
463                    RomuDuoJrRand,
464                    CachedOnDiskCorpus<Trace<PT>>,
465                >,
466                PT,
467            >,
468            ConcreteFeedback<'a>,
469            LogicEagerOr,
470        >,
471        OF,
472        ConcreteObservers<'a>,
473        CS,
474        PT,
475    >
476where
477    Trace<PT>: Input,
478    //ConcreteState<C, R, SC, Trace<PT>>: Input,
479    C: Corpus<Trace<PT>> + fmt::Debug,
480    R: Rand,
481    SC: Corpus<Trace<PT>> + fmt::Debug,
482    H: FnMut(&Trace<PT>) -> ExitKind,
483    OF: Feedback<EM, Trace<PT>, ConcreteObservers<'a>, ConcreteState<C, R, SC, Trace<PT>>>,
484    CS: Scheduler<Trace<PT>, ConcreteState<C, R, SC, Trace<PT>>>,
485    EM: EventFirer<Trace<PT>, ConcreteState<C, R, SC, Trace<PT>>>
486        + EventRestarter<ConcreteState<C, R, SC, Trace<PT>>>
487        + HasEventManagerId
488        + ProgressReporter<ConcreteState<C, R, SC, Trace<PT>>>,
489    ConcreteState<C, R, SC, Trace<PT>>: HasClientPerfMonitor + HasMetadata + HasExecutions,
490    PT: ProtocolTypes + 'static,
491{
492    fn create_feedback_observers(&self) -> (ConcreteFeedback<'a>, ConcreteObservers<'a>) {
493        #[cfg(not(test))]
494        let map = unsafe {
495            pub use libafl_targets::{EDGES_MAP, MAX_EDGES_FOUND};
496            &mut EDGES_MAP[0..MAX_EDGES_FOUND]
497        };
498
499        #[cfg(test)]
500        let map = unsafe {
501            // When testing we should not import libafl_targets, else it conflicts with sancov_dummy
502            pub const EDGES_MAP_SIZE: usize = 65536;
503            pub static mut EDGES_MAP: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE];
504            pub static mut MAX_EDGES_FOUND: usize = 0;
505            &mut EDGES_MAP[0..MAX_EDGES_FOUND]
506        };
507
508        {
509            let time_observer = TimeObserver::new("time");
510            let edges_observer =
511                HitcountsMapObserver::new(unsafe { StdMapObserver::new(EDGES_OBSERVER_NAME, map) });
512
513            let edges_observer = edges_observer.track_indices();
514
515            let map_feedback = MaxMapFeedback::with_name(MAP_FEEDBACK_NAME, &edges_observer);
516
517            let feedback = feedback_or!(
518                // New maximization map feedback linked to the edges observer and the feedback
519                // state `track_indexes` needed because of
520                // IndexesLenTimeMinimizerCorpusScheduler
521                map_feedback,
522                // Time feedback, this one does not need a feedback state
523                // needed for IndexesLenTimeMinimizerCorpusScheduler
524                TimeFeedback::new(&time_observer)
525            );
526            let observers = tuple_list!(edges_observer, time_observer);
527            (feedback, observers)
528        }
529    }
530}
531
532/// Starts the fuzzing loop
533pub fn start<PB>(
534    put_registry: &PutRegistry<PB>,
535    config: FuzzerConfig,
536    log_handle: Handle,
537) -> Result<(), Error>
538where
539    PB: ProtocolBehavior + Clone + 'static,
540{
541    let FuzzerConfig {
542        core_definition,
543        corpus_dir,
544        objective_dir,
545        static_seed: _,
546        stats_file,
547        stats_interval,
548        broker_port,
549        tui,
550        no_launcher,
551        mutation_stage_config,
552        is_experiment,
553        log_folder,
554        verbosity,
555        target,
556        initial_corpus,
557        ..
558    } = &config;
559
560    log::info!("Running on cores: {}", &core_definition);
561    log::info!("Config: {:?}\n\nlog_handle: {:?}", &config, &log_handle);
562
563    let mut run_client = |state: Option<StdState<_, Trace<PB::ProtocolTypes>, _, _>>,
564                          event_manager: LlmpRestartingEventManager<
565        _,
566        Trace<PB::ProtocolTypes>,
567        StdState<_, Trace<PB::ProtocolTypes>, _, _>,
568        _,
569        StdShMemProvider,
570    >,
571                          _client_description: ClientDescription|
572     -> Result<(), Error> {
573        if *is_experiment {
574            log_handle
575                .clone()
576                .set_config(set_experiment_fuzzing_client(log_folder, *verbosity));
577        } else {
578            log_handle.clone().set_config(load_fuzzing_client());
579        }
580        log::info!("log_handle: {:?}", &log_handle);
581
582        let default_put = put_registry.default_put_descriptor();
583
584        // Choose a harness to do single target/differential fuzzing
585        // We can't directly return a closure since they don't have the same
586        // signature so we return a boxed closure that we call in the next closure
587        let mut boxed_harness_fn: Box<dyn FnMut(&Trace<PB::ProtocolTypes>) -> ExitKind> =
588            match target {
589                FuzzingTarget::Single(put) => {
590                    let put_desc = if let Some(put_desc) = put {
591                        put_desc
592                    } else {
593                        &default_put
594                    };
595                    Box::new(|input: &_| harness::harness::<PB>(put_registry, put_desc, input))
596                }
597                FuzzingTarget::Differential(first, second) => Box::new(|input: &_| {
598                    harness::differential_harness::<PB>(put_registry, first, second, input)
599                }),
600            };
601
602        let harness_fn = &mut (|input: &_| boxed_harness_fn(input));
603
604        // The PUT(s) that will actually run the fuzzing campaign are already known from
605        // `target`, so the initial seed corpus is restricted to what they support instead of
606        // always checking against the registry's default PUT.
607        let requested_puts: Vec<PutDescriptor> = match target {
608            FuzzingTarget::Single(put) => vec![put.clone().unwrap_or_else(|| default_put.clone())],
609            FuzzingTarget::Differential(first, second) => vec![first.clone(), second.clone()],
610        };
611
612        // Only used if no `--corpus` was given and `initial_corpus_dir` does not exist
613        let default_corpus = vec!["default".to_string()];
614        let corpus_list: &[String] = initial_corpus.as_deref().unwrap_or(&default_corpus);
615
616        let mut builder = RunClientBuilder::new(config.clone(), harness_fn, state, event_manager);
617        builder = builder
618            .with_initial_inputs(build_corpus(put_registry, &requested_puts, corpus_list))
619            .with_rand(StdRand::new())
620            .with_corpus(
621                //InMemoryCorpus::new(),
622                CachedOnDiskCorpus::with_meta_format(
623                    corpus_dir.clone(),
624                    4096, // mimicking libafl_sugar: https://github.com/AFLplusplus/LibAFL/blob/8445ae54b34a6cea48ae243d40bb1b1b94493898/libafl_sugar/src/lib.rs#L78
625                    Some(OnDiskMetadataFormat::Json),
626                )
627                .unwrap(),
628            )
629            .with_objective_corpus(
630                CachedOnDiskCorpus::with_meta_format(
631                    objective_dir.clone(),
632                    4096, // mimicking libafl_sugar: https://github.com/AFLplusplus/LibAFL/blob/8445ae54b34a6cea48ae243d40bb1b1b94493898/libafl_sugar/src/lib.rs#L78
633                    Some(OnDiskMetadataFormat::Json),
634                )
635                .unwrap(),
636            )
637            .with_objective(feedback_or_fast!(
638                // don't execute second if first is conclusive, mimicking https://github.com/AFLplusplus/LibAFL/blob/8445ae54b34a6cea48ae243d40bb1b1b94493898/libafl_sugar/src/inmemory.rs#L164
639                ObjectiveFeedback::new(),
640                CrashFeedback::new(),
641                TimeoutFeedback::new()
642            ));
643
644        //#[cfg(feature = "sancov")]
645        //{
646        /* let (feedback, observers) = builder.create_feedback_observers(); */
647        /* builder = builder */
648        /* .with_feedback(feedback) */
649        /* .with_observers(observers) */
650        /* .with_scheduler(IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new())); */
651        //}
652
653        //#[cfg(not(feature = "sancov"))]
654        {
655            // FIXME
656            log::warn!("Running without minimizer is unsupported");
657            let (feedback, observer) = builder.create_feedback_observers();
658            let feedback_with_minimizer = feedback_or!(
659                MinimizingFeedback::new(mutation_stage_config.with_truncation),
660                feedback
661            );
662            #[cfg(feature = "with-min-scheduler")]
663            let scheduler = IndexesLenTimeMinimizerScheduler::new(QueueScheduler::new());
664            #[cfg(not(feature = "with-min-scheduler"))]
665            let scheduler = RandScheduler::new();
666
667            builder = builder
668                .with_feedback(feedback_with_minimizer)
669                .with_observers(observer)
670                .with_scheduler(scheduler);
671        } // TODO:EVAL investigate using QueueScheduler instead (see https://github.com/AFLplusplus/LibAFL/blob/8445ae54b34a6cea48ae243d40bb1b1b94493898/libafl_sugar/src/inmemory.rs#L190)
672          // TODO:EVAL: investigate this versus Rand, versus Queue, versus Minimizer
673
674        builder.run_client(put_registry)
675    };
676
677    if *no_launcher {
678        let stats_monitor = StatsMonitor::with_raw_output(stats_file.clone(), *stats_interval);
679
680        let (state, restarting_mgr) =
681            setup_restarting_mgr_std(stats_monitor, *broker_port, EventConfig::AlwaysUnique)?;
682
683        run_client(
684            state,
685            restarting_mgr,
686            ClientDescription::new(0, 0, CoreId(0)),
687        )
688    } else {
689        let cores = Cores::from_cmdline(config.core_definition.as_str()).unwrap();
690        let configuration: EventConfig = "launcher default".into();
691        let sh_mem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
692
693        // NOTE tlspuffin's fuzzer should never write to stdout
694        //
695        // During fuzzing the logs are redirected to `log_file` (which is
696        // usually `tlspuffin.log`) and there should be no reason to print
697        // directly to stdout. We should therefore be able to safely discard the
698        // log output of clients.
699        //
700        // To verify this assumption, we save the clients' output to a file that
701        // should always be empty.
702        let out_path = log_folder.join("puffin_main_broker_stdout.log");
703        let out_file = out_path
704            .to_str()
705            .expect("failed to create path to redirect fuzzer clients' stdout");
706        let err_path = log_folder.join("puffin_main_broker_stderr.log");
707        let err_file = err_path
708            .to_str()
709            .expect("failed to create path to redirect fuzzer clients' stderr");
710
711        if *tui {
712            let stats_monitor = StatsMonitor::with_tui_output(stats_file.clone(), *stats_interval);
713
714            Launcher::builder()
715                .shmem_provider(sh_mem_provider)
716                .configuration(configuration)
717                .monitor(stats_monitor)
718                .run_client(&mut run_client)
719                .cores(&cores)
720                .broker_port(*broker_port)
721                .stdout_file(Some(out_file))
722                .stderr_file(Some(err_file))
723                .build()
724                .launch()
725        } else {
726            let stats_monitor = StatsMonitor::with_raw_output(stats_file.clone(), *stats_interval);
727
728            Launcher::builder()
729                .shmem_provider(sh_mem_provider)
730                .configuration(configuration)
731                .monitor(stats_monitor)
732                .run_client(&mut run_client)
733                .cores(&cores)
734                .broker_port(*broker_port)
735                .stdout_file(Some(out_file))
736                .stderr_file(Some(err_file))
737                .build()
738                .launch()
739        }
740    }
741}