use crate::util::digest_path;
use crate::writer::{OutputSink, Writer};
use anyhow::{bail, Context as _, Result};
use fidl_fuchsia_fuzzer::Input as FidlInput;
use futures::{AsyncReadExt, AsyncWriteExt};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct InputPair {
pub fidl_input: FidlInput,
pub input: Input,
}
impl From<(FidlInput, Input)> for InputPair {
fn from(tuple: (FidlInput, Input)) -> Self {
InputPair { fidl_input: tuple.0, input: tuple.1 }
}
}
impl InputPair {
pub fn try_from_str<S, O>(input: S, writer: &Writer<O>) -> Result<Self>
where
S: AsRef<str>,
O: OutputSink,
{
let input = input.as_ref();
let hex_result = hex::decode(input);
let fs_result = fs::read(input);
let input_data = match (hex_result, fs_result) {
(Ok(input_data), Err(_)) => input_data,
(Err(_), Ok(input_data)) => input_data,
(Ok(input_data), Ok(_)) => {
writer.print("WARNING: ");
writer.print(input);
writer.println("can be interpreted as either a hex string or a file.");
writer.println("The input will be treated as a hex string.");
writer.println("To force treatment as a file, include more of the path, e.g.");
writer.print(" ./");
writer.println(input);
input_data
}
(Err(_), Err(e)) => bail!("failed to read fuzzer input: {}", e),
};
InputPair::try_from_data(input_data)
}
pub fn try_from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let input_data = fs::read(path)
.with_context(|| format!("failed to read '{}'", path.to_string_lossy()))?;
InputPair::try_from_data(input_data)
}
pub fn try_from_data(input_data: Vec<u8>) -> Result<Self> {
let (reader, writer) = fidl::Socket::create_stream();
let fidl_input = FidlInput { socket: reader, size: input_data.len() as u64 };
let input = Input { socket: Some(writer), data: input_data };
Ok(InputPair::from((fidl_input, input)))
}
pub fn as_tuple(self) -> (FidlInput, Input) {
(self.fidl_input, self.input)
}
pub fn len(&self) -> usize {
self.input.data.len()
}
}
#[derive(Debug)]
pub struct Input {
socket: Option<fidl::Socket>,
pub data: Vec<u8>,
}
impl Input {
pub async fn send(mut self) -> Result<()> {
let socket = self.socket.take().context("input already sent")?;
let mut writer = fidl::AsyncSocket::from_socket(socket);
writer.write_all(&self.data).await.context("failed to write fuzz input")?;
Ok(())
}
pub async fn try_receive(fidl_input: FidlInput) -> Result<Self> {
let mut data = Vec::new();
let reader = fidl::AsyncSocket::from_socket(fidl_input.socket);
reader
.take(fidl_input.size)
.read_to_end(&mut data)
.await
.context("failed to read fuzz input from socket")?;
Ok(Input { socket: None, data })
}
}
pub async fn save_input<P: AsRef<Path>>(fidl_input: FidlInput, out_dir: P) -> Result<PathBuf> {
let input =
Input::try_receive(fidl_input).await.context("failed to receive fuzzer input data")?;
let path = digest_path(out_dir, None, &input.data);
fs::write(&path, input.data)
.with_context(|| format!("failed to write fuzzer input to '{}'", path.to_string_lossy()))?;
Ok(path)
}
#[cfg(test)]
mod tests {
use super::{save_input, Input};
use crate::util::digest_path;
use anyhow::Result;
use fidl_fuchsia_fuzzer::Input as FidlInput;
use fuchsia_fuzzctl::InputPair;
use fuchsia_fuzzctl_test::{verify_saved, Test};
use futures::{join, AsyncReadExt};
use std::fs::File;
use std::io::Write;
#[fuchsia::test]
fn test_from_str() -> Result<()> {
let test = Test::try_new()?;
let writer = test.writer();
let input_dir = test.create_dir("inputs")?;
let input1 = input_dir.join("input1");
let actual = format!("{:?}", InputPair::try_from_str(input1.to_string_lossy(), writer));
assert!(actual.contains("failed to read fuzzer input"));
let mut file = File::create(&input1)?;
let input_pair = InputPair::try_from_str(input1.to_string_lossy(), writer)?;
let (fidl_input, input) = input_pair.as_tuple();
assert_eq!(fidl_input.size, 0);
assert!(input.data.is_empty());
file.write_all(b"data")?;
let input_pair = InputPair::try_from_str(input1.to_string_lossy(), writer)?;
let (fidl_input, input) = input_pair.as_tuple();
assert_eq!(fidl_input.size, 4);
assert_eq!(input.data, b"data");
let input_pair = InputPair::try_from_str("64617461", writer)?;
let (fidl_input, input) = input_pair.as_tuple();
assert_eq!(fidl_input.size, 4);
assert_eq!(input.data, b"data");
Ok(())
}
#[fuchsia::test]
fn test_from_path() -> Result<()> {
let test = Test::try_new()?;
let mut path = test.create_dir("inputs")?;
path.push("input");
assert!(InputPair::try_from_path(&path).is_err());
let mut file = File::create(&path)?;
let input_pair = InputPair::try_from_path(&path)?;
let (fidl_input, input) = input_pair.as_tuple();
assert_eq!(fidl_input.size, 0);
assert!(input.data.is_empty());
file.write_all(b"data")?;
let input_pair = InputPair::try_from_path(&path)?;
let (fidl_input, input) = input_pair.as_tuple();
assert_eq!(fidl_input.size, 4);
assert_eq!(input.data, b"data");
Ok(())
}
#[fuchsia::test]
async fn test_send() -> Result<()> {
async fn recv(fidl_input: FidlInput, expected: &[u8]) {
let mut reader = fidl::AsyncSocket::from_socket(fidl_input.socket);
let mut buf = Vec::new();
let num_read = reader.read_to_end(&mut buf).await.expect("read_to_end failed");
assert_eq!(num_read as u64, fidl_input.size);
assert_eq!(buf, expected);
}
let input_pair = InputPair::try_from_data(b"".to_vec())?;
let (fidl_input, input) = input_pair.as_tuple();
assert!(join!(input.send(), recv(fidl_input, b"")).0.is_ok());
let input_pair = InputPair::try_from_data(b"data".to_vec())?;
let (fidl_input, input) = input_pair.as_tuple();
assert!(join!(input.send(), recv(fidl_input, b"data")).0.is_ok());
Ok(())
}
#[fuchsia::test]
async fn test_save_input() -> Result<()> {
let test = Test::try_new()?;
let saved_dir = test.create_dir("saved")?;
let (reader, writer) = fidl::Socket::create_stream();
let fidl_input = FidlInput { socket: reader, size: 0 };
let input = Input { socket: Some(writer), data: Vec::new() };
let send_fut = input.send();
let save_fut = save_input(fidl_input, &saved_dir);
let results = join!(send_fut, save_fut);
assert!(results.0.is_ok());
assert!(results.1.is_ok());
let saved = digest_path(&saved_dir, None, b"");
verify_saved(&saved, b"")?;
Ok(())
}
}