fuchsia_fuzzctl/
artifact.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::Input;
6use crate::util::digest_path;
7use anyhow::{bail, Context as _, Result};
8use fidl_fuchsia_fuzzer::{Artifact as FidlArtifact, Result_ as FuzzResult};
9use std::fs;
10use std::path::{Path, PathBuf};
11use zx_status as zx;
12
13/// Combines the results of a long-running fuzzer workflow.
14pub struct Artifact {
15    /// If `status` is OK, indicates the outcome of the workflow; otherwise undefined.
16    pub result: FuzzResult,
17
18    /// The path to which the fuzzer input, if any, has been saved.
19    pub path: Option<PathBuf>,
20}
21
22impl Artifact {
23    /// Returns an artifact for a workflow that completed without producing results or data.
24    pub fn ok() -> Self {
25        Self { result: FuzzResult::NoErrors, path: None }
26    }
27
28    /// Returns an artifact for a workflow that produced a result without data.
29    pub fn from_result(result: FuzzResult) -> Self {
30        Self { result, path: None }
31    }
32
33    /// Returns `artifact.path` as a String, or an empty string if it is `None`.
34    pub fn pathname(&self) -> String {
35        self.path.as_ref().map(|p| p.to_string_lossy().to_string()).unwrap_or(String::default())
36    }
37}
38
39/// Reads fuzzer input data from a `FidlArtifact` and saves it locally.
40///
41/// Returns:
42/// Returns an `Artifact` on success. Returns an error if the artifact indicates an error, if
43/// it fails to read the data from the `input`, or if it fails to write the data to the file.
44///
45/// See also `utils::digest_path`.
46///
47pub async fn save_artifact<P: AsRef<Path>>(
48    fidl_artifact: FidlArtifact,
49    out_dir: P,
50) -> Result<Option<Artifact>> {
51    if let Some(e) = fidl_artifact.error {
52        if e == zx::Status::PEER_CLOSED.into_raw() {
53            return Ok(None);
54        }
55        bail!("workflow returned an error: ZX_ERR_{}", e);
56    }
57    let result = fidl_artifact.result.context("invalid FIDL artifact: missing result")?;
58    let mut artifact = Artifact::from_result(result);
59    if let Some(fidl_input) = fidl_artifact.input {
60        let input =
61            Input::try_receive(fidl_input).await.context("failed to receive fuzzer input data")?;
62        if artifact.result != FuzzResult::NoErrors {
63            let path = digest_path(out_dir, Some(artifact.result), &input.data);
64            fs::write(&path, input.data).with_context(|| {
65                format!("failed to write fuzzer input to '{}'", path.to_string_lossy())
66            })?;
67            artifact.path = Some(path);
68        }
69    };
70    Ok(Some(artifact))
71}
72
73#[cfg(test)]
74mod tests {
75    use super::save_artifact;
76    use crate::input::InputPair;
77    use crate::util::digest_path;
78    use anyhow::Result;
79    use fidl_fuchsia_fuzzer::{Artifact as FidlArtifact, Result_ as FuzzResult};
80    use fuchsia_fuzzctl_test::{verify_saved, Test};
81    use futures::join;
82
83    #[fuchsia::test]
84    async fn test_save_artifact() -> Result<()> {
85        let test = Test::try_new()?;
86        let saved_dir = test.create_dir("saved")?;
87
88        let input_pair = InputPair::try_from_data(b"data".to_vec())?;
89        let (fidl_input, input) = input_pair.as_tuple();
90        let send_fut = input.send();
91        let fidl_artifact = FidlArtifact {
92            result: Some(FuzzResult::Crash),
93            input: Some(fidl_input),
94            ..Default::default()
95        };
96        let save_fut = save_artifact(fidl_artifact, &saved_dir);
97        let results = join!(send_fut, save_fut);
98        assert!(results.0.is_ok());
99        assert!(results.1.is_ok());
100        let saved = digest_path(&saved_dir, Some(FuzzResult::Crash), b"data");
101        verify_saved(&saved, b"data")?;
102        Ok(())
103    }
104}