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.
45use 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};
1213/// 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.
17pub fidl_input: FidlInput,
1819/// Client-side representation of a fuzzer input.
20pub input: Input,
21}
2223impl From<(FidlInput, Input)> for InputPair {
24fn from(tuple: (FidlInput, Input)) -> Self {
25 InputPair { fidl_input: tuple.0, input: tuple.1 }
26 }
27}
2829impl 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 ///
39pub fn try_from_str<S, O>(input: S, writer: &Writer<O>) -> Result<Self>
40where
41S: AsRef<str>,
42 O: OutputSink,
43 {
44let input = input.as_ref();
45let hex_result = hex::decode(input);
46let fs_result = fs::read(input);
47let 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 }
6465/// Generates an input pair from a filesystem path.
66 ///
67 /// Returns an error if the `path` is invalid.
68 ///
69pub fn try_from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
70let path = path.as_ref();
71let input_data = fs::read(path)
72 .with_context(|| format!("failed to read '{}'", path.to_string_lossy()))?;
73 InputPair::try_from_data(input_data)
74 }
7576/// Creates an input pair from a sequence of bytes.
77pub fn try_from_data(input_data: Vec<u8>) -> Result<Self> {
78let (reader, writer) = fidl::Socket::create_stream();
79let fidl_input = FidlInput { socket: reader, size: input_data.len() as u64 };
80let input = Input { socket: Some(writer), data: input_data };
81Ok(InputPair::from((fidl_input, input)))
82 }
8384/// Destructures the object into a `FidlInput` and an `Input`.
85pub fn as_tuple(self) -> (FidlInput, Input) {
86 (self.fidl_input, self.input)
87 }
8889/// Returns the length of this object's data.
90pub fn len(&self) -> usize {
91self.input.data.len()
92 }
93}
9495/// 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>,
105106/// The received data
107pub data: Vec<u8>,
108}
109110impl 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.
114pub async fn send(mut self) -> Result<()> {
115let socket = self.socket.take().context("input already sent")?;
116let mut writer = fidl::AsyncSocket::from_socket(socket);
117 writer.write_all(&self.data).await.context("failed to write fuzz input")?;
118Ok(())
119 }
120121/// Reads the object's data from a `FidlInput`.
122 ///
123 /// Returns an error if unable to read from the underlying socket.
124pub async fn try_receive(fidl_input: FidlInput) -> Result<Self> {
125let mut data = Vec::new();
126let 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")?;
132Ok(Input { socket: None, data })
133 }
134}
135136/// 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> {
144let input =
145 Input::try_receive(fidl_input).await.context("failed to receive fuzzer input data")?;
146let 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()))?;
149Ok(path)
150}
151152#[cfg(test)]
153mod tests {
154use super::{save_input, Input};
155use crate::util::digest_path;
156use anyhow::Result;
157use fidl_fuchsia_fuzzer::Input as FidlInput;
158use fuchsia_fuzzctl::InputPair;
159use fuchsia_fuzzctl_test::{verify_saved, Test};
160use futures::{join, AsyncReadExt};
161use std::fs::File;
162use std::io::Write;
163164#[fuchsia::test]
165fn test_from_str() -> Result<()> {
166let test = Test::try_new()?;
167let writer = test.writer();
168let input_dir = test.create_dir("inputs")?;
169170// Missing file.
171let input1 = input_dir.join("input1");
172let actual = format!("{:?}", InputPair::try_from_str(input1.to_string_lossy(), writer));
173assert!(actual.contains("failed to read fuzzer input"));
174175// Empty file.
176let mut file = File::create(&input1)?;
177let input_pair = InputPair::try_from_str(input1.to_string_lossy(), writer)?;
178let (fidl_input, input) = input_pair.as_tuple();
179assert_eq!(fidl_input.size, 0);
180assert!(input.data.is_empty());
181182// File with data.
183file.write_all(b"data")?;
184let input_pair = InputPair::try_from_str(input1.to_string_lossy(), writer)?;
185let (fidl_input, input) = input_pair.as_tuple();
186assert_eq!(fidl_input.size, 4);
187assert_eq!(input.data, b"data");
188189// Hex value.
190let input_pair = InputPair::try_from_str("64617461", writer)?;
191let (fidl_input, input) = input_pair.as_tuple();
192assert_eq!(fidl_input.size, 4);
193assert_eq!(input.data, b"data");
194Ok(())
195 }
196197#[fuchsia::test]
198fn test_from_path() -> Result<()> {
199let test = Test::try_new()?;
200let mut path = test.create_dir("inputs")?;
201 path.push("input");
202assert!(InputPair::try_from_path(&path).is_err());
203204let mut file = File::create(&path)?;
205let input_pair = InputPair::try_from_path(&path)?;
206let (fidl_input, input) = input_pair.as_tuple();
207assert_eq!(fidl_input.size, 0);
208assert!(input.data.is_empty());
209210 file.write_all(b"data")?;
211let input_pair = InputPair::try_from_path(&path)?;
212let (fidl_input, input) = input_pair.as_tuple();
213assert_eq!(fidl_input.size, 4);
214assert_eq!(input.data, b"data");
215Ok(())
216 }
217218#[fuchsia::test]
219async fn test_send() -> Result<()> {
220async fn recv(fidl_input: FidlInput, expected: &[u8]) {
221let mut reader = fidl::AsyncSocket::from_socket(fidl_input.socket);
222let mut buf = Vec::new();
223let num_read = reader.read_to_end(&mut buf).await.expect("read_to_end failed");
224assert_eq!(num_read as u64, fidl_input.size);
225assert_eq!(buf, expected);
226 }
227let input_pair = InputPair::try_from_data(b"".to_vec())?;
228let (fidl_input, input) = input_pair.as_tuple();
229assert!(join!(input.send(), recv(fidl_input, b"")).0.is_ok());
230let input_pair = InputPair::try_from_data(b"data".to_vec())?;
231let (fidl_input, input) = input_pair.as_tuple();
232assert!(join!(input.send(), recv(fidl_input, b"data")).0.is_ok());
233Ok(())
234 }
235236#[fuchsia::test]
237async fn test_save_input() -> Result<()> {
238let test = Test::try_new()?;
239let saved_dir = test.create_dir("saved")?;
240241let (reader, writer) = fidl::Socket::create_stream();
242let fidl_input = FidlInput { socket: reader, size: 0 };
243let input = Input { socket: Some(writer), data: Vec::new() };
244let send_fut = input.send();
245let save_fut = save_input(fidl_input, &saved_dir);
246let results = join!(send_fut, save_fut);
247assert!(results.0.is_ok());
248assert!(results.1.is_ok());
249let saved = digest_path(&saved_dir, None, b"");
250 verify_saved(&saved, b"")?;
251Ok(())
252 }
253}