Skip to main content

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