use crate::constants::*;
use crate::corpus;
use crate::diagnostics::Forwarder;
use crate::duration::deadline_after;
use crate::input::{Input, InputPair};
use crate::writer::{OutputSink, Writer};
use anyhow::{anyhow, bail, Context as _, Error, Result};
use fidl::endpoints::create_request_stream;
use fidl::Error::ClientChannelClosed;
use fidl_fuchsia_fuzzer::{self as fuzz, Artifact as FidlArtifact};
use fuchsia_async::Timer;
use futures::future::{pending, Either};
use futures::{pin_mut, select, try_join, Future, FutureExt};
use std::cell::RefCell;
use std::cmp::max;
use std::path::Path;
use zx_status as zx;
#[derive(Debug)]
pub struct Controller<O: OutputSink> {
proxy: fuzz::ControllerProxy,
forwarder: Forwarder<O>,
min_timeout: i64,
timeout: RefCell<Option<i64>>,
}
impl<O: OutputSink> Controller<O> {
pub fn new(proxy: fuzz::ControllerProxy, writer: &Writer<O>) -> Self {
Self {
proxy,
forwarder: Forwarder::<O>::new(writer),
min_timeout: 60 * NANOS_PER_SECOND,
timeout: RefCell::new(None),
}
}
pub fn set_output<P: AsRef<Path>>(
&mut self,
socket: fidl::Socket,
output: fuzz::TestOutput,
logs_dir: &Option<P>,
) -> Result<()> {
self.forwarder.set_output(socket, output, logs_dir)
}
pub fn set_min_timeout(&mut self, min_timeout: i64) {
self.min_timeout = min_timeout;
}
pub async fn configure(&self, options: fuzz::Options) -> Result<()> {
self.set_timeout(&options, None);
let result =
self.proxy.configure(&options).await.context("fuchsia.fuzzer/Controller.Configure")?;
result.map_err(|status| {
anyhow!("fuchsia.fuzzer/Controller.Configure returned: ZX_ERR_{}", status)
})
}
pub async fn get_options(&self) -> Result<fuzz::Options> {
self.proxy
.get_options()
.await
.map_err(Error::msg)
.context("`fuchsia.fuzzer.Controller/GetOptions` failed")
}
pub async fn reset_timer(&self) -> Result<()> {
let options = self.get_options().await?;
let status = self.get_status().await?;
self.set_timeout(&options, Some(status));
Ok(())
}
fn set_timeout(&self, options: &fuzz::Options, status: Option<fuzz::Status>) {
let elapsed = status.map(|s| s.elapsed.unwrap_or(0)).unwrap_or(0);
if let Some(max_total_time) = options.max_total_time {
let mut timeout_mut = self.timeout.borrow_mut();
match max_total_time {
0 => {
*timeout_mut = None;
}
n => {
*timeout_mut = Some(max(n * 2, self.min_timeout) - elapsed);
}
}
}
}
pub async fn read_corpus<P: AsRef<Path>>(
&self,
corpus_type: fuzz::Corpus,
corpus_dir: P,
) -> Result<corpus::Stats> {
let (client_end, stream) = create_request_stream::<fuzz::CorpusReaderMarker>();
let (_, corpus_stats) = try_join!(
async { self.proxy.read_corpus(corpus_type, client_end).await.map_err(Error::msg) },
async { corpus::read(stream, corpus_dir).await },
)
.context("`fuchsia.fuzzer.Controller/ReadCorpus` failed")?;
Ok(corpus_stats)
}
pub async fn add_to_corpus(
&self,
input_pairs: Vec<InputPair>,
corpus_type: fuzz::Corpus,
) -> Result<corpus::Stats> {
let expected_num_inputs = input_pairs.len();
let expected_total_size =
input_pairs.iter().fold(0, |total, input_pair| total + input_pair.len());
let mut corpus_stats = corpus::Stats { num_inputs: 0, total_size: 0 };
for input_pair in input_pairs.into_iter() {
let (fidl_input, input) = input_pair.as_tuple();
let fidl_input_size = fidl_input.size;
let (result, _) = try_join!(
async {
self.proxy.add_to_corpus(corpus_type, fidl_input).await.map_err(Error::msg)
},
input.send(),
)
.context("fuchsia.fuzzer/Controller.AddToCorpus failed")?;
if let Err(status) = result {
bail!(
"fuchsia.fuzzer/Controller.AddToCorpus returned: ZX_ERR_{} \
after writing {} of {} files ({} of {} bytes)",
status,
corpus_stats.num_inputs,
expected_num_inputs,
corpus_stats.total_size,
expected_total_size
)
}
corpus_stats.num_inputs += 1;
corpus_stats.total_size += fidl_input_size;
}
Ok(corpus_stats)
}
pub async fn get_status(&self) -> Result<fuzz::Status> {
match self.proxy.get_status().await {
Err(fidl::Error::ClientChannelClosed { status, .. })
if status == zx::Status::PEER_CLOSED =>
{
return Ok(fuzz::Status::default())
}
Err(e) => bail!("`fuchsia.fuzzer.Controller/GetStatus` failed: {:?}", e),
Ok(fuzz_status) => Ok(fuzz_status),
}
}
pub async fn fuzz(&self) -> Result<()> {
let response = self.proxy.fuzz().await;
let status = check_response("Fuzz", response)?;
check_status("Fuzz", status)
}
pub async fn try_one(&self, input_pair: InputPair) -> Result<()> {
let (fidl_input, input) = input_pair.as_tuple();
let status = self.with_input("TryOne", self.proxy.try_one(fidl_input), input).await?;
check_status("TryOne", status)
}
pub async fn minimize(&self, input_pair: InputPair) -> Result<()> {
let (fidl_input, input) = input_pair.as_tuple();
let status = self.with_input("Minimize", self.proxy.minimize(fidl_input), input).await?;
match status {
zx::Status::INVALID_ARGS => bail!("the provided input did not cause an error"),
status => check_status("Minimize", status),
}
}
pub async fn cleanse(&self, input_pair: InputPair) -> Result<()> {
let (fidl_input, input) = input_pair.as_tuple();
let status = self.with_input("Cleanse", self.proxy.cleanse(fidl_input), input).await?;
match status {
zx::Status::INVALID_ARGS => bail!("the provided input did not cause an error"),
status => check_status("Cleanse", status),
}
}
pub async fn merge(&self) -> Result<()> {
let response = self.proxy.merge().await;
let status = check_response("Merge", response)?;
match status {
zx::Status::INVALID_ARGS => bail!("an input in the seed corpus triggered an error"),
status => check_status("Merge", status),
}
}
async fn with_input<F>(&self, name: &str, fidl_fut: F, input: Input) -> Result<zx::Status>
where
F: Future<Output = Result<Result<(), i32>, fidl::Error>>,
{
let fidl_fut = fidl_fut.fuse();
let send_fut = input.send().fuse();
let timer_fut = match deadline_after(*self.timeout.borrow()) {
Some(deadline) => Either::Left(Timer::new(deadline)),
None => Either::Right(pending()),
};
let timer_fut = timer_fut.fuse();
pin_mut!(fidl_fut, send_fut, timer_fut);
let mut remaining = 2;
let mut status = zx::Status::OK;
while remaining > 0 && status == zx::Status::OK {
select! {
response = fidl_fut => {
status = check_response(name, response)?;
remaining -= 1;
}
result = send_fut => {
result?;
remaining -= 1;
}
_ = timer_fut => {
bail!("workflow timed out");
}
};
}
Ok(status)
}
pub async fn watch_artifact(&self) -> Result<FidlArtifact> {
let watch_fut = || async move {
loop {
let artifact = self.proxy.watch_artifact().await?;
if artifact != FidlArtifact::default() {
return Ok(artifact);
}
}
};
let watch_fut = watch_fut().fuse();
let forward_fut = self.forwarder.forward_all().fuse();
let timer_fut = match deadline_after(*self.timeout.borrow()) {
Some(deadline) => Either::Left(Timer::new(deadline)),
None => Either::Right(pending()),
};
let timer_fut = timer_fut.fuse();
pin_mut!(watch_fut, forward_fut, timer_fut);
let mut remaining = 2;
let mut fidl_artifact = FidlArtifact::default();
while remaining > 0 {
select! {
result = watch_fut => {
fidl_artifact = match result {
Ok(fidl_artifact) => {
if let Some(e) = fidl_artifact.error {
bail!("workflow returned an error: ZX_ERR_{}", e);
}
fidl_artifact
}
Err(ClientChannelClosed { status, .. }) if status == zx::Status::PEER_CLOSED => FidlArtifact { error: Some(zx::Status::CANCELED.into_raw()), ..Default::default() },
Err(e) => bail!("fuchsia.fuzzer/Controller.WatchArtifact: {:?}", e),
};
remaining -= 1;
}
result = forward_fut => {
result?;
remaining -= 1;
}
_ = timer_fut => {
bail!("workflow timed out");
}
};
}
Ok(fidl_artifact)
}
}
fn check_response(
name: &str,
response: Result<Result<(), i32>, fidl::Error>,
) -> Result<zx::Status> {
match response {
Err(fidl::Error::ClientChannelClosed { status, .. })
if status == zx::Status::PEER_CLOSED =>
{
Ok(zx::Status::OK)
}
Err(e) => bail!("`fuchsia.fuzzer.Controller/{}` failed: {:?}", name, e),
Ok(Err(raw)) => Ok(zx::Status::from_raw(raw)),
Ok(Ok(())) => Ok(zx::Status::OK),
}
}
fn check_status(name: &str, status: zx::Status) -> Result<()> {
match status {
zx::Status::OK => Ok(()),
zx::Status::BAD_STATE => bail!("another long-running workflow is in progress"),
status => bail!("`fuchsia.fuzzer.Controller/{}` returned: ZX_ERR_{}", name, status),
}
}
#[cfg(test)]
mod tests {
use crate::util::digest_path;
use anyhow::{Context as _, Result};
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_fuzzer::{self as fuzz, Result_ as FuzzResult};
use fuchsia_fuzzctl::{Controller, Input, InputPair};
use fuchsia_fuzzctl_test::{create_task, serve_controller, verify_saved, FakeController, Test};
use {fuchsia_async as fasync, zx_status as zx};
fn perform_test_setup(
test: &Test,
) -> Result<(FakeController, fuzz::ControllerProxy, fasync::Task<()>)> {
let fake = test.controller();
let (proxy, stream) = create_proxy_and_stream::<fuzz::ControllerMarker>();
let task = create_task(serve_controller(stream, test.clone()), test.writer());
Ok((fake, proxy, task))
}
#[fuchsia::test]
async fn test_configure() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
let expected = fuzz::Options {
dictionary_level: Some(1),
detect_exits: Some(true),
detect_leaks: Some(false),
death_exitcode: Some(2),
debug: Some(true),
..Default::default()
};
controller.configure(expected.clone()).await?;
let actual = fake.get_options();
assert_eq!(actual.dictionary_level, expected.dictionary_level);
assert_eq!(actual.detect_exits, expected.detect_exits);
assert_eq!(actual.detect_leaks, expected.detect_leaks);
assert_eq!(actual.death_exitcode, expected.death_exitcode);
assert_eq!(actual.debug, expected.debug);
Ok(())
}
#[fuchsia::test]
async fn test_get_options() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
let expected = fuzz::Options {
max_total_time: Some(20000),
max_input_size: Some(2000),
mutation_depth: Some(20),
malloc_limit: Some(200),
malloc_exitcode: Some(2),
..Default::default()
};
fake.set_options(expected.clone());
let actual = controller.get_options().await?;
assert_eq!(actual.max_total_time, expected.max_total_time);
assert_eq!(actual.max_input_size, expected.max_input_size);
assert_eq!(actual.mutation_depth, expected.mutation_depth);
assert_eq!(actual.malloc_limit, expected.malloc_limit);
assert_eq!(actual.malloc_exitcode, expected.malloc_exitcode);
Ok(())
}
#[fuchsia::test]
async fn test_read_corpus() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
let seed_dir = test.create_dir("seed")?;
fake.set_input_to_send(b"foo");
let stats = controller.read_corpus(fuzz::Corpus::Seed, &seed_dir).await?;
assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Seed);
assert_eq!(stats.num_inputs, 1);
assert_eq!(stats.total_size, 3);
let path = digest_path(&seed_dir, None, b"foo");
verify_saved(&path, b"foo")?;
let live_dir = test.create_dir("live")?;
fake.set_input_to_send(b"barbaz");
let stats = controller.read_corpus(fuzz::Corpus::Live, &live_dir).await?;
assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Live);
assert_eq!(stats.num_inputs, 1);
assert_eq!(stats.total_size, 6);
let path = digest_path(&live_dir, None, b"barbaz");
verify_saved(&path, b"barbaz")?;
Ok(())
}
#[fuchsia::test]
async fn test_add_to_corpus() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
let input_pairs: Vec<InputPair> = vec![b"foo".to_vec(), b"bar".to_vec(), b"baz".to_vec()]
.into_iter()
.map(|data| InputPair::try_from_data(data).unwrap())
.collect();
let stats = controller.add_to_corpus(input_pairs, fuzz::Corpus::Seed).await?;
assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Seed);
assert_eq!(stats.num_inputs, 3);
assert_eq!(stats.total_size, 9);
let input_pairs: Vec<InputPair> =
vec![b"qux".to_vec(), b"quux".to_vec(), b"corge".to_vec()]
.into_iter()
.map(|data| InputPair::try_from_data(data).unwrap())
.collect();
let stats = controller.add_to_corpus(input_pairs, fuzz::Corpus::Live).await?;
assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Live);
assert_eq!(stats.num_inputs, 3);
assert_eq!(stats.total_size, 12);
Ok(())
}
#[fuchsia::test]
async fn test_get_status() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
let expected = fuzz::Status {
running: Some(true),
runs: Some(1),
elapsed: Some(2),
covered_pcs: Some(3),
covered_features: Some(4),
corpus_num_inputs: Some(5),
corpus_total_size: Some(6),
process_stats: None,
..Default::default()
};
fake.set_status(expected.clone());
let actual = controller.get_status().await?;
assert_eq!(actual, expected);
Ok(())
}
#[fuchsia::test]
async fn test_try_one() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
let input_pair = InputPair::try_from_data(b"foo".to_vec())?;
controller.try_one(input_pair).await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, None);
assert_eq!(artifact.result, Some(FuzzResult::NoErrors));
fake.set_result(Ok(FuzzResult::Crash));
let input_pair = InputPair::try_from_data(b"bar".to_vec())?;
controller.try_one(input_pair).await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, None);
assert_eq!(artifact.result, Some(FuzzResult::Crash));
fake.cancel();
let input_pair = InputPair::try_from_data(b"baz".to_vec())?;
controller.try_one(input_pair).await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
Ok(())
}
#[fuchsia::test]
async fn test_fuzz() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
let options = fuzz::Options { runs: Some(10), ..Default::default() };
controller.configure(options).await?;
controller.fuzz().await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, None);
assert_eq!(artifact.result, Some(FuzzResult::NoErrors));
fake.set_result(Ok(FuzzResult::Death));
fake.set_input_to_send(b"foo");
controller.fuzz().await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, None);
assert_eq!(artifact.result, Some(FuzzResult::Death));
let fidl_input = artifact.input.context("invalid FIDL artifact")?;
let input = Input::try_receive(fidl_input).await?;
assert_eq!(input.data, b"foo");
fake.cancel();
controller.fuzz().await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
Ok(())
}
#[fuchsia::test]
async fn test_minimize() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
fake.set_input_to_send(b"foo");
let input_pair = InputPair::try_from_data(b"foofoofoo".to_vec())?;
controller.minimize(input_pair).await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, None);
assert_eq!(artifact.result, Some(FuzzResult::Minimized));
let fidl_input = artifact.input.context("invalid FIDL artifact")?;
let input = Input::try_receive(fidl_input).await?;
assert_eq!(input.data, b"foo");
fake.cancel();
let input_pair = InputPair::try_from_data(b"bar".to_vec())?;
controller.minimize(input_pair).await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
Ok(())
}
#[fuchsia::test]
async fn test_cleanse() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
fake.set_input_to_send(b" bar ");
let input_pair = InputPair::try_from_data(b"foobarbaz".to_vec())?;
controller.cleanse(input_pair).await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, None);
assert_eq!(artifact.result, Some(FuzzResult::Cleansed));
let fidl_input = artifact.input.context("invalid FIDL artifact")?;
let input = Input::try_receive(fidl_input).await?;
assert_eq!(input.data, b" bar ");
fake.cancel();
let input_pair = InputPair::try_from_data(b"foobarbaz".to_vec())?;
controller.cleanse(input_pair).await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
Ok(())
}
#[fuchsia::test]
async fn test_merge() -> Result<()> {
let test = Test::try_new()?;
let (fake, proxy, _task) = perform_test_setup(&test)?;
let controller = Controller::new(proxy, test.writer());
controller.merge().await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, None);
assert_eq!(artifact.result, Some(FuzzResult::Merged));
fake.cancel();
controller.merge().await?;
let artifact = controller.watch_artifact().await?;
assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
Ok(())
}
}