1use crate::constants::*;
6use crate::corpus;
7use crate::diagnostics::Forwarder;
8use crate::duration::deadline_after;
9use crate::input::{Input, InputPair};
10use crate::writer::{OutputSink, Writer};
11use anyhow::{anyhow, bail, Context as _, Error, Result};
12use fidl::endpoints::create_request_stream;
13use fidl::Error::ClientChannelClosed;
14use fidl_fuchsia_fuzzer::{self as fuzz, Artifact as FidlArtifact};
15use fuchsia_async::Timer;
16use futures::future::{pending, Either};
17use futures::{pin_mut, select, try_join, Future, FutureExt};
18use std::cell::RefCell;
19use std::cmp::max;
20use std::path::Path;
21use zx_status as zx;
22
23#[derive(Debug)]
25pub struct Controller<O: OutputSink> {
26 proxy: fuzz::ControllerProxy,
27 forwarder: Forwarder<O>,
28 min_timeout: i64,
29 timeout: RefCell<Option<i64>>,
30}
31
32impl<O: OutputSink> Controller<O> {
33 pub fn new(proxy: fuzz::ControllerProxy, writer: &Writer<O>) -> Self {
35 Self {
36 proxy,
37 forwarder: Forwarder::<O>::new(writer),
38 min_timeout: 60 * NANOS_PER_SECOND,
39 timeout: RefCell::new(None),
40 }
41 }
42
43 pub fn set_output<P: AsRef<Path>>(
45 &mut self,
46 socket: fidl::Socket,
47 output: fuzz::TestOutput,
48 logs_dir: &Option<P>,
49 ) -> Result<()> {
50 self.forwarder.set_output(socket, output, logs_dir)
51 }
52
53 pub fn set_min_timeout(&mut self, min_timeout: i64) {
61 self.min_timeout = min_timeout;
62 }
63
64 pub async fn configure(&self, options: fuzz::Options) -> Result<()> {
72 self.set_timeout(&options, None);
73 let result =
74 self.proxy.configure(&options).await.context("fuchsia.fuzzer/Controller.Configure")?;
75 result.map_err(|status| {
76 anyhow!("fuchsia.fuzzer/Controller.Configure returned: ZX_ERR_{}", status)
77 })
78 }
79
80 pub async fn get_options(&self) -> Result<fuzz::Options> {
85 self.proxy
86 .get_options()
87 .await
88 .map_err(Error::msg)
89 .context("`fuchsia.fuzzer.Controller/GetOptions` failed")
90 }
91
92 pub async fn reset_timer(&self) -> Result<()> {
98 let options = self.get_options().await?;
99 let status = self.get_status().await?;
100 self.set_timeout(&options, Some(status));
101 Ok(())
102 }
103
104 fn set_timeout(&self, options: &fuzz::Options, status: Option<fuzz::Status>) {
106 let elapsed = status.map(|s| s.elapsed.unwrap_or(0)).unwrap_or(0);
107 if let Some(max_total_time) = options.max_total_time {
108 let mut timeout_mut = self.timeout.borrow_mut();
109 match max_total_time {
110 0 => {
111 *timeout_mut = None;
112 }
113 n => {
114 *timeout_mut = Some(max(n * 2, self.min_timeout) - elapsed);
115 }
116 }
117 }
118 }
119
120 pub async fn read_corpus<P: AsRef<Path>>(
130 &self,
131 corpus_type: fuzz::Corpus,
132 corpus_dir: P,
133 ) -> Result<corpus::Stats> {
134 let (client_end, stream) = create_request_stream::<fuzz::CorpusReaderMarker>();
135 let (_, corpus_stats) = try_join!(
136 async { self.proxy.read_corpus(corpus_type, client_end).await.map_err(Error::msg) },
137 async { corpus::read(stream, corpus_dir).await },
138 )
139 .context("`fuchsia.fuzzer.Controller/ReadCorpus` failed")?;
140 Ok(corpus_stats)
141 }
142
143 pub async fn add_to_corpus(
154 &self,
155 input_pairs: Vec<InputPair>,
156 corpus_type: fuzz::Corpus,
157 ) -> Result<corpus::Stats> {
158 let expected_num_inputs = input_pairs.len();
159 let expected_total_size =
160 input_pairs.iter().fold(0, |total, input_pair| total + input_pair.len());
161 let mut corpus_stats = corpus::Stats { num_inputs: 0, total_size: 0 };
162 for input_pair in input_pairs.into_iter() {
163 let (fidl_input, input) = input_pair.as_tuple();
164 let fidl_input_size = fidl_input.size;
165 let (result, _) = try_join!(
166 async {
167 self.proxy.add_to_corpus(corpus_type, fidl_input).await.map_err(Error::msg)
168 },
169 input.send(),
170 )
171 .context("fuchsia.fuzzer/Controller.AddToCorpus failed")?;
172 if let Err(status) = result {
173 bail!(
174 "fuchsia.fuzzer/Controller.AddToCorpus returned: ZX_ERR_{} \
175 after writing {} of {} files ({} of {} bytes)",
176 status,
177 corpus_stats.num_inputs,
178 expected_num_inputs,
179 corpus_stats.total_size,
180 expected_total_size
181 )
182 }
183 corpus_stats.num_inputs += 1;
184 corpus_stats.total_size += fidl_input_size;
185 }
186 Ok(corpus_stats)
187 }
188
189 pub async fn get_status(&self) -> Result<fuzz::Status> {
197 match self.proxy.get_status().await {
198 Err(fidl::Error::ClientChannelClosed { status, .. })
199 if status == zx::Status::PEER_CLOSED =>
200 {
201 return Ok(fuzz::Status::default())
202 }
203 Err(e) => bail!("`fuchsia.fuzzer.Controller/GetStatus` failed: {:?}", e),
204 Ok(fuzz_status) => Ok(fuzz_status),
205 }
206 }
207
208 pub async fn fuzz(&self) -> Result<()> {
223 let response = self.proxy.fuzz().await;
224 let status = check_response("Fuzz", response)?;
225 check_status("Fuzz", status)
226 }
227
228 pub async fn try_one(&self, input_pair: InputPair) -> Result<()> {
236 let (fidl_input, input) = input_pair.as_tuple();
237 let status = self.with_input("TryOne", self.proxy.try_one(fidl_input), input).await?;
238 check_status("TryOne", status)
239 }
240
241 pub async fn minimize(&self, input_pair: InputPair) -> Result<()> {
254 let (fidl_input, input) = input_pair.as_tuple();
255 let status = self.with_input("Minimize", self.proxy.minimize(fidl_input), input).await?;
256 match status {
257 zx::Status::INVALID_ARGS => bail!("the provided input did not cause an error"),
258 status => check_status("Minimize", status),
259 }
260 }
261
262 pub async fn cleanse(&self, input_pair: InputPair) -> Result<()> {
274 let (fidl_input, input) = input_pair.as_tuple();
275 let status = self.with_input("Cleanse", self.proxy.cleanse(fidl_input), input).await?;
276 match status {
277 zx::Status::INVALID_ARGS => bail!("the provided input did not cause an error"),
278 status => check_status("Cleanse", status),
279 }
280 }
281
282 pub async fn merge(&self) -> Result<()> {
294 let response = self.proxy.merge().await;
295 let status = check_response("Merge", response)?;
296 match status {
297 zx::Status::INVALID_ARGS => bail!("an input in the seed corpus triggered an error"),
298 status => check_status("Merge", status),
299 }
300 }
301
302 async fn with_input<F>(&self, name: &str, fidl_fut: F, input: Input) -> Result<zx::Status>
304 where
305 F: Future<Output = Result<Result<(), i32>, fidl::Error>>,
306 {
307 let fidl_fut = fidl_fut.fuse();
308 let send_fut = input.send().fuse();
309 let timer_fut = match deadline_after(*self.timeout.borrow()) {
310 Some(deadline) => Either::Left(Timer::new(deadline)),
311 None => Either::Right(pending()),
312 };
313 let timer_fut = timer_fut.fuse();
314 pin_mut!(fidl_fut, send_fut, timer_fut);
315 let mut remaining = 2;
316 let mut status = zx::Status::OK;
317 while remaining > 0 && status == zx::Status::OK {
320 select! {
321 response = fidl_fut => {
322 status = check_response(name, response)?;
323 remaining -= 1;
324 }
325 result = send_fut => {
326 result?;
327 remaining -= 1;
328 }
329 _ = timer_fut => {
330 bail!("workflow timed out");
331 }
332 };
333 }
334 Ok(status)
335 }
336 pub async fn watch_artifact(&self) -> Result<FidlArtifact> {
345 let watch_fut = || async move {
346 loop {
347 let artifact = self.proxy.watch_artifact().await?;
348 if artifact != FidlArtifact::default() {
349 return Ok(artifact);
350 }
351 }
352 };
353 let watch_fut = watch_fut().fuse();
354 let forward_fut = self.forwarder.forward_all().fuse();
355 let timer_fut = match deadline_after(*self.timeout.borrow()) {
356 Some(deadline) => Either::Left(Timer::new(deadline)),
357 None => Either::Right(pending()),
358 };
359 let timer_fut = timer_fut.fuse();
360 pin_mut!(watch_fut, forward_fut, timer_fut);
361 let mut remaining = 2;
362 let mut fidl_artifact = FidlArtifact::default();
363 while remaining > 0 {
366 select! {
367 result = watch_fut => {
368 fidl_artifact = match result {
369 Ok(fidl_artifact) => {
370 if let Some(e) = fidl_artifact.error {
371 bail!("workflow returned an error: ZX_ERR_{}", e);
372 }
373 fidl_artifact
374 }
375 Err(ClientChannelClosed { status, .. }) if status == zx::Status::PEER_CLOSED => FidlArtifact { error: Some(zx::Status::CANCELED.into_raw()), ..Default::default() },
376 Err(e) => bail!("fuchsia.fuzzer/Controller.WatchArtifact: {:?}", e),
377 };
378 remaining -= 1;
379 }
380 result = forward_fut => {
381 result?;
382 remaining -= 1;
383 }
384 _ = timer_fut => {
385 bail!("workflow timed out");
386 }
387 };
388 }
389 Ok(fidl_artifact)
390 }
391}
392
393fn check_response(
395 name: &str,
396 response: Result<Result<(), i32>, fidl::Error>,
397) -> Result<zx::Status> {
398 match response {
399 Err(fidl::Error::ClientChannelClosed { status, .. })
400 if status == zx::Status::PEER_CLOSED =>
401 {
402 Ok(zx::Status::OK)
403 }
404 Err(e) => bail!("`fuchsia.fuzzer.Controller/{}` failed: {:?}", name, e),
405 Ok(Err(raw)) => Ok(zx::Status::from_raw(raw)),
406 Ok(Ok(())) => Ok(zx::Status::OK),
407 }
408}
409
410fn check_status(name: &str, status: zx::Status) -> Result<()> {
412 match status {
413 zx::Status::OK => Ok(()),
414 zx::Status::BAD_STATE => bail!("another long-running workflow is in progress"),
415 status => bail!("`fuchsia.fuzzer.Controller/{}` returned: ZX_ERR_{}", name, status),
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use crate::util::digest_path;
422 use anyhow::{Context as _, Result};
423 use fidl::endpoints::create_proxy_and_stream;
424 use fidl_fuchsia_fuzzer::{self as fuzz, Result_ as FuzzResult};
425 use fuchsia_fuzzctl::{Controller, Input, InputPair};
426 use fuchsia_fuzzctl_test::{create_task, serve_controller, verify_saved, FakeController, Test};
427 use {fuchsia_async as fasync, zx_status as zx};
428
429 fn perform_test_setup(
431 test: &Test,
432 ) -> Result<(FakeController, fuzz::ControllerProxy, fasync::Task<()>)> {
433 let fake = test.controller();
434 let (proxy, stream) = create_proxy_and_stream::<fuzz::ControllerMarker>();
435 let task = create_task(serve_controller(stream, test.clone()), test.writer());
436 Ok((fake, proxy, task))
437 }
438
439 #[fuchsia::test]
440 async fn test_configure() -> Result<()> {
441 let test = Test::try_new()?;
442 let (fake, proxy, _task) = perform_test_setup(&test)?;
443 let controller = Controller::new(proxy, test.writer());
444
445 let expected = fuzz::Options {
447 dictionary_level: Some(1),
448 detect_exits: Some(true),
449 detect_leaks: Some(false),
450 death_exitcode: Some(2),
451 debug: Some(true),
452 ..Default::default()
453 };
454 controller.configure(expected.clone()).await?;
455 let actual = fake.get_options();
456 assert_eq!(actual.dictionary_level, expected.dictionary_level);
457 assert_eq!(actual.detect_exits, expected.detect_exits);
458 assert_eq!(actual.detect_leaks, expected.detect_leaks);
459 assert_eq!(actual.death_exitcode, expected.death_exitcode);
460 assert_eq!(actual.debug, expected.debug);
461
462 Ok(())
463 }
464
465 #[fuchsia::test]
466 async fn test_get_options() -> Result<()> {
467 let test = Test::try_new()?;
468 let (fake, proxy, _task) = perform_test_setup(&test)?;
469 let controller = Controller::new(proxy, test.writer());
470
471 let expected = fuzz::Options {
473 max_total_time: Some(20000),
474 max_input_size: Some(2000),
475 mutation_depth: Some(20),
476 malloc_limit: Some(200),
477 malloc_exitcode: Some(2),
478 ..Default::default()
479 };
480 fake.set_options(expected.clone());
481 let actual = controller.get_options().await?;
482 assert_eq!(actual.max_total_time, expected.max_total_time);
483 assert_eq!(actual.max_input_size, expected.max_input_size);
484 assert_eq!(actual.mutation_depth, expected.mutation_depth);
485 assert_eq!(actual.malloc_limit, expected.malloc_limit);
486 assert_eq!(actual.malloc_exitcode, expected.malloc_exitcode);
487
488 Ok(())
489 }
490
491 #[fuchsia::test]
492 async fn test_read_corpus() -> Result<()> {
493 let test = Test::try_new()?;
494 let (fake, proxy, _task) = perform_test_setup(&test)?;
495 let controller = Controller::new(proxy, test.writer());
496
497 let seed_dir = test.create_dir("seed")?;
498 fake.set_input_to_send(b"foo");
499 let stats = controller.read_corpus(fuzz::Corpus::Seed, &seed_dir).await?;
500 assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Seed);
501 assert_eq!(stats.num_inputs, 1);
502 assert_eq!(stats.total_size, 3);
503 let path = digest_path(&seed_dir, None, b"foo");
504 verify_saved(&path, b"foo")?;
505
506 let live_dir = test.create_dir("live")?;
507 fake.set_input_to_send(b"barbaz");
508 let stats = controller.read_corpus(fuzz::Corpus::Live, &live_dir).await?;
509 assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Live);
510 assert_eq!(stats.num_inputs, 1);
511 assert_eq!(stats.total_size, 6);
512 let path = digest_path(&live_dir, None, b"barbaz");
513 verify_saved(&path, b"barbaz")?;
514
515 Ok(())
516 }
517
518 #[fuchsia::test]
519 async fn test_add_to_corpus() -> Result<()> {
520 let test = Test::try_new()?;
521 let (fake, proxy, _task) = perform_test_setup(&test)?;
522 let controller = Controller::new(proxy, test.writer());
523
524 let input_pairs: Vec<InputPair> = vec![b"foo".to_vec(), b"bar".to_vec(), b"baz".to_vec()]
525 .into_iter()
526 .map(|data| InputPair::try_from_data(data).unwrap())
527 .collect();
528 let stats = controller.add_to_corpus(input_pairs, fuzz::Corpus::Seed).await?;
529 assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Seed);
530 assert_eq!(stats.num_inputs, 3);
531 assert_eq!(stats.total_size, 9);
532
533 let input_pairs: Vec<InputPair> =
534 vec![b"qux".to_vec(), b"quux".to_vec(), b"corge".to_vec()]
535 .into_iter()
536 .map(|data| InputPair::try_from_data(data).unwrap())
537 .collect();
538 let stats = controller.add_to_corpus(input_pairs, fuzz::Corpus::Live).await?;
539 assert_eq!(fake.get_corpus_type(), fuzz::Corpus::Live);
540 assert_eq!(stats.num_inputs, 3);
541 assert_eq!(stats.total_size, 12);
542
543 Ok(())
544 }
545
546 #[fuchsia::test]
547 async fn test_get_status() -> Result<()> {
548 let test = Test::try_new()?;
549 let (fake, proxy, _task) = perform_test_setup(&test)?;
550 let controller = Controller::new(proxy, test.writer());
551
552 let expected = fuzz::Status {
553 running: Some(true),
554 runs: Some(1),
555 elapsed: Some(2),
556 covered_pcs: Some(3),
557 covered_features: Some(4),
558 corpus_num_inputs: Some(5),
559 corpus_total_size: Some(6),
560 process_stats: None,
561 ..Default::default()
562 };
563 fake.set_status(expected.clone());
564 let actual = controller.get_status().await?;
565 assert_eq!(actual, expected);
566
567 Ok(())
568 }
569
570 #[fuchsia::test]
571 async fn test_try_one() -> Result<()> {
572 let test = Test::try_new()?;
573 let (fake, proxy, _task) = perform_test_setup(&test)?;
574 let controller = Controller::new(proxy, test.writer());
575
576 let input_pair = InputPair::try_from_data(b"foo".to_vec())?;
577 controller.try_one(input_pair).await?;
578 let artifact = controller.watch_artifact().await?;
579 assert_eq!(artifact.error, None);
580 assert_eq!(artifact.result, Some(FuzzResult::NoErrors));
581
582 fake.set_result(Ok(FuzzResult::Crash));
583 let input_pair = InputPair::try_from_data(b"bar".to_vec())?;
584 controller.try_one(input_pair).await?;
585 let artifact = controller.watch_artifact().await?;
586 assert_eq!(artifact.error, None);
587 assert_eq!(artifact.result, Some(FuzzResult::Crash));
588
589 fake.cancel();
590 let input_pair = InputPair::try_from_data(b"baz".to_vec())?;
591 controller.try_one(input_pair).await?;
592 let artifact = controller.watch_artifact().await?;
593 assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
594
595 Ok(())
596 }
597
598 #[fuchsia::test]
599 async fn test_fuzz() -> Result<()> {
600 let test = Test::try_new()?;
601 let (fake, proxy, _task) = perform_test_setup(&test)?;
602 let controller = Controller::new(proxy, test.writer());
603
604 let options = fuzz::Options { runs: Some(10), ..Default::default() };
605 controller.configure(options).await?;
606 controller.fuzz().await?;
607 let artifact = controller.watch_artifact().await?;
608 assert_eq!(artifact.error, None);
609 assert_eq!(artifact.result, Some(FuzzResult::NoErrors));
610
611 fake.set_result(Ok(FuzzResult::Death));
612 fake.set_input_to_send(b"foo");
613 controller.fuzz().await?;
614 let artifact = controller.watch_artifact().await?;
615 assert_eq!(artifact.error, None);
616 assert_eq!(artifact.result, Some(FuzzResult::Death));
617
618 let fidl_input = artifact.input.context("invalid FIDL artifact")?;
619 let input = Input::try_receive(fidl_input).await?;
620 assert_eq!(input.data, b"foo");
621
622 fake.cancel();
623 controller.fuzz().await?;
624 let artifact = controller.watch_artifact().await?;
625 assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
626
627 Ok(())
628 }
629
630 #[fuchsia::test]
631 async fn test_minimize() -> Result<()> {
632 let test = Test::try_new()?;
633 let (fake, proxy, _task) = perform_test_setup(&test)?;
634 let controller = Controller::new(proxy, test.writer());
635
636 fake.set_input_to_send(b"foo");
637 let input_pair = InputPair::try_from_data(b"foofoofoo".to_vec())?;
638 controller.minimize(input_pair).await?;
639 let artifact = controller.watch_artifact().await?;
640 assert_eq!(artifact.error, None);
641 assert_eq!(artifact.result, Some(FuzzResult::Minimized));
642
643 let fidl_input = artifact.input.context("invalid FIDL artifact")?;
644 let input = Input::try_receive(fidl_input).await?;
645 assert_eq!(input.data, b"foo");
646
647 fake.cancel();
648 let input_pair = InputPair::try_from_data(b"bar".to_vec())?;
649 controller.minimize(input_pair).await?;
650 let artifact = controller.watch_artifact().await?;
651 assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
652
653 Ok(())
654 }
655
656 #[fuchsia::test]
657 async fn test_cleanse() -> Result<()> {
658 let test = Test::try_new()?;
659 let (fake, proxy, _task) = perform_test_setup(&test)?;
660 let controller = Controller::new(proxy, test.writer());
661
662 fake.set_input_to_send(b" bar ");
663 let input_pair = InputPair::try_from_data(b"foobarbaz".to_vec())?;
664 controller.cleanse(input_pair).await?;
665 let artifact = controller.watch_artifact().await?;
666 assert_eq!(artifact.error, None);
667 assert_eq!(artifact.result, Some(FuzzResult::Cleansed));
668
669 let fidl_input = artifact.input.context("invalid FIDL artifact")?;
670 let input = Input::try_receive(fidl_input).await?;
671 assert_eq!(input.data, b" bar ");
672
673 fake.cancel();
674 let input_pair = InputPair::try_from_data(b"foobarbaz".to_vec())?;
675 controller.cleanse(input_pair).await?;
676 let artifact = controller.watch_artifact().await?;
677 assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
678
679 Ok(())
680 }
681
682 #[fuchsia::test]
683 async fn test_merge() -> Result<()> {
684 let test = Test::try_new()?;
685 let (fake, proxy, _task) = perform_test_setup(&test)?;
686 let controller = Controller::new(proxy, test.writer());
687
688 controller.merge().await?;
689 let artifact = controller.watch_artifact().await?;
690 assert_eq!(artifact.error, None);
691 assert_eq!(artifact.result, Some(FuzzResult::Merged));
692
693 fake.cancel();
694 controller.merge().await?;
695 let artifact = controller.watch_artifact().await?;
696 assert_eq!(artifact.error, Some(zx::Status::CANCELED.into_raw()));
697
698 Ok(())
699 }
700}