tsffs/fuzzer/
mod.rs

1// Copyright (C) 2024 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4//! Fuzzing engine implementation, configure and run LibAFL on a separate thread
5
6use crate::{
7    fuzzer::{
8        executors::inprocess::InProcessExecutor, feedbacks::ReportingMapFeedback,
9        messages::FuzzerMessage,
10    },
11    Tsffs,
12};
13use anyhow::{anyhow, Result};
14use libafl::{
15    feedback_or, feedback_or_fast,
16    inputs::{HasBytesVec, Input},
17    prelude::{
18        havoc_mutations, ondisk::OnDiskMetadataFormat, tokens_mutations, AFLppRedQueen, BytesInput,
19        CachedOnDiskCorpus, Corpus, CrashFeedback, ExitKind, HasCurrentCorpusIdx, HasTargetBytes,
20        HitcountsMapObserver, I2SRandReplace, MaxMapFeedback, OnDiskCorpus, RandBytesGenerator,
21        SimpleEventManager, SimpleMonitor, StdCmpValuesObserver, StdMOptMutator, StdMapObserver,
22        StdScheduledMutator, TimeFeedback, TimeObserver, Tokens,
23    },
24    schedulers::{
25        powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler,
26    },
27    stages::{
28        mutational::MultiMutationalStage, CalibrationStage, ColorizationStage, DumpToDiskStage,
29        GeneralizationStage, IfStage, StdMutationalStage, StdPowerMutationalStage,
30        SyncFromDiskStage, TracingStage,
31    },
32    state::{HasCorpus, HasMetadata, StdState},
33    Fuzzer, StdFuzzer,
34};
35use libafl_bolts::{
36    current_nanos,
37    prelude::{OwnedMutSlice, OwnedRefMut},
38    rands::StdRand,
39    tuples::{tuple_list, Merge},
40    AsMutSlice, AsSlice,
41};
42use libafl_targets::{AFLppCmpLogObserver, AFLppCmplogTracingStage};
43use simics::{api::AsConfObject, debug, trace, warn};
44use std::{
45    cell::RefCell, fmt::Debug, fs::write, io::stderr, slice::from_raw_parts_mut,
46    sync::mpsc::channel, thread::spawn,
47};
48use tokenize::{tokenize_executable_file, tokenize_src_file};
49use tracing::{level_filters::LevelFilter, Level};
50use tracing_subscriber::{
51    filter::filter_fn, fmt, layer::SubscriberExt, registry, util::SubscriberInitExt, Layer,
52};
53
54pub mod executors;
55pub mod feedbacks;
56pub mod messages;
57pub mod tokenize;
58
59#[derive(Clone, PartialEq, Eq)]
60pub(crate) struct Testcase {
61    pub testcase: BytesInput,
62    pub cmplog: bool,
63}
64
65impl Debug for Testcase {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("Testcase")
68            .field(
69                "testcase",
70                &format!(
71                    "{:?}{} ({} bytes)",
72                    &self.testcase.bytes()[..(if self.testcase.bytes().len() < 32 {
73                        self.testcase.bytes().len()
74                    } else {
75                        32
76                    })],
77                    if self.testcase.bytes().len() >= 32 {
78                        "..."
79                    } else {
80                        ""
81                    },
82                    self.testcase.bytes().len()
83                ),
84            )
85            .field("cmplog", &self.cmplog)
86            .finish()
87    }
88}
89
90#[derive(Debug, Clone, Default, PartialEq, Eq)]
91pub(crate) struct ShutdownMessage {}
92
93impl Tsffs {
94    const EDGES_OBSERVER_NAME: &'static str = "coverage";
95    const AFLPP_CMP_OBSERVER_NAME: &'static str = "aflpp_cmplog";
96    const CMPLOG_OBSERVER_NAME: &'static str = "cmplog";
97    const TIME_OBSERVER_NAME: &'static str = "time";
98    const TIMEOUT_FEEDBACK_NAME: &'static str = "time";
99    const CORPUS_CACHE_SIZE: usize = 4096;
100
101    /// Start the fuzzing thread.
102    pub fn start_fuzzer_thread(&mut self) -> Result<()> {
103        if self.fuzz_thread.get().is_some() {
104            warn!(self.as_conf_object(), "Fuzz thread already started but start_fuzzer_thread called. Returning without error.");
105            // We can only start the thread once
106            return Ok(());
107        }
108
109        debug!(self.as_conf_object_mut(), "Starting fuzzer thread");
110
111        let (tx, orx) = channel::<ExitKind>();
112        let (otx, rx) = channel::<Testcase>();
113        let (stx, srx) = channel::<ShutdownMessage>();
114        let (mtx, mrx) = channel::<FuzzerMessage>();
115
116        self.fuzzer_tx
117            .set(tx)
118            .map_err(|_| anyhow!("Fuzzer sender already set"))?;
119        self.fuzzer_rx
120            .set(rx)
121            .map_err(|_| anyhow!("Fuzzer receiver already set"))?;
122        self.fuzzer_shutdown
123            .set(stx)
124            .map_err(|_| anyhow!("Fuzzer shutdown sender already set"))?;
125        self.fuzzer_messages
126            .set(mrx)
127            .map_err(|_| anyhow!("Fuzzer messages receiver already set"))?;
128
129        let client = RefCell::new((otx, orx));
130
131        let coverage_map = unsafe {
132            from_raw_parts_mut(
133                self.coverage_map
134                    .get_mut()
135                    .ok_or_else(|| anyhow!("Coverage map not set"))?
136                    .as_mut_slice()
137                    .as_mut_ptr(),
138                Self::COVERAGE_MAP_SIZE,
139            )
140        };
141
142        let aflpp_cmp_map = Box::leak(unsafe {
143            Box::from_raw(
144                *self
145                    .aflpp_cmp_map_ptr
146                    .get()
147                    .ok_or_else(|| anyhow!("Comparison map pointer not set"))?,
148            )
149        });
150
151        let aflpp_cmp_map_dup = Box::leak(unsafe {
152            Box::from_raw(
153                *self
154                    .aflpp_cmp_map_ptr
155                    .get()
156                    .ok_or_else(|| anyhow!("Comparison map pointer not set"))?,
157            )
158        });
159
160        let cmplog_enabled = self.cmplog;
161        let corpus_directory = self.corpus_directory.clone();
162        let solutions_directory = self.solutions_directory.clone();
163        let executable_tokens = self
164            .token_executables
165            .iter()
166            .map(tokenize_executable_file)
167            .collect::<Result<Vec<_>>>()?
168            .into_iter()
169            .flatten()
170            .collect::<Vec<_>>();
171        let src_file_tokens = self
172            .token_src_files
173            .iter()
174            .map(|f| {
175                tokenize_src_file(f)
176                    .map(|t| t.iter().map(|s| s.as_bytes().to_vec()).collect::<Vec<_>>())
177            })
178            .collect::<Result<Vec<_>>>()?
179            .into_iter()
180            .flatten()
181            .collect::<Vec<_>>();
182        let token_files = self.token_files.clone();
183        let input_tokens = self.tokens.clone();
184        let generate_random_corpus = self.generate_random_corpus;
185        let initial_random_corpus_size = self.initial_random_corpus_size;
186        let debug_log_libafl = self.debug_log_libafl;
187        let initial_contents = self
188            .use_initial_as_corpus
189            .then(|| {
190                self.start_info
191                    .get()
192                    .map(|si| BytesInput::new(si.contents.clone()))
193            })
194            .flatten();
195
196        // NOTE: We do *not* use `run_in_thread` because it causes the fuzzer to block when HAPs arrive
197        // which prevents forward progress.
198        self.fuzz_thread
199            .set(spawn(move || -> Result<()> {
200                if debug_log_libafl {
201                    let reg = registry().with({
202                        fmt::layer()
203                            .compact()
204                            .with_thread_ids(true)
205                            .with_thread_names(true)
206                            .with_writer(stderr)
207                            .with_filter(LevelFilter::TRACE)
208                            .with_filter(filter_fn(|metadata| {
209                                // LLMP absolutely spams the log when tracing
210                                !(metadata.target() == "libafl_bolts::llmp"
211                                    && matches!(metadata.level(), &Level::TRACE))
212                            }))
213                    });
214
215                    reg.try_init()
216                        .map_err(|e| {
217                            eprintln!("Could not install tracing subscriber: {}", e);
218                            e
219                        })
220                        .ok();
221                }
222
223                let mut harness = |input: &BytesInput| {
224                    let testcase = BytesInput::new(input.target_bytes().as_slice().to_vec());
225                    client
226                        .borrow_mut()
227                        .0
228                        .send(Testcase {
229                            testcase,
230                            cmplog: false,
231                        })
232                        .expect("Failed to send testcase message");
233
234                    let status = match client.borrow_mut().1.recv() {
235                        Err(e) => panic!("Error receiving status: {e}"),
236                        Ok(m) => m,
237                    };
238
239                    status
240                };
241
242                let mut aflpp_cmp_harness = |input: &BytesInput| {
243                    let testcase = BytesInput::new(input.target_bytes().as_slice().to_vec());
244                    client
245                        .borrow_mut()
246                        .0
247                        .send(Testcase {
248                            testcase,
249                            cmplog: true,
250                        })
251                        .expect("Failed to send testcase message");
252
253                    let status = match client.borrow_mut().1.recv() {
254                        Err(e) => panic!("Error receiving status: {e}"),
255                        Ok(m) => m,
256                    };
257
258                    status
259                };
260
261                let mut tracing_harness = aflpp_cmp_harness;
262
263                let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_slice(
264                    Self::EDGES_OBSERVER_NAME,
265                    OwnedMutSlice::from(coverage_map),
266                ));
267
268                let aflpp_cmp_observer = AFLppCmpLogObserver::new(
269                    Self::AFLPP_CMP_OBSERVER_NAME,
270                    OwnedRefMut::Ref(aflpp_cmp_map),
271                    true,
272                );
273
274                let cmplog_observer = StdCmpValuesObserver::new(
275                    Self::CMPLOG_OBSERVER_NAME,
276                    OwnedRefMut::Ref(aflpp_cmp_map_dup),
277                    true,
278                );
279                let time_observer = TimeObserver::new(Self::TIME_OBSERVER_NAME);
280
281                let map_feedback = ReportingMapFeedback::new(
282                    MaxMapFeedback::tracking(&edges_observer, true, true),
283                    mtx.clone(),
284                );
285                let time_feedback = TimeFeedback::with_observer(&time_observer);
286
287                let crash_feedback = CrashFeedback::new();
288                let timeout_feedback = TimeFeedback::new(Self::TIMEOUT_FEEDBACK_NAME);
289
290                let solutions = OnDiskCorpus::with_meta_format(
291                    solutions_directory.clone(),
292                    OnDiskMetadataFormat::JsonPretty,
293                )
294                .map_err(|e| {
295                    eprintln!("Failed to initialize solutions corpus: {e}");
296                    anyhow!("Failed to initialize solutions corpus: {e}")
297                })?;
298
299                let corpus = CachedOnDiskCorpus::with_meta_format(
300                    corpus_directory.clone(),
301                    Self::CORPUS_CACHE_SIZE,
302                    Some(OnDiskMetadataFormat::Json),
303                )
304                .map_err(|e| {
305                    eprintln!("Failed to initialize corpus: {e}");
306                    anyhow!("Failed to initialize corpus: {e}")
307                })?;
308
309                // NOTE: Initialize these here before we move the feedbacks
310                let calibration_stage = CalibrationStage::new(&map_feedback);
311                let colorization_stage = ColorizationStage::new(&edges_observer);
312                let generalization_stage = GeneralizationStage::new(&edges_observer);
313
314                let mut feedback = feedback_or!(map_feedback, time_feedback);
315                let mut objective = feedback_or_fast!(crash_feedback, timeout_feedback);
316
317                let mut state = StdState::new(
318                    StdRand::with_seed(current_nanos()),
319                    corpus,
320                    solutions,
321                    &mut feedback,
322                    &mut objective,
323                )
324                .map_err(|e| {
325                    eprintln!("Couldn't initialize fuzzer state: {e}");
326                    anyhow!("Couldn't initialize state: {e}")
327                })?;
328
329                let mut tokens = Tokens::default().add_from_files(token_files)?;
330
331                tokens.add_tokens(executable_tokens);
332                tokens.add_tokens(src_file_tokens);
333                tokens.add_tokens(input_tokens);
334
335                state.add_metadata(tokens);
336
337                let scheduler =
338                    IndexesLenTimeMinimizerScheduler::new(StdWeightedScheduler::with_schedule(
339                        &mut state,
340                        &edges_observer,
341                        Some(PowerSchedule::EXPLORE),
342                    ));
343
344                let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
345
346                let monitor = {
347                    let mtx = mtx.clone();
348                    SimpleMonitor::new(move |s| {
349                        mtx.send(FuzzerMessage::String(s.to_string()))
350                            .expect("Failed to send monitor message");
351                    })
352                };
353
354                let mut manager = SimpleEventManager::new(monitor);
355
356                let mut executor = InProcessExecutor::new(
357                    &mut harness,
358                    tuple_list!(edges_observer, time_observer),
359                    &mut fuzzer,
360                    &mut manager,
361                )
362                .map_err(|e| {
363                    eprintln!("Couldn't initialize fuzzer executor: {e}");
364                    anyhow!("Couldn't initialize fuzzer executor: {e}")
365                })?;
366
367                let aflpp_cmp_executor = InProcessExecutor::new(
368                    &mut aflpp_cmp_harness,
369                    tuple_list!(aflpp_cmp_observer),
370                    &mut fuzzer,
371                    &mut manager,
372                )
373                .map_err(|e| {
374                    eprintln!("Couldn't initialize fuzzer AFL++ cmplog executor: {e}");
375                    anyhow!("Couldn't initialize fuzzer AFL++ cmplog executor: {e}")
376                })?;
377
378                let tracing_executor = InProcessExecutor::new(
379                    &mut tracing_harness,
380                    tuple_list!(cmplog_observer),
381                    &mut fuzzer,
382                    &mut manager,
383                )
384                .map_err(|e| {
385                    eprintln!("Couldn't initialize fuzzer AFL++ cmplog executor: {e}");
386                    anyhow!("Couldn't initialize fuzzer AFL++ cmplog executor: {e}")
387                })?;
388
389                let input_to_state_stage = StdMutationalStage::new(StdScheduledMutator::new(
390                    tuple_list!(I2SRandReplace::new()),
391                ));
392                let havoc_mutational_stage = StdPowerMutationalStage::new(
393                    StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())),
394                );
395                let mopt_mutational_stage = StdPowerMutationalStage::new(
396                    StdMOptMutator::new(
397                        &mut state,
398                        havoc_mutations().merge(tokens_mutations()),
399                        7,
400                        5,
401                    )
402                    .map_err(|e| {
403                        eprintln!("Couldn't initialize fuzzer MOpt mutator: {e}");
404                        anyhow!("Couldn't initialize fuzzer MOpt mutator: {e}")
405                    })?,
406                );
407                let redqueen_mutational_stage =
408                    MultiMutationalStage::new(AFLppRedQueen::with_cmplog_options(true, true));
409                let aflpp_tracing_stage = AFLppCmplogTracingStage::with_cmplog_observer_name(
410                    aflpp_cmp_executor,
411                    Self::AFLPP_CMP_OBSERVER_NAME,
412                );
413                let tracing_stage = TracingStage::new(tracing_executor);
414                let synchronize_corpus_stage =
415                    SyncFromDiskStage::with_from_file(corpus_directory.clone());
416                let dump_corpus_stage = DumpToDiskStage::new(
417                    |input: &BytesInput, _state: &_| input.target_bytes().as_slice().to_vec(),
418                    corpus_directory.clone(),
419                    solutions_directory.clone(),
420                )
421                .map_err(|e| {
422                    eprintln!("Couldn't initialize fuzzer dump to disk stage: {e}");
423                    anyhow!("Couldn't initialize fuzzer dump to disk stage: {e}")
424                })?;
425
426                if let Some(contents) = initial_contents {
427                    write(
428                        corpus_directory.join(contents.generate_name(0)),
429                        contents.bytes(),
430                    )?;
431                }
432
433                if state.must_load_initial_inputs() {
434                    state
435                        .load_initial_inputs(
436                            &mut fuzzer,
437                            &mut executor,
438                            &mut manager,
439                            std::slice::from_ref(&corpus_directory),
440                        )
441                        .map_err(|e| {
442                            eprintln!(
443                                "Error loading initial inputs from {corpus_directory:?}: {e}"
444                            );
445                            anyhow!("Error loading initial inputs from {corpus_directory:?}: {e}")
446                        })?;
447
448                    if state.corpus().count() < 1 && generate_random_corpus {
449                        let mut generator = RandBytesGenerator::new(64);
450                        state
451                            .generate_initial_inputs(
452                                &mut fuzzer,
453                                &mut executor,
454                                &mut generator,
455                                &mut manager,
456                                initial_random_corpus_size,
457                            )
458                            .map_err(|e| {
459                                eprintln!("Error generating random inputs: {e}");
460                                anyhow!("Error generating random inputs: {e}")
461                            })?;
462                    }
463                }
464
465                if state.corpus().count() < 1 {
466                    panic!(
467                        "No interesting cases found from inputs! This may mean \
468                            your harness is incorrect (check your arguments), your inputs \
469                            are not triggering new code paths, or all inputs are causing \
470                            crashes.",
471                    );
472                }
473
474                let mut stages = tuple_list!(
475                    calibration_stage,
476                    generalization_stage,
477                    IfStage::new(
478                        |_fuzzer: &mut _,
479                         _executor: &mut _,
480                         state: &mut StdState<_, CachedOnDiskCorpus<_>, _, _>,
481                         _event_manager: &mut _|
482                         -> Result<bool, libafl::Error> {
483                            Ok(cmplog_enabled
484                                && state
485                                    .corpus()
486                                    .get(
487                                        state
488                                            .current_corpus_idx()
489                                            .map_err(|e| {
490                                                eprintln!(
491                                                    "Error getting current corpus index: {e}"
492                                                );
493                                                // libafl::Error::unkown(format!(
494                                                //     "Error getting current corpus index: {e}"
495                                                // ))
496                                                e
497                                            })?
498                                            .ok_or_else(|| {
499                                                eprintln!("No current corpus index");
500
501                                                libafl::Error::unknown("No current corpus index")
502                                            })?,
503                                    )
504                                    .map_err(|e| {
505                                        eprintln!("Error getting current corpus entry: {e}");
506                                        e
507                                    })?
508                                    .borrow()
509                                    .scheduled_count()
510                                    == 1)
511                        },
512                        tuple_list!(
513                            colorization_stage,
514                            aflpp_tracing_stage,
515                            redqueen_mutational_stage
516                        )
517                    ),
518                    IfStage::new(
519                        |_fuzzer: &mut _,
520                         _executor: &mut _,
521                         _state: &mut StdState<_, CachedOnDiskCorpus<_>, _, _>,
522                         _event_manager: &mut _|
523                         -> Result<bool, libafl::Error> {
524                            Ok(cmplog_enabled)
525                        },
526                        tuple_list!(tracing_stage, input_to_state_stage)
527                    ),
528                    havoc_mutational_stage,
529                    mopt_mutational_stage,
530                    dump_corpus_stage,
531                    synchronize_corpus_stage,
532                );
533
534                loop {
535                    // Check if we have a message to shut down, and if so, exit.
536                    if let Ok(_msg) = srx.try_recv() {
537                        break;
538                    }
539
540                    fuzzer
541                        .fuzz_one(&mut stages, &mut executor, &mut state, &mut manager)
542                        .map_err(|e| {
543                            eprintln!("Error running iteration of fuzzing loop: {e}");
544                            anyhow!("Error running iteration of fuzzing loop: {e}")
545                        })?;
546                }
547
548                println!("Fuzzing loop exited.");
549
550                Ok(())
551            }))
552            .map_err(|_| anyhow!("Fuzzer thread already set"))?;
553
554        Ok(())
555    }
556
557    pub fn send_shutdown(&mut self) -> Result<()> {
558        if let Some(stx) = self.fuzzer_shutdown.get_mut() {
559            stx.send(ShutdownMessage::default())?;
560        }
561
562        Ok(())
563    }
564
565    pub fn get_testcase(&mut self) -> Result<Testcase> {
566        let testcase = if let Some(testcase) = self.repro_testcase.as_ref() {
567            debug!(self.as_conf_object(), "Using repro testcase");
568            Testcase {
569                testcase: BytesInput::new(testcase.clone()),
570                cmplog: false,
571            }
572        } else {
573            self.fuzzer_rx
574                .get_mut()
575                .ok_or_else(|| anyhow!("Fuzzer receiver not set"))?
576                .recv()
577                .map_err(|e| anyhow!("Error receiving from fuzzer: {e}"))?
578        };
579
580        if self.keep_all_corpus {
581            let testcase_name = testcase.testcase.generate_name(0);
582            trace!(
583                self.as_conf_object(),
584                "Writing testcase {}.testcase to corpus directory: {}",
585                &testcase_name,
586                self.corpus_directory.display()
587            );
588
589            write(
590                self.corpus_directory
591                    .join(format!("{}.testcase", &testcase_name)),
592                testcase.testcase.bytes(),
593            )?;
594        }
595
596        self.cmplog_enabled = testcase.cmplog;
597
598        debug!(self.as_conf_object(), "Testcase: {testcase:?}");
599
600        Ok(testcase)
601    }
602}