fuchsia_fuzzctl/
input.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::util::digest_path;
6use crate::writer::{OutputSink, Writer};
7use anyhow::{bail, Context as _, Result};
8use fidl_fuchsia_fuzzer::Input as FidlInput;
9use futures::{AsyncReadExt, AsyncWriteExt};
10use std::fs;
11use std::path::{Path, PathBuf};
12
13/// Represents an `Input` that can send or read data from an associated `FidlInput`.
14#[derive(Debug)]
15pub struct InputPair {
16    /// Socket and size used to send input data to or receive input data from a fuzzer.
17    pub fidl_input: FidlInput,
18
19    /// Client-side representation of a fuzzer input.
20    pub input: Input,
21}
22
23impl From<(FidlInput, Input)> for InputPair {
24    fn from(tuple: (FidlInput, Input)) -> Self {
25        InputPair { fidl_input: tuple.0, input: tuple.1 }
26    }
27}
28
29impl InputPair {
30    /// Generates an input pair from a  string.
31    ///
32    /// The `input` string can be either a file name or a hex-encoded value. This method also
33    /// returns a `fuchsia.fuzzer.Input` that can be sent via FIDL calls to receive this object's
34    /// data. The `writer` is used to alert the user if there is ambiguity about how to interpret
35    /// `input`.
36    ///
37    /// Returns an error if the `input` string is neither valid hex nor a valid path to a file.
38    ///
39    pub fn try_from_str<S, O>(input: S, writer: &Writer<O>) -> Result<Self>
40    where
41        S: AsRef<str>,
42        O: OutputSink,
43    {
44        let input = input.as_ref();
45        let hex_result = hex::decode(input);
46        let fs_result = fs::read(input);
47        let input_data = match (hex_result, fs_result) {
48            (Ok(input_data), Err(_)) => input_data,
49            (Err(_), Ok(input_data)) => input_data,
50            (Ok(input_data), Ok(_)) => {
51                writer.print("WARNING: ");
52                writer.print(input);
53                writer.println("can be interpreted as either a hex string or a file.");
54                writer.println("The input will be treated as a hex string.");
55                writer.println("To force treatment as a file, include more of the path, e.g.");
56                writer.print("  ./");
57                writer.println(input);
58                input_data
59            }
60            (Err(_), Err(e)) => bail!("failed to read fuzzer input: {}", e),
61        };
62        InputPair::try_from_data(input_data)
63    }
64
65    /// Generates an input pair from a filesystem path.
66    ///
67    /// Returns an error if the `path` is invalid.
68    ///
69    pub fn try_from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
70        let path = path.as_ref();
71        let input_data = fs::read(path)
72            .with_context(|| format!("failed to read '{}'", path.to_string_lossy()))?;
73        InputPair::try_from_data(input_data)
74    }
75
76    /// Creates an input pair from a sequence of bytes.
77    pub fn try_from_data(input_data: Vec<u8>) -> Result<Self> {
78        let (reader, writer) = fidl::Socket::create_stream();
79        let fidl_input = FidlInput { socket: reader, size: input_data.len() as u64 };
80        let input = Input { socket: Some(writer), data: input_data };
81        Ok(InputPair::from((fidl_input, input)))
82    }
83
84    /// Destructures the object into a `FidlInput` and an `Input`.
85    pub fn as_tuple(self) -> (FidlInput, Input) {
86        (self.fidl_input, self.input)
87    }
88
89    /// Returns the length of this object's data.
90    pub fn len(&self) -> usize {
91        self.input.data.len()
92    }
93}
94
95/// Represents a sequence of bytes, paired with a `fuchsia.fuzzer.Input`.
96///
97/// The `fuchsia.fuzzer.Input` FIDL struct is used to transport test inputs and artifacts between
98/// a target device running a fuzzer and a development host running the `ffx fuzz` plugin. This
99/// struct and that FIDL struct are created in pairs. The FIDL struct can be sent to the target
100/// device, and this struct can be used to transmit the actual test input data to the target
101/// device.
102#[derive(Debug)]
103pub struct Input {
104    socket: Option<fidl::Socket>,
105
106    /// The received data
107    pub data: Vec<u8>,
108}
109
110impl Input {
111    /// Writes the object's data to its internal socket.
112    ///
113    /// This will deliver the data to the `fuchsia.fuzzer.Input` created with this object.
114    pub async fn send(mut self) -> Result<()> {
115        let socket = self.socket.take().context("input already sent")?;
116        let mut writer = fidl::AsyncSocket::from_socket(socket);
117        writer.write_all(&self.data).await.context("failed to write fuzz input")?;
118        Ok(())
119    }
120
121    /// Reads the object's data from a `FidlInput`.
122    ///
123    /// Returns an error if unable to read from the underlying socket.
124    pub async fn try_receive(fidl_input: FidlInput) -> Result<Self> {
125        let mut data = Vec::new();
126        let reader = fidl::AsyncSocket::from_socket(fidl_input.socket);
127        reader
128            .take(fidl_input.size)
129            .read_to_end(&mut data)
130            .await
131            .context("failed to read fuzz input from socket")?;
132        Ok(Input { socket: None, data })
133    }
134}
135
136/// Reads fuzzer input data from a `FidlInput` and saves it locally.
137///
138/// Returns the path to the file on success. Returns an error if it fails to read the data from the
139/// `input` or if it fails to write the data to the file.
140///
141/// See also `utils::digest_path`.
142///
143pub async fn save_input<P: AsRef<Path>>(fidl_input: FidlInput, out_dir: P) -> Result<PathBuf> {
144    let input =
145        Input::try_receive(fidl_input).await.context("failed to receive fuzzer input data")?;
146    let path = digest_path(out_dir, None, &input.data);
147    fs::write(&path, input.data)
148        .with_context(|| format!("failed to write fuzzer input to '{}'", path.to_string_lossy()))?;
149    Ok(path)
150}
151
152#[cfg(test)]
153mod tests {
154    use super::{save_input, Input};
155    use crate::util::digest_path;
156    use anyhow::Result;
157    use fidl_fuchsia_fuzzer::Input as FidlInput;
158    use fuchsia_fuzzctl::InputPair;
159    use fuchsia_fuzzctl_test::{verify_saved, Test};
160    use futures::{join, AsyncReadExt};
161    use std::fs::File;
162    use std::io::Write;
163
164    #[fuchsia::test]
165    fn test_from_str() -> Result<()> {
166        let test = Test::try_new()?;
167        let writer = test.writer();
168        let input_dir = test.create_dir("inputs")?;
169
170        // Missing file.
171        let input1 = input_dir.join("input1");
172        let actual = format!("{:?}", InputPair::try_from_str(input1.to_string_lossy(), writer));
173        assert!(actual.contains("failed to read fuzzer input"));
174
175        // Empty file.
176        let mut file = File::create(&input1)?;
177        let input_pair = InputPair::try_from_str(input1.to_string_lossy(), writer)?;
178        let (fidl_input, input) = input_pair.as_tuple();
179        assert_eq!(fidl_input.size, 0);
180        assert!(input.data.is_empty());
181
182        // File with data.
183        file.write_all(b"data")?;
184        let input_pair = InputPair::try_from_str(input1.to_string_lossy(), writer)?;
185        let (fidl_input, input) = input_pair.as_tuple();
186        assert_eq!(fidl_input.size, 4);
187        assert_eq!(input.data, b"data");
188
189        // Hex value.
190        let input_pair = InputPair::try_from_str("64617461", writer)?;
191        let (fidl_input, input) = input_pair.as_tuple();
192        assert_eq!(fidl_input.size, 4);
193        assert_eq!(input.data, b"data");
194        Ok(())
195    }
196
197    #[fuchsia::test]
198    fn test_from_path() -> Result<()> {
199        let test = Test::try_new()?;
200        let mut path = test.create_dir("inputs")?;
201        path.push("input");
202        assert!(InputPair::try_from_path(&path).is_err());
203
204        let mut file = File::create(&path)?;
205        let input_pair = InputPair::try_from_path(&path)?;
206        let (fidl_input, input) = input_pair.as_tuple();
207        assert_eq!(fidl_input.size, 0);
208        assert!(input.data.is_empty());
209
210        file.write_all(b"data")?;
211        let input_pair = InputPair::try_from_path(&path)?;
212        let (fidl_input, input) = input_pair.as_tuple();
213        assert_eq!(fidl_input.size, 4);
214        assert_eq!(input.data, b"data");
215        Ok(())
216    }
217
218    #[fuchsia::test]
219    async fn test_send() -> Result<()> {
220        async fn recv(fidl_input: FidlInput, expected: &[u8]) {
221            let mut reader = fidl::AsyncSocket::from_socket(fidl_input.socket);
222            let mut buf = Vec::new();
223            let num_read = reader.read_to_end(&mut buf).await.expect("read_to_end failed");
224            assert_eq!(num_read as u64, fidl_input.size);
225            assert_eq!(buf, expected);
226        }
227        let input_pair = InputPair::try_from_data(b"".to_vec())?;
228        let (fidl_input, input) = input_pair.as_tuple();
229        assert!(join!(input.send(), recv(fidl_input, b"")).0.is_ok());
230        let input_pair = InputPair::try_from_data(b"data".to_vec())?;
231        let (fidl_input, input) = input_pair.as_tuple();
232        assert!(join!(input.send(), recv(fidl_input, b"data")).0.is_ok());
233        Ok(())
234    }
235
236    #[fuchsia::test]
237    async fn test_save_input() -> Result<()> {
238        let test = Test::try_new()?;
239        let saved_dir = test.create_dir("saved")?;
240
241        let (reader, writer) = fidl::Socket::create_stream();
242        let fidl_input = FidlInput { socket: reader, size: 0 };
243        let input = Input { socket: Some(writer), data: Vec::new() };
244        let send_fut = input.send();
245        let save_fut = save_input(fidl_input, &saved_dir);
246        let results = join!(send_fut, save_fut);
247        assert!(results.0.is_ok());
248        assert!(results.1.is_ok());
249        let saved = digest_path(&saved_dir, None, b"");
250        verify_saved(&saved, b"")?;
251        Ok(())
252    }
253}