Skip to main content

fuchsia_fuzzctl_test/
controller.rs

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.
4
5use crate::diagnostics::send_log_entry;
6use crate::options::add_defaults;
7use crate::test::Test;
8use anyhow::{Context as _, Result, anyhow};
9use flex_fuchsia_fuzzer::{
10    self as fuzz, Artifact as FidlArtifact, Input as FidlInput, Result_ as FuzzResult,
11};
12use fuchsia_fuzzctl::InputPair;
13use futures::{AsyncReadExt, AsyncWriteExt, StreamExt, join};
14use std::cell::RefCell;
15use std::rc::Rc;
16use zx_status as zx;
17
18/// Test fake that allows configuring how to respond to `fuchsia.fuzzer.Controller` methods.
19///
20/// These fields are Rc<RefCell<_>> in order to be cloned and shared with the `Task` serving the
21/// controller. Unit tests can use this object to query values passed in FIDL requests and set
22/// values returned by FIDL responses.
23#[derive(Debug)]
24pub struct FakeController {
25    corpus_type: Rc<RefCell<fuzz::Corpus>>,
26    input_to_send: Rc<RefCell<Option<Vec<u8>>>>,
27    options: Rc<RefCell<fuzz::Options>>,
28    received_input: Rc<RefCell<Vec<u8>>>,
29    result: Rc<RefCell<Result<FuzzResult, zx::Status>>>,
30    status: Rc<RefCell<fuzz::Status>>,
31    stdout: Rc<RefCell<Option<flex_client::AsyncSocket>>>,
32    stderr: Rc<RefCell<Option<flex_client::AsyncSocket>>>,
33    syslog: Rc<RefCell<Option<flex_client::AsyncSocket>>>,
34    canceled: Rc<RefCell<bool>>,
35}
36
37impl FakeController {
38    /// Creates a fake fuzzer that can serve `fuchsia.fuzzer.Controller`.
39    pub fn new() -> Self {
40        let status = fuzz::Status { running: Some(false), ..Default::default() };
41        let mut options = fuzz::Options::default();
42        add_defaults(&mut options);
43        Self {
44            corpus_type: Rc::new(RefCell::new(fuzz::Corpus::Seed)),
45            input_to_send: Rc::new(RefCell::new(None)),
46            options: Rc::new(RefCell::new(options)),
47            received_input: Rc::new(RefCell::new(Vec::new())),
48            result: Rc::new(RefCell::new(Ok(FuzzResult::NoErrors))),
49            status: Rc::new(RefCell::new(status)),
50            stdout: Rc::new(RefCell::new(None)),
51            stderr: Rc::new(RefCell::new(None)),
52            syslog: Rc::new(RefCell::new(None)),
53            canceled: Rc::new(RefCell::new(false)),
54        }
55    }
56
57    /// Simulates a call to `fuchsia.fuzzer.Manager/GetOutput` without a `fuzz-manager`.
58    pub fn set_output(
59        &self,
60        output: fuzz::TestOutput,
61        socket: flex_client::Socket,
62    ) -> Result<(), zx::Status> {
63        let socket = flex_client::socket_to_async(socket);
64        match output {
65            fuzz::TestOutput::Stdout => {
66                let mut stdout_mut = self.stdout.borrow_mut();
67                *stdout_mut = Some(socket);
68            }
69            fuzz::TestOutput::Stderr => {
70                let mut stderr_mut = self.stderr.borrow_mut();
71                *stderr_mut = Some(socket);
72            }
73            fuzz::TestOutput::Syslog => {
74                let mut syslog_mut = self.syslog.borrow_mut();
75                *syslog_mut = Some(socket);
76            }
77            _ => todo!("not supported"),
78        }
79        Ok(())
80    }
81
82    /// Returns the type of corpus received via FIDL requests.
83    pub fn get_corpus_type(&self) -> fuzz::Corpus {
84        self.corpus_type.borrow().clone()
85    }
86
87    /// Sets the type of corpus to return via FIDL responses.
88    pub fn set_corpus_type(&self, corpus_type: fuzz::Corpus) {
89        let mut corpus_type_mut = self.corpus_type.borrow_mut();
90        *corpus_type_mut = corpus_type;
91    }
92
93    /// Returns the test input to be sent via a FIDL response.
94    pub fn take_input_to_send(&self) -> Option<Vec<u8>> {
95        self.input_to_send.borrow_mut().take()
96    }
97
98    /// Sets the test input to be sent via a FIDL response.
99    pub fn set_input_to_send(&self, input_to_send: &[u8]) {
100        let mut input_to_send_mut = self.input_to_send.borrow_mut();
101        *input_to_send_mut = Some(input_to_send.to_vec());
102    }
103
104    /// Returns the options received via FIDL requests.
105    pub fn get_options(&self) -> fuzz::Options {
106        self.options.borrow().clone()
107    }
108
109    /// Sets the options to return via FIDL responses.
110    pub fn set_options(&self, mut options: fuzz::Options) {
111        add_defaults(&mut options);
112        let mut options_mut = self.options.borrow_mut();
113        *options_mut = options;
114    }
115
116    /// Returns the test input received via FIDL requests.
117    pub fn get_received_input(&self) -> Vec<u8> {
118        self.received_input.borrow().clone()
119    }
120
121    /// Reads test input data from a `fuchsia.fuzzer.Input` from a FIDL request.
122    async fn receive_input(&self, input: FidlInput) -> Result<()> {
123        let mut received_input = Vec::new();
124        let mut reader = flex_client::socket_to_async(input.socket);
125        reader.read_to_end(&mut received_input).await?;
126        let mut received_input_mut = self.received_input.borrow_mut();
127        *received_input_mut = received_input;
128        Ok(())
129    }
130
131    /// Returns the fuzzing result to be sent via a FIDL response.
132    pub fn get_result(&self) -> Result<FuzzResult, zx::Status> {
133        self.result.borrow().clone()
134    }
135
136    /// Sets the fuzzing result to be sent via a FIDL response.
137    pub fn set_result(&self, result: Result<FuzzResult, zx::Status>) {
138        let mut result_mut = self.result.borrow_mut();
139        *result_mut = result;
140    }
141
142    /// Gets the fuzzer status to be sent via FIDL responses.
143    pub fn get_status(&self) -> fuzz::Status {
144        self.status.borrow().clone()
145    }
146
147    /// Sets the fuzzer status to be sent via FIDL responses.
148    pub fn set_status(&self, status: fuzz::Status) {
149        let mut status_mut = self.status.borrow_mut();
150        *status_mut = status;
151    }
152
153    // Simulates sending a `msg` to a fuzzer's standard output, standard error, and system log.
154    async fn send_output(&self, msg: &str) -> Result<()> {
155        let msg_str = format!("{}\n", msg);
156        {
157            let mut stdout_mut = self.stdout.borrow_mut();
158            if let Some(mut stdout) = stdout_mut.take() {
159                stdout.write_all(msg_str.as_bytes()).await?;
160                *stdout_mut = Some(stdout);
161            }
162        }
163        {
164            let mut stderr_mut = self.stderr.borrow_mut();
165            if let Some(mut stderr) = stderr_mut.take() {
166                stderr.write_all(msg_str.as_bytes()).await?;
167                *stderr_mut = Some(stderr);
168            }
169        }
170        {
171            let mut syslog_mut = self.syslog.borrow_mut();
172            if let Some(mut syslog) = syslog_mut.take() {
173                send_log_entry(&mut syslog, msg).await?;
174                *syslog_mut = Some(syslog);
175            }
176        }
177        Ok(())
178    }
179
180    /// Simulates a long-running workflow being canceled by `fuchsia.fuzzer.Manager/Stop`.
181    pub fn cancel(&self) {
182        let mut canceled_mut = self.canceled.borrow_mut();
183        *canceled_mut = true;
184    }
185
186    /// Get whether a simulated call to `fuchsia.fuzzer.Manager/Stop` has been made.
187    pub fn is_canceled(&self) -> bool {
188        *self.canceled.borrow()
189    }
190}
191
192impl Clone for FakeController {
193    fn clone(&self) -> Self {
194        Self {
195            corpus_type: Rc::clone(&self.corpus_type),
196            input_to_send: Rc::clone(&self.input_to_send),
197            options: Rc::clone(&self.options),
198            received_input: Rc::clone(&self.received_input),
199            result: Rc::clone(&self.result),
200            status: Rc::clone(&self.status),
201            stdout: Rc::clone(&self.stdout),
202            stderr: Rc::clone(&self.stderr),
203            syslog: Rc::clone(&self.syslog),
204            canceled: Rc::clone(&self.canceled),
205        }
206    }
207}
208
209/// Serves `fuchsia.fuzzer.Controller` using test fakes.
210pub async fn serve_controller(
211    mut stream: fuzz::ControllerRequestStream,
212    mut test: Test,
213) -> Result<()> {
214    let fake = test.controller();
215    let mut artifact = Some(FidlArtifact::default());
216    let mut watcher: Option<fuzz::ControllerWatchArtifactResponder> = None;
217    // Helper function to send an empty artifact when a workflow is starting.
218    fn reset_artifact(mut watcher: Option<fuzz::ControllerWatchArtifactResponder>) -> Result<()> {
219        if let Some(watcher) = watcher.take() {
220            watcher.send(FidlArtifact::default())?;
221        }
222        Ok(())
223    }
224    loop {
225        let request = stream.next().await;
226        if fake.is_canceled() {
227            break;
228        }
229        match request {
230            Some(Ok(fuzz::ControllerRequest::Configure { options, responder })) => {
231                test.record("fuchsia.fuzzer/Controller.Configure");
232                fake.set_options(options);
233                responder.send(Ok(()))?;
234            }
235            Some(Ok(fuzz::ControllerRequest::GetOptions { responder })) => {
236                test.record("fuchsia.fuzzer/Controller.GetOptions");
237                let options = fake.get_options();
238                responder.send(&options)?;
239            }
240            Some(Ok(fuzz::ControllerRequest::AddToCorpus { corpus, input, responder })) => {
241                test.record(format!("fuchsia.fuzzer/Controller.AddToCorpus({:?})", corpus));
242                fake.receive_input(input).await?;
243                fake.set_corpus_type(corpus);
244                responder.send(Ok(()))?;
245            }
246            Some(Ok(fuzz::ControllerRequest::ReadCorpus { corpus, corpus_reader, responder })) => {
247                test.record("fuchsia.fuzzer/Controller.ReadCorpus");
248                fake.set_corpus_type(corpus);
249                let corpus_reader = corpus_reader.into_proxy();
250                if let Some(input_to_send) = fake.take_input_to_send() {
251                    let input_pair = InputPair::try_from_data(&test.domain(), input_to_send)?;
252                    let (fidl_input, input) = input_pair.as_tuple();
253                    let corpus_fut = corpus_reader.next(fidl_input);
254                    let input_fut = input.send();
255                    let results = join!(corpus_fut, input_fut);
256                    assert!(results.0.is_ok());
257                    assert!(results.1.is_ok());
258                }
259                responder.send()?;
260            }
261            Some(Ok(fuzz::ControllerRequest::GetStatus { responder })) => {
262                test.record("fuchsia.fuzzer/Controller.GetStatus");
263                let status = fake.get_status();
264                responder.send(&status)?;
265            }
266            Some(Ok(fuzz::ControllerRequest::Fuzz { responder })) => {
267                test.record("fuchsia.fuzzer/Controller.Fuzz");
268                reset_artifact(watcher.take())?;
269                // As special cases, fuzzing indefinitely without any errors or fuzzing with an
270                // explicit error of `SHOULD_WAIT` will imitate a FIDL call that does not
271                // complete. These can be interrupted by the shell or allowed to timeout.
272                let result = fake.get_result();
273                let options = fake.get_options();
274                match (options.runs, options.max_total_time, result) {
275                    (Some(0), Some(0), Ok(FuzzResult::NoErrors))
276                    | (_, _, Err(zx::Status::SHOULD_WAIT)) => {
277                        let mut status = fake.get_status();
278                        status.running = Some(true);
279                        fake.set_status(status);
280                        responder.send(Ok(()))?;
281                    }
282                    (_, _, Ok(fuzz_result)) => {
283                        let input_to_send = fake.take_input_to_send().unwrap_or(Vec::new());
284                        let input_pair = InputPair::try_from_data(&test.domain(), input_to_send)?;
285                        let (fidl_input, input) = input_pair.as_tuple();
286                        artifact = Some(FidlArtifact {
287                            result: Some(fuzz_result),
288                            input: Some(fidl_input),
289                            ..Default::default()
290                        });
291                        responder.send(Ok(()))?;
292                        input.send().await?;
293                        fake.send_output(fuzz::DONE_MARKER).await?;
294                    }
295                    (_, _, Err(status)) => {
296                        responder.send(Err(status.into_raw()))?;
297                    }
298                };
299            }
300            Some(Ok(fuzz::ControllerRequest::TryOne { test_input, responder })) => {
301                test.record("fuchsia.fuzzer/Controller.TryOne");
302                reset_artifact(watcher.take())?;
303                fake.receive_input(test_input).await?;
304                match fake.get_result() {
305                    Ok(fuzz_result) => {
306                        artifact = Some(FidlArtifact {
307                            result: Some(fuzz_result),
308                            input: None,
309                            ..Default::default()
310                        });
311                        responder.send(Ok(()))?;
312                        fake.send_output(fuzz::DONE_MARKER).await?;
313                    }
314                    Err(status) => {
315                        responder.send(Err(status.into_raw()))?;
316                    }
317                }
318            }
319            Some(Ok(fuzz::ControllerRequest::Minimize { test_input, responder })) => {
320                test.record("fuchsia.fuzzer/Controller.Minimize");
321                reset_artifact(watcher.take())?;
322                fake.receive_input(test_input).await?;
323                let input_to_send = fake.take_input_to_send().context("input_to_send unset")?;
324                let input_pair = InputPair::try_from_data(&test.domain(), input_to_send)?;
325                let (fidl_input, input) = input_pair.as_tuple();
326                artifact = Some(FidlArtifact {
327                    result: Some(FuzzResult::Minimized),
328                    input: Some(fidl_input),
329                    ..Default::default()
330                });
331                responder.send(Ok(()))?;
332                input.send().await?;
333                fake.send_output(fuzz::DONE_MARKER).await?;
334            }
335            Some(Ok(fuzz::ControllerRequest::Cleanse { test_input, responder })) => {
336                test.record("fuchsia.fuzzer/Controller.Cleanse");
337                reset_artifact(watcher.take())?;
338                fake.receive_input(test_input).await?;
339                let input_to_send = fake.take_input_to_send().context("input_to_send unset")?;
340                let input_pair = InputPair::try_from_data(&test.domain(), input_to_send)?;
341                let (fidl_input, input) = input_pair.as_tuple();
342                artifact = Some(FidlArtifact {
343                    result: Some(FuzzResult::Cleansed),
344                    input: Some(fidl_input),
345                    ..Default::default()
346                });
347                responder.send(Ok(()))?;
348                input.send().await?;
349                fake.send_output(fuzz::DONE_MARKER).await?;
350            }
351            Some(Ok(fuzz::ControllerRequest::Merge { responder })) => {
352                test.record("fuchsia.fuzzer/Controller.Merge");
353                reset_artifact(watcher.take())?;
354                artifact = Some(FidlArtifact {
355                    result: Some(FuzzResult::Merged),
356                    input: None,
357                    ..Default::default()
358                });
359                responder.send(Ok(()))?;
360                fake.send_output(fuzz::DONE_MARKER).await?;
361            }
362            Some(Ok(fuzz::ControllerRequest::WatchArtifact { responder })) => {
363                match artifact.take() {
364                    Some(artifact) => {
365                        responder.send(artifact)?;
366                    }
367                    None => {
368                        watcher = Some(responder);
369                    }
370                };
371            }
372            Some(Err(e)) => return Err(anyhow!(e)),
373            None => break,
374            _ => todo!("not yet implemented"),
375        };
376    }
377    Ok(())
378}