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