Skip to main content

fuchsia_fuzzctl_fdomain/
corpus.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::input::save_input;
6use anyhow::{Context as _, Error, Result};
7use flex_fuchsia_fuzzer as fuzz;
8use futures::TryStreamExt;
9use std::cell::RefCell;
10use std::path::Path;
11use zx_status as zx;
12
13/// Returns which type of corpus is represented by the `fuchsia.fuzzer.Corpus` enum.
14///
15/// A seed corpus is immutable. A fuzzer can add or modify inputs in its live corpus.
16pub fn get_type(seed: bool) -> fuzz::Corpus {
17    if seed { fuzz::Corpus::Seed } else { fuzz::Corpus::Live }
18}
19
20/// Get the corresponding name for a `fuchsia.fuzzer.Corpus` enum.
21pub fn get_name(corpus_type: fuzz::Corpus) -> &'static str {
22    match corpus_type {
23        fuzz::Corpus::Seed => "seed",
24        fuzz::Corpus::Live => "live",
25        other => unreachable!("unsupported type: {:?}", other),
26    }
27}
28
29/// Basic corpus information returned by `read`.
30#[derive(Debug, Default, PartialEq)]
31pub struct Stats {
32    pub num_inputs: u64,
33    pub total_size: u64,
34}
35
36/// Receives and saves inputs from a corpus.
37///
38/// Takes a `stream` and serves `fuchsia.fuzzer.CorpusReader`. A fuzzer can publish a sequence of
39/// test inputs using this protocol, typically in response to a `fuchsia.fuzzer.Controller/Fetch`
40/// request or similar. The inputs are saved under `out_dir`, or in the current working directory
41/// if `out_dir` is `None.
42pub async fn read<P: AsRef<Path>>(
43    stream: fuzz::CorpusReaderRequestStream,
44    out_dir: P,
45) -> Result<Stats> {
46    // Without these `RefCell`s, the compiler will complain about references in the async block
47    // below that escape the closure.
48    let num_inputs: RefCell<u64> = RefCell::new(0);
49    let total_size: RefCell<u64> = RefCell::new(0);
50    stream
51        .try_for_each(|request| async {
52            match request {
53                fuzz::CorpusReaderRequest::Next { test_input, responder } => {
54                    {
55                        let mut num_inputs = num_inputs.borrow_mut();
56                        let mut total_size = total_size.borrow_mut();
57                        *num_inputs += 1;
58                        *total_size += test_input.size;
59                    }
60                    let result = match save_input(test_input, out_dir.as_ref()).await {
61                        Ok(_) => zx::Status::OK,
62                        Err(_) => zx::Status::IO,
63                    };
64                    responder.send(result.into_raw())
65                }
66            }
67        })
68        .await
69        .map_err(Error::msg)
70        .context("failed to handle fuchsia.fuzzer.CorpusReader request")?;
71    let num_inputs = num_inputs.borrow();
72    let total_size = total_size.borrow();
73    Ok(Stats { num_inputs: *num_inputs, total_size: *total_size })
74}
75
76#[cfg(test)]
77mod tests {
78    use super::{Stats, get_name, get_type, read};
79    use crate::input::InputPair;
80    use crate::util::digest_path;
81    use anyhow::{Error, Result};
82    use flex_fuchsia_fuzzer as fuzz;
83    use fuchsia_fuzzctl_test::{Test, verify_saved};
84    use futures::join;
85    use zx_status as zx;
86
87    // Writes a test input using the given `corpus_reader`.
88    async fn send_one_input(
89        corpus_reader: &fuzz::CorpusReaderProxy,
90        data: Vec<u8>,
91        client: &flex_client::ClientArg,
92    ) -> Result<()> {
93        let input_pair = InputPair::try_from_data(client, data)?;
94        let (fidl_input, input) = input_pair.as_tuple();
95        let (response, _) = futures::try_join!(
96            async move { corpus_reader.next(fidl_input).await.map_err(Error::msg) },
97            input.send(),
98        )?;
99        zx::Status::ok(response).map_err(Error::msg)
100    }
101
102    #[test]
103    fn test_get_type() -> Result<()> {
104        assert_eq!(get_type(true), fuzz::Corpus::Seed);
105        assert_eq!(get_type(false), fuzz::Corpus::Live);
106        Ok(())
107    }
108
109    #[test]
110    fn test_get_name() -> Result<()> {
111        assert_eq!(get_name(fuzz::Corpus::Seed), "seed");
112        assert_eq!(get_name(fuzz::Corpus::Live), "live");
113        Ok(())
114    }
115
116    #[fuchsia::test]
117    async fn test_read() -> Result<()> {
118        let test = Test::try_new()?;
119        let corpus_dir = test.create_dir("corpus")?;
120        let corpus = vec![b"hello".to_vec(), b"world".to_vec(), b"".to_vec()];
121        let cloned = corpus.clone();
122
123        let (proxy, stream) = test.domain().create_proxy_and_stream::<fuzz::CorpusReaderMarker>();
124        let read_fut = read(stream, &corpus_dir);
125        let test_ref = &test;
126        let send_fut = || async move {
127            for input in corpus.iter() {
128                send_one_input(&proxy, input.to_vec(), &test_ref.domain()).await?;
129            }
130            Ok::<(), Error>(())
131        };
132        let send_fut = send_fut();
133        let results = join!(read_fut, send_fut);
134        assert_eq!(results.0.unwrap(), Stats { num_inputs: 3, total_size: 10 });
135        assert!(results.1.is_ok());
136        for input in cloned.iter() {
137            let saved = digest_path(&corpus_dir, None, input);
138            verify_saved(&saved, input)?;
139        }
140        Ok(())
141    }
142}