| {% set rfcid = "RFC-0117" %} |
| {% include "docs/contribute/governance/rfcs/_common/_rfc_header.md" %} |
| # {{ rfc.name }} - {{ rfc.title }} |
| <!-- *** DO NOT EDIT ABOVE THIS LINE --> |
| |
| |
| ## Summary |
| |
| [Guided fuzzing][209] is an effective way of reducing bugs and increasing |
| confidence in a platform, but there currently is no fuzzing framework available |
| that can fuzz across multiple process boundaries as found in Fuchsia |
| [component topologies][204]. This document proposes a design for such a |
| framework that shares [coverage][301] and [test inputs][320] across processes |
| and within [test realms][203], allowing components to be fuzzed in their most |
| typical configurations. |
| |
| ## Motivation |
| |
| > Program testing can be used to show the presence of bugs, but never to show |
| > their absence! |
| > |
| > --<cite>[Edsger W. Dijkstra][325]</cite> |
| |
| Guided fuzzing is a process of testing software in a feedback loop with |
| generated data: |
| |
| 1. A fuzzer generates some **test input** data and uses it to test the |
| **target** software. |
| 1. If the test results in a failure, the fuzzer records the input and exits. |
| 1. Target software produces **feedback** that is collected by the fuzzer. |
| 1. The fuzzer uses the feedback to generate additional test inputs, and repeats. |
| |
| Guided fuzzing is extremely useful for finding software errors that are |
| unrelated to project requirements (and therefore often untested). By automating |
| test coverage, it can also improve developers' confidence of critical portions |
| of the system that have **security**, **correctness**, and/or **stability** |
| considerations. |
| |
| Guided fuzzing frameworks can be described using the following taxonomy: |
| <br>![Fuzzing taxonomy][407]<br> |
| |
| * **Engine**: The target-agnostic feedback loop. |
| * **Corpus management**: Maintains a collection of fuzzing inputs (a |
| **corpus**). Records new inputs and modifies existing ones (e.g. merging). |
| * The **seed corpus** is a set of handcrafted initial inputs. |
| * The **live corpus** is a continually updated set of generated inputs. |
| * **Mutators**: A set of **mutation strategies** and a source of deterministic |
| pseudo-randomness used to create new inputs from the corpus. |
| * **Feedback analysis**: Dispositions an input based on its feedback. |
| * **Management interface**: Interacts with the user to coordinate workflows: |
| * Exercising the target with a specific input, i.e. performing a single |
| **fuzzer run**. |
| * Fuzzing a target, i.e. performing a (possibly indefinite) sequence of |
| fuzzer runs. |
| * Analyzing or manipulating a given corpus. |
| * Responding to a detected error and/or processing the artifact that |
| caused it. |
| * **Target**: The specific target realm being fuzzed. |
| * **Input processing**: Maps the fuzzer input for a single run to the code |
| under test, e.g. via a specific function, an I/O pipe, etc. |
| * **Feedback collection**: Observes the behavior caused by an input. May |
| collect hardware or software traces, code-coverage data, timings, etc. |
| * **Error detection**: Determines when an input has caused an error. Collects |
| and records **test artifacts**, e.g. the input, logs, backtraces, etc. |
| |
| Several of these aspects may require specific support from the OS and/or its |
| toolchain, such as feedback collection and error detection. Currently on |
| Fuchsia, the most fully supported fuzzing framework is [libFuzzer][319], which |
| is delivered via the prebuilt [clang][215] toolchain as a |
| [compiler runtime][302]. Support has been added both to the |
| [sanitizer_common][312] runtime used to collect code coverage feedback, and to |
| libFuzzer itself to detect [exceptions][208]. Along with a set of |
| [GN templates][218] and [host tools][220], these allow developers to quickly |
| develop fuzzers for libraries on Fuchsia. |
| |
| Unlike Linux, however, on Fuchsia the basic executable units of software are |
| components, not libraries. Using existing guided fuzzing frameworks to fuzz |
| components is cumbersome, as the granularity of their feedback is either too |
| narrow (e.g. libFuzzer in a [single process][114]), or too broad (e.g. |
| [TriforceAFL][115] on an instance of qemu). |
| |
| An ideal framework for fuzzing components in Fuchsia has the following features: |
| |
| * Integration with existing continuous fuzzing infrastructures, such as |
| [ClusterFuzz][315]. |
| * A modular approach that can leverage platform-agnostic portions of other |
| fuzzing frameworks, e.g. mutation strategies. |
| * A high performance, cross-process code coverage mechanism. |
| * Integration with existing Fuchsia workflows, such as [`ffx`][223]. |
| * A hermetic environment that can isolate the components under test and/or |
| provide mock components for their dependencies. |
| * Unmodified source for target components. |
| * A robust and flexible approach to analyzing execution and detecting errors. |
| * A developer story similar to other styles of testing within Fuchsia. |
| |
| ## Design |
| |
| This design tries to: |
| |
| * Be idiomatic to Fuchsia. |
| * Reuse existing implementations. |
| |
| At a high-level the design leverages the test runner framework and adds: |
| |
| * A `fuzzer_engine` to drive fuzzing. |
| * An `ffx` plugin and fuzz manager to interact with and manage fuzzers. |
| * A `fuzz_test_runner` to connect the `fuzzer_engine` to the fuzz manager. |
| |
| <br>![Component fuzzing framework design][402]<br> |
| |
| This section of the document is organized roughly according to control flow; |
| i.e. it starts with a human or bot desiring to perform a fuzzing task and works |
| towards the target realm being fuzzed. The reader should be aware that some |
| sections refer to concepts described in detail in subsequent sections. |
| |
| ### `ffx fuzz` host tool |
| |
| Users (both humans and bots) interact with the framework via an |
| [`ffx` plugin][222]. This plugin will be able to communicate with a |
| [`fuzz_manager`][103] service via: |
| |
| * The [`fuchsia.fuzzer.Manager`][108] protocol. |
| * The [data transfer protocol][109]. |
| |
| The subcommands of `ffx fuzz` mirror those of `fx fuzz`, e.g.: |
| |
| * `analyze`: Report coverage info for a given corpus and/or dictionary. |
| * `check`: Check on the status of one or more fuzzers. |
| * `coverage`: Generate a coverage report for a test. |
| * `list`: List available fuzzers in the current build. |
| * `repro`: Reproduce fuzzer findings by replaying test units. |
| * `start`: Start a specific fuzzer. |
| * `stop`: Stop a specific fuzzer. |
| * `update`: Update the BUILD.gn file for a fuzzer corpus. |
| |
| ### Fuzz manager |
| |
| The [test runner framework][210] provides two important features: |
| |
| * It makes it easy to create complex yet hermetic test realms and drive them |
| with customizable [test runners][212]. |
| * It provides the means to collect important diagnostics such as logs and |
| backtraces. |
| |
| Moreover, a single fuzzing run can be naturally expressed in the terminology of |
| the component testing framework: the code is exercised with a given test input, |
| and can be thought of as having passed or failed depending on whether an error |
| occurred. |
| |
| However, fuzz testing does differ from other forms of testing, and this |
| difference is amplified when comparing *continuous fuzzing* to *continuous |
| testing*: |
| |
| * The test inputs are not known a priori. |
| * Test inputs are generated as a result of fuzzing. |
| * A continuous fuzzing infrastructure such as [ClusterFuzz][315] will have |
| many instances of a fuzzer and will "cross-pollinate" their test inputs |
| *while fuzzing is ongoing*. |
| * Fuzz test execution is open-ended. Fuzz tests never really "pass", they only |
| fail or are stopped early. |
| * A consequence is the need to provide on-demand status that includes details |
| not typically provided by other tests, such as execution speed, total |
| feedback collected, memory consumed, etc. |
| * This status needs to be provided on an ongoing basis to the human or fuzzing |
| infrastructure bot that is monitoring the fuzzer's execution. |
| * Fuzz test results are richer than simply pass/fail. |
| * On failure, outputs need to include the triggering input as well as any |
| associated logs and backtraces. |
| * On early termination, outputs may include accumulated feedback and |
| recommended parameters (e.g. dictionaries) for future fuzzing. |
| * A fuzzed realm can be used for several different workflows that the *fuzzing |
| infrastructure* chooses to perform in succession, e.g. "Fuzz for a while. If |
| an error is found, cleanse it, otherwise, merge and compact the corpus". |
| Representing each step as a test suite leads to significant work extracting |
| state from one step only to restore it on the next. |
| |
| Some of these can be addressed by extending the test runner framework, e.g. it |
| could provide structured outputs. However, using this approach for all the |
| fuzzing needs would add significant capabilities to other tests that do not need |
| them. For this reason, the design adds a new `fuzz_manager` that: |
| |
| * Provides the management interface to users via `ffx`. |
| * Interacts with the `test_manager` to launch fuzzers within a fuzzed realm in |
| the [test runner framework][104]. |
| * Provides a [`fuchsia.fuzzer.manager.Harness`][108] for those fuzzers to |
| connect back and service user requests. |
| * Provides a [data transfer protocol][109] to facilitate injecting data into or |
| extracting data from fuzzers. |
| |
| The test runner framework is then modified as follows: |
| |
| 1. A new `fuzz_test_runner` is added. This runner builds on the existing |
| `elf_test_runner` to start the `fuzzer_engine` and pass it the fuzzer URL. |
| 1. The `test_manager` is modified to route the |
| [`fuchsia.fuzzer.manager.Harness`][108] capability to the `fuzz_test_runner`. |
| This capability is *not* routed to tests, and the hermeticity of non-fuzzers |
| is unaffected. |
| 1. The `fuzz_test_runner` creates a channel pair for the |
| [`fuchsia.fuzzer.Controller`][108] protocol. It installs one end as a startup |
| handle in the `fuzzer_engine` and uses [`fuchsia.fuzzer.manager.Harness`][108] |
| to pass the other to the the `fuzz_manager`. |
| |
| ### Fuzzer engine |
| |
| The `fuzzer_engine` is a component of fuzzed realm. In terms of the |
| [fuzzer taxonomy][101], it: |
| |
| * Implements the `fuchsia.fuzzer.Controller` protocol to provide the |
| *management interface*. |
| * Creates and uses a storage capability to *manage each corpus*. |
| * *Mutates* inputs from the corpus to create new test inputs. (e.g. links |
| against [libMutagen][324]). |
| * `Uses` an `Adapter` capability to send new *inputs to be processed*. |
| * `Exposes` a `fuchsia.fuzzer.ProcessProxy` capability that instrumented remote |
| processes in the fuzzed realm can use to provide *collected feedback* and |
| *report errors*. |
| * *Analyzes the feedback*. |
| |
| If fuzzing is considered as a series of tests with different input, then one |
| approach is to having the fuzzer engine instantiate a fresh test realm for each |
| input, i.e. have a test *runner* perform each fuzzing *run* in succession. The |
| major problem with such an approach is the performance of the feedback analysis |
| and mutation loop. Fuzzer quality is directly tied to throughput, and the main |
| loop must be extremely fast: the overhead of "mutate, process input, collect |
| feedback, and analyze feedback" should be on the order of microseconds. |
| |
| For this reason, the fuzzer engine is included in the test realm itself in a |
| manner similar to the test driver used for [testing complex topologies][214]. |
| [Shared VMOs][207] coordinated by [eventpairs][205] are used to transfer test |
| inputs to the [fuzz target adapter][106] and feedback from |
| [instrumented remote processes][107] with the lowest possible latency. |
| |
| The fuzzer engine is started by a `fuzz_test_runner`. This runner is extremely |
| similar to the existing [`elf_test_runner`][211], with one significant addition: |
| It creates a channel pair for the `fuchsia.fuzzer.Controller` protocol. It |
| installs one end of this pair as a startup handle in the `fuzzer_engine`. It |
| passes the other to the `fuzz_manager` using the `fuchsia.fuzz.manager.Harness` |
| capability routed to it by the `test_manager`. This allows `test_manager` to |
| provide the `Harness` capability only to the `fuzz_test_runner` and the fuzzers |
| it starts, rather than to all tests. |
| |
| ### Target adapter |
| |
| The fuzz target adapter performs the *input processing* role in the |
| [fuzzer taxonomy][101]. Using the shared VMO and eventpair described above, it |
| takes the test inputs generated by the fuzzer engine and maps them to specific |
| interactions with the [instrumented remote processes][107] of the target realm |
| being fuzzed. |
| |
| These specific interactions are **provided by the fuzzer author** and are |
| typically the contribution referred to as "writing a fuzzer". |
| |
| A fuzzer author can provide their own custom implementation of a fuzz target |
| adapter, or use one of the provided scaffolds. |
| |
| Examples of possible adapter scaffolds include: |
| |
| * `llvm_fuzzer_adapter`: Expects authors to implement LLVM's |
| [fuzz target function][221]. |
| * For C/C++, authors implement: |
| |
| ```cpp |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); |
| ``` |
| |
| * For Rust, authors implement a method with `#[fuzz]` `proc_macro` attribute. |
| * For Go, authors implement: |
| |
| ```golang |
| func Fuzz(s []byte); |
| ``` |
| |
| * `realm_builder_adapter`: In addition to the LLVM fuzz target function, |
| authors implement a method that modifies a provided `RealmBuilder`. The |
| adapter provides a default builder to this function and uses the result to |
| build the realm of components to be fuzzed. Authors can modify it by adding |
| additional routes, capabilities, mocks, etc.: |
| |
| ```rust |
| pub trait FuzzedRealmBuilder { |
| fn extend(builder : &mut RealmBuilder); |
| } |
| ``` |
| |
| * `libfuzzer_adapter`: Similar expectations to `llvm_fuzzer_adapter`, but its |
| component manifest omits the fuzzer engine, exposes the [`Controller`][108] |
| capability itself, and links directly against [libFuzzer][319]. This |
| distinctly different component topology allows conventional library fuzzing |
| with libFuzzer in this framework. |
| * `honggfuzz-persistent-adapter`: Expects fuzzer authors to implement: |
| |
| ``` |
| extern HF_ITER(uint8_t** buf, size_t* len); |
| ``` |
| |
| [`honggfuzz`][311] itself is not currently supported, but fuzz target |
| functions written for it can still integrate with this framework. |
| |
| Note that the target adapter can and should also link against the remote library |
| and act as an instrumented remote process along with those in the instrumented |
| target. |
| |
| ### Instrumented remote processes |
| |
| In order to collect feedback and detect errors, all processes within the target |
| realm being fuzzed need to be built with additional instrumentation (e.g. |
| [SanitizerCoverage][301]). For fuzzers built in-tree, this can be achieved via a |
| [toolchain variant][201] that propagates `flags` and `deps` to a GN target's |
| dependencies. Required flags, e.g. `-fsanitize-coverage=inline-8bit-counters`, |
| will be documented to also allow out-of-tree compilation. |
| |
| Additionally, the processes also need a `fuchsia.fuzzer.ProcessProxy` client |
| implementation. The same toolchain variant described above can automatically add |
| a dependency to link processes for in-tree fuzzers against a remote library. |
| |
| The remote library provides, in terms of the [fuzzing taxonomy][101]: |
| |
| * *Feedback collection* via callbacks, e.g. |
| `__sanitizer_cov_inline_8bit_counters_init`. |
| * Early startup connection to the `fuzzer_engine`'s `ProcessProxy`. |
| * Background threads that can *detect errors*, e.g. by monitoring exceptions, |
| memory usage, etc. |
| |
| Out-of-tree fuzzers can provide their own client implementations. Adding the |
| `fuchsia.fuzzer.ProcessProxy` FIDL interface and remote library implementation |
| to the SDK will make writing out-of-tree fuzzers easier. |
| |
| Finally, the needed compile-time modifications are only transformations on LLVM |
| IR. All other modifications are link-time only. This enables service providers |
| to provide "fuzzing as a service" to SDK consumers who are willing to provide |
| [LLVM bytecode][318] for their components, without requiring source code. |
| |
| ### Component Topology |
| |
| Putting all of the above together, fuzzer component topology includes: |
| |
| * `core`: The system root component. |
| * `fuzz_manager`: Bridge in the root realm between the fuzzer and the host tool. |
| * `test_manager`: As in the [test runner framework][213]. |
| * `target_fuzzer`: Fuzzed realm entry point. |
| * `fuzzer_engine`: Target-agnostic fuzzing driver. |
| * `target_adapter`: Target-specific component with user-provided input |
| processing code. |
| * `instrumented_target`: Component being fuzzed. |
| |
| The `adapter` and `target` components may have additional children, such as |
| mocks and the target realm being fuzzed. |
| |
| The interactions of the pieces described above can be illustrated as follows: |
| <br>![Fuzzing framework topology][408]<br> |
| |
| ### FIDL interfaces |
| |
| The framework adds two FIDL libraries: one for interacting with the |
| `fuzz_manager`, and another for interacting with the fuzzers themselves. |
| |
| #### `fuchsia.fuzzer.manager` |
| |
| Types defined by `fuchsia.fuzzer.manager` include: |
| |
| * `LaunchError`: An extensible `enum` listing errors related to finding and |
| launching a fuzzer. |
| |
| Protocols defined by `fuchsia.fuzzer.manager` include: |
| |
| * `fuchsia.fuzzer.manager.Coordinator`: Served by `fuzz_manager` to the user via |
| `ffx`. Includes a method to start a fuzzer and connect a |
| `fuchsia.fuzzer.Controller`, and a method to stop fuzzers. |
| * `fuchsia.fuzzer.manager.Harness`: Served by `fuzz_manager` to the |
| `fuzz_test_runner`, via static routing through `core` and `test_manager`. The |
| runner uses this protocol to pass one end of a channel to the manager that can |
| be used for the `fuchsia.fuzzer.Controller` protocol. |
| |
| #### `fuchsia.fuzzer` |
| |
| Types defined by `fuchsia.fuzzer` include: |
| |
| * `Options`: An extensible `table` with parameters to configure execution, error |
| detection, etc. |
| * `Feedback`: A flexible `union` representing target feedback, e.g. code |
| coverage, traces, timings, etc. |
| * `Status`: An extensible `table` with various fuzzing metrics, e.g. total |
| coverage, speed, etc., |
| * `FuzzerError`: An extensible `enum` listing error categories, e.g. those |
| recognized by [ClusterFuzz][315]. |
| |
| Protocols defined by `fuchsia.fuzzer` include: |
| |
| * `fuchsia.fuzzer.Controller`: Provided by the `fuzzer_engine`, and passed to |
| the `fuzz_manager` via the `fuzz_test_runner`. Proxied by the `fuzz_manager` |
| to the user. Includes methods to transfer inputs to or artifacts from the |
| fuzzer, and perform workflows on a fuzzer like input minimization, corpus |
| merging, and normal fuzzing. |
| * `fuchsia.fuzzer.CorpusReader`: Requested from `fuchsia.fuzzer.Controller`. |
| Used to get inputs from a specific seed or live corpus. |
| * `fuchsia.fuzzer.CorpusWriter`: Requested from `fuchsia.fuzzer.Controller`. |
| Used to add inputs to a specific seed or live corpus. |
| * `fuchsia.fuzzer.Adapter`: Provided to the `fuzzer_engine` by the |
| developer-provided `target_adapter`. Includes a method to register the |
| coordinating eventpair and the shared VMO used to send test inputs. |
| * `fuchsia.fuzzer.ProcessProxy`: Provided by the `fuzzer_engine` to each |
| instrumented process in the fuzzed realm. Includes methods to register the |
| coordinating eventpair and to register shared VMOs used to provide feedback. |
| |
| ### Build utilities |
| |
| The framework provides a `fuchsia_fuzzer_package` GN template to developers. |
| This allows them to: |
| |
| * Automatically include the fuzzer_engine. |
| * Produce metadata that can be used by tooling, e.g. the location of a seed |
| corpus. |
| * Build integration tests instead of fuzzers when a non-fuzzing toolchain |
| variant is selected, as described in the [Testing][113] section. |
| * Reuse the build rules for the components under test from relevant integration |
| tests. |
| |
| The framework also includes a [component manifest shard][202] that includes |
| common elements needed for fuzzers, e.g. the `fuzzer_engine` and its |
| capabilities, the `fuzz_test_runner`, etc. A component manifest for a fuzzer |
| consists of: |
| |
| * The default fuzzer shard. |
| * A URL to the target-adapter component. |
| * A URL to manifest of the component(s) being fuzzed. This should typically be |
| reusable from a relevant integration test. |
| |
| Together, these build utilities designed to make the fuzzer development |
| experience similar to the [integration test development experience][214]. |
| Compare: |
| <br>![Test and fuzzer development process][403]<br> |
| |
| ## Implementation |
| |
| The implementation plan is straightforward: develop and unit test individual |
| classes in a series of changes, then assemble integration tests derived from |
| libFuzzer as discussed in the [Testing][113] section. |
| |
| ### Language |
| |
| The `fuzzer_engine` and `remote_library` are implemented in C++ to facilitate |
| their idiosyncrasies: |
| |
| * The `fuzzer_engine` and `remote_library` both must integrate with other C |
| ABIs, e.g. [libMutagen][324], [SanitizerCoverage][301], etc. |
| * Most `remote_library` functionality happens "before `main` and after `exit`", |
| i.e. when [LLVM modules][321] are constructed and/or loaded, when `atexit` |
| handlers are run, or when a fatal exception has been raised. As a result, the |
| framework needs explicit control over subtle details of ELF executables' |
| lifecycles. |
| |
| Other pieces, e.g. the `realm_builder_adapter`, are written in Rust. |
| |
| ### Data transfer protocol |
| |
| There are several situations in which users need to be able to provide or |
| retrieve arbitrary amounts of data, including: |
| |
| * Providing specific test inputs to execute, cleanse, or minimize. |
| * Synchronizing a fuzzer corpus with one on a developer's host, or across many |
| [ClusterFuzz][315] instances. |
| * Extracting the test input that triggered an error. |
| |
| To minimize maintenance burden, it is desirable to transfer this data using |
| [overnet][304]. However, any single transfer may [exceed the size][206] of a |
| single FIDL message over a Zircon channel. Instead, the [`Controller`][108] |
| protocol includes several methods that provide `zx_socket` objects which the |
| fuzzer engine uses to stream data to or from VMOs and/or locally stored files. |
| |
| The data is streamed using a minimal protocol to read or write a named sequence |
| of bytes. The protocol is *not* FIDL, as the data being sent may exceed the |
| maximum length of a FIDL message. Still the named bytes sequences are |
| conceptually equivalent to the following FIDL struct: |
| |
| ```fidl |
| struct NamedByteSequence { |
| uint32 name_length; |
| uint32 size; |
| bytes:name_length name; |
| bytes:size data; |
| }; |
| ``` |
| |
| ### Stack unwinding |
| |
| Currently, libFuzzer uses an unwinder from LLVM that assumes it is called from a |
| POSIX signal handler executing on the thread that triggered the signal. For |
| Fuchsia, this has necessitated a complex approach to handling exceptions that |
| includes modifying the stack of a crashed thread and injecting a backtrace- |
| preserving assembly trampoline to "resurrect" the thread in the unwinder. |
| |
| None of this is needed if errors are not being handled by libFuzzer. Instead, |
| different types errors are handled whichever way is most convenient and |
| effective, e.g.: |
| |
| * Exceptions are handled by the [fuzzer engine][104], which receives an |
| exception channel from the fuzz test runner that it created from its handle to |
| the test job. |
| * Timeouts are also managed by the fuzzer engine. |
| * Sanitizer callbacks and OOMs are handled by the remote library, which notifies |
| the fuzzer engine. |
| |
| ## Performance |
| |
| Fuzzing is not performed on production systems, and therefore has no impact on |
| the performance of any shipping code. While the inclusion of fuzzing toolchain |
| variants does have a minor impact on the performance of building Fuchsia, this |
| framework will reuse the existing variants and should add no new impacts. |
| |
| Similarly, the generation of unit tests from fuzzer on uninstrumented builds |
| mirrors the current approach and is not expected to add any significant |
| per-fuzzer testing costs over the current approach. |
| |
| For the fuzzers themselves, the most critical metric for determining fuzzer |
| quality is coverage per unit time, which can be derived by measuring two |
| additional metrics: |
| |
| 1. The total coverage of a fuzzer running over a fixed amount of time. |
| 1. The total number of runs performed in a fixed amount of time. |
| |
| [ClusterFuzz][315] already monitors and publishes these metrics for each fuzzer |
| on its dashboard. |
| |
| ## Ergonomics |
| |
| Ergonomics is an important facet of this design, as its impact depends on |
| adoption by developers. |
| |
| This framework attempts to make fuzzing as easy as possible in several ways. It |
| allows developers to: |
| |
| * Write fuzzers in both familiar and flexible ways, as noted in the |
| [target adapter][106] section. |
| * Build fuzzers using the existing family of [GN fuzzing templates][218]. |
| * Run fuzzers using familiar workflows. The usage of`ffx fuzz` is intentionally |
| similar to `fx fuzz`. |
| * Get actionable results. By integrating with [ClusterFuzz][315] bugs are filed |
| automatically with symbolized backtraces and reproduction instructions. |
| |
| ## Backwards Compatibility |
| |
| Existing libFuzzer-based fuzzers implement the [fuzz target function][320]. By |
| providing a libFuzzer-specific [fuzz target adapter][106], these fuzzers will be |
| able to work in this framework without any source modification. |
| |
| ## Security considerations |
| |
| This framework will not be used on a shipping product configuration. For devices |
| built in a fuzzing configuration, communication to and from the device will use |
| the existing authentication and secure communication features provided by |
| [`overnet`][109] and [`ffx`][102]. |
| |
| The fuzzer outputs may have security considerations, e.g. a test input may cause |
| an exploitable memory corruption. These concerns MUST be handled by the fuzzer |
| operator (human or fuzzing infrastructure) in the same manner as any other |
| exploitable bug report (e.g. correct labelling, prevention of unauthorized |
| disclosure, etc.). |
| |
| ## Privacy considerations |
| |
| When considering privacy implications, no assumptions are made about how the |
| fuzzer operator handles fuzzer outputs. These outputs consist of symbolized |
| logs, error-causing inputs, generated dictionaries, and generated corpora. |
| The logs are assumed to already be free of user data, as that is a separate |
| and closely monitored privacy concern. The remaining outputs are all directly |
| derived from test inputs. Thus, keeping fuzzer _inputs_ free of user data is |
| necessary and sufficient to keep fuzzer _outputs_ free of user data. |
| |
| There are three ways inputs are added to a fuzzer's corpora: |
| |
| * As seed inputs. The seed corpus should be checked in to the source repository. |
| The usual restrictions against including user data in the source repository |
| apply. |
| * As manual addtions to the live corpus. |
| * This will most typically be done by the fuzzing infrastructure, e.g. |
| [ClusterFuzz][315], as it "cross-pollinates" fuzzers with inputs produced by |
| other instances. In this case, the other instances will not contain user |
| data, and the added inputs will not either. |
| * It is also possible for a human operator to add inputs via [`ffx`][102]. |
| The tool will display warnings about user data when adding manual inputs in |
| this manner. |
| * As generated additions to the live corpus. These inputs are mutated from |
| existing inputs. Since those inputs are user-data free, the generated ones are |
| as well. It is possible some inputs may match some user-data by pure chance, |
| e.g. the fuzzer manages to generate a valid username. However, in this case |
| there is no clear association to user data. |
| |
| No other data is included in the corpus, even if the fuzzer is non-hermetic (and |
| non-deterministic!) and uses data from sources exposed by the test realm. The |
| framework will not consider that data as part of the test input and will not |
| save it. |
| |
| The worst-case scenario is a fuzzer that is designed to be intentionally |
| non-hermetic and uses exposed capabilities to send data _out_ of the test realm |
| to some other service that validates PII, e.g. return whether a username is |
| valid. This would require a noticeable amount of effort to circumvent the |
| fuzzing and test frameworks attempts to encourage hermeticity. And, since the |
| external service is uninstrumented, this is no better than random guessing. |
| |
| Additionally, in practice the fuzzers will be completely hermetic. They will not |
| be run on product configurations with user data, but only locally when |
| developing fuzzers and on ClusterFuzz. |
| |
| ## Testing |
| |
| The fuzzer engine, and target adapter libraries, and remote library are unit |
| tested using the usual approaches (e.g. [GoogleTest][317], `#[cfg(test)]`, |
| etc.). Additionally, integration tests use the default ELF test runner to run a |
| set of fuzzing workflows with purpose-built example targets, based on the |
| applicable [subset from compiler-rt][313]. |
| |
| For fuzzers written using the framework, the framework will adopt the same |
| approach as currently supported by the GN fuzzer templates: When building |
| fuzzers in an *uninstrumented* build, the engine will be replaced by a test |
| driver that simply executes each input in the seed corpus. This mitigates |
| "bit-rot" by ensuring all fuzzers can build and run. It also acts as a |
| regression test, especially if fuzzer authors maintain their seed corpora by |
| adding inputs when fixing defects found by fuzzing. |
| |
| ## Documentation |
| |
| The [fuzzing documentation tree][217] will need to be updated with specific |
| examples of using the new GN templates. Any other planned documentation changes |
| (e.g. code-labs, etc.) should reflect this framework as well. |
| |
| ## Drawbacks and alternatives |
| |
| Potential drawbacks to the proposed approach include: |
| |
| * Risk of performance degradation, mitigated by the implementation closely |
| imitating performance-critical sections of highly-optimized fuzzers. |
| * Maintenance burden, offset by the savings of not needing to maintain |
| awkward integrations, e.g. POSIX emulations. |
| * Coupling risk, e.g. the Test Runner Framework may change in a way that breaks |
| this design in the future, or may not be able to because of this design. If |
| this becomes a problem in the future, it could be addressed by incorporating |
| more of `test_manager`'s functionality directly into `fuzz_manager`, e.g. have |
| the latter create isolated test realms directly. |
| |
| These drawbacks are not as consequential as those of other alternatives |
| that have been explored: |
| |
| ### Library fuzzing only with libFuzzer. |
| |
| Sufficient Fuchsia support has been added to libFuzzer to build fuzzers with it |
| on Fuchsia. These have been successful in finding hundreds of bugs over the |
| last few years. |
| |
| At the same time, they are limited to single processes structured as libraries. |
| Since components are the unit of executable software on Fuchsia, and components |
| communicate extensively through FIDL, this leaves a large and growing amount of |
| Fuchsia code "unfuzzable" by this approach. |
| |
| <br>![Conventional libFuzzer][405]<br> |
| |
| ### In-process FIDL fuzzing. |
| |
| Projects such as Chrome have tried to address RPC fuzzing by running client and |
| server threads in a single process. This requires modifying both and client and |
| server to run in a new, non-standard configuration. This can be reusable between |
| services, but tends towards inflexible assumptions about component lifecycles |
| and/or per language-binding re-implementations. |
| |
| More fundamentally, it becomes increasingly difficult to fuzz the *closure* of |
| interacting components. Many components have a non-trivial topology. To either |
| run or mock the entire closure quickly becomes unsustainable in terms of |
| complexity, overhead, and performance. |
| |
| This approach is [already available][219] on Fuchsia, but has not seen |
| widespread adoption due at least in part to these limitations. |
| |
| <br>![In-process FIDL fuzzing][404]<br> |
| |
| ### Single-service FIDL fuzzing. |
| |
| An initial attempt at designing a cross-process FIDL fuzzing framework |
| considered a single client and service. In this design, libFuzzer was linked |
| against the *service*, and the client was maintained as a simple proxy. By |
| retaining the FIDL interface between the client and server, it could keep the |
| target in a more typical configuration, allowing for more flexible service |
| lifecycles and less code needing to be reimplemented. |
| |
| However, it does not address the problem of fuzzing component closures, and |
| therefore provides very limited benefit over in-process FIDL fuzzing. |
| |
| <br>![Single-service FIDL fuzzing][406]<br> |
| |
| ### LibFuzzer with support for cross-process fuzzing. |
| |
| As a general principle, reusing code has several advantages over reimplementing |
| it: the code is typically more "mature", with better performance and fewer bugs, |
| and has a lower and shared maintenance cost. For these reasons, another prior |
| attempt sought to extend libFuzzer rather than design and implement a new |
| fuzzing framework. A new compiler runtime, `clang_rt.fuzzer-remote.a`, would act |
| as the remote library above, while libFuzzer itself could be used as the engine. |
| Both of these compiler runtimes would use a pair of OS-specific IPC transport |
| libraries to proxy method calls to the other process. |
| |
| In coordination with libFuzzer's maintainers, a series of changes implemented |
| both runtimes and published them for [review][322]. Additionally, |
| implementations of the IPC transport libraries were developed for both Linux and |
| Fuchsia. The maintainers explicitly requested Linux support to allow for |
| continuous testing, and it was again sent for [review][323]. |
| |
| * On Linux, the shared memory was created as an anonymously mapped file, i.e. |
| via `memfd_create`, and the signals were simply messages passed via Unix |
| domain sockets. These sockets were also used to transfer the shared memory |
| file descriptors, i.e. via `sendmsg` and `recvmsg`. |
| * On Fuchsia, the shared memory was implemented using VMOs, the signals via |
| eventpairs, and the exchange via FIDL messages in a manner similar to the |
| design in this proposal. |
| |
| Unfortunately, during extended review, this approach became infeasible not for |
| technical reasons but for process ones: Over time, the libFuzzer maintainers |
| became increasingly concerned at the scope of the necessary changes required to |
| make libFuzzer act in a way it was not originally designed for. Eventually, the |
| team decided to defer landing the proposed changes indefinitely. |
| |
| <br>![Single-service FIDL fuzzing][401]<br> |
| |
| ### AFL |
| |
| LibFuzzer is by no means the only fuzzing framework. Some, such as [AFL][309], |
| were explicitly designed to be cross-process from the start. However, there are |
| a few reasons AFL would require more investment than might otherwise be assumed: |
| |
| * AFL assumes it is fuzzing a single process, so it still faces the closure |
| problem. |
| * AFL makes heavy use of certain Linux and/or POSIX features for feedback and |
| error detection. These include POSIX signals, but even more significantly, |
| considerable use of the `/proc` filesystem, for which there is (correctly) no |
| analogue on Fuchsia. |
| * AFL uses a modified GCC to instrument the code, which is not part of Fuchsia's |
| toolchain. |
| |
| [AFLplusplus][305] is an improved fork of AFL maintained by a set of security |
| researchers and CTF competitors. It has excellent performance on |
| [FuzzBench][316], and has modularized AFL. Unfortunately, the |
| [first version][307] is deprecated, and the [second][306] is not ready yet (or |
| at least is not mature enough to force altering the design above). Still, |
| several pieces align with this proposal's design, and there are future |
| opportunities to integrate them to improve the framework's coverage, speed, or |
| both. |
| |
| ### AFL with qemu |
| |
| Additionally, there have been a few projects that combine AFL with qemu: |
| |
| * [afl-unicorn][308] combines AFL with [Unicorn][327], a project that exposes |
| the CPU emulation core of qemu with a fairly clean interface. This allows |
| fuzzing opaque binaries without source by collecting coverage feedback from |
| the CPU emulation. It is not suitable for a component framework for a few |
| reasons: |
| * The integration with qemu's core CPU emulation is complex enough that |
| Unicorn has decided to forgo following qemu development and is locked to |
| v2.1.2 (compared with current version 6.0.0 of qemu). Code that expects |
| more recent emulation features is unlikely to function correctly. |
| * There is no significant need for opaque binary fuzzing. In fact, the design |
| only requires the target code to be instrumented and linked against the |
| remote library; LLVM byte code is sufficient to achieve this. |
| * [TriforceAFL][314] uses AFL on a complete, instrumented qemu instance. This |
| again allows fuzzing opaque binaries without source by collecting coverage |
| from qemu itself. It is not suitable for similar reasons as afl-unicorn: |
| * Again, there is no significant need for opaque binary fuzzing. |
| * Additionally, since the coverage collected is of the entire instance, |
| fuzzing with TriforceAFL tends to be very noisy, especially with many |
| components running. It typically is only useful for fuzzing extremely |
| constrained configurations, such as a USB driver immediately after booting. |
| |
| [1xx]: # "1xx links refer to a section in this document." |
| [101]: #motivation |
| [102]: #ffx_fuzz_host_tool |
| [103]: #fuzz_manager |
| [104]: #fuzzer_engine |
| [106]: #target_adapter |
| [107]: #instrumented_remote_processes |
| [108]: #fidl_interfaces |
| [109]: #data_transfer_protocol |
| [110]: #stack_unwinding |
| [111]: #performance |
| [112]: #security_considerations |
| [113]: #testing |
| [114]: #single_service_fidl_fuzzing |
| [115]: #afl_with_qemu |
| |
| [2xx]: # "2xx links refer to other Fuchsia documentation." |
| [201]: /docs/development/build/build_system/variants.md |
| [202]: /docs/concepts/components/v2/component_manifests.md#include |
| [203]: /docs/concepts/components/v2/realms.md |
| [204]: /docs/concepts/components/v2/topology.md |
| [205]: /docs/concepts/kernel/concepts.md#events_event_pairs |
| [206]: /docs/concepts/kernel/concepts.md#message_passing_sockets_and_channels |
| [207]: /docs/concepts/kernel/concepts.md#shared_memory_virtual_memory_objects_vmos |
| [208]: /docs/concepts/kernel/exceptions.md |
| [209]: /docs/contribute/testing/fuzz_testing.md#coverage-guided-fuzzing |
| [210]: /docs/development/testing/components/test_runner_framework.md |
| [211]: /docs/development/testing/components/test_runner_framework.md#elf-test-runner |
| [212]: /docs/development/testing/components/test_runner_framework.md#test-runners |
| [213]: /docs/development/testing/components/test_runner_framework.md#the_test_manager |
| [214]: /docs/development/testing/components/integration_testing.md |
| [215]: /docs/development/build/toolchain.md |
| [216]: /docs/development/languages |
| [217]: /docs/development/testing/fuzzing/overview.md |
| [218]: /docs/development/testing/fuzzing/build-a-fuzzer.md |
| [219]: /docs/development/testing/fuzzing/fidl-fuzzing.md |
| [220]: /docs/development/testing/fuzzing/run-a-fuzzer.md |
| [221]: /docs/development/testing/fuzzing/write-a-fuzzer.md |
| [222]: /docs/development/tools/ffx/development/plugins.md |
| [223]: /docs/development/tools/ffx/overview.md |
| |
| [3xx]: # "3xx links refer to external documentation." |
| [301]: https://clang.llvm.org/docs/SanitizerCoverage.html |
| [302]: https://compiler-rt.llvm.org/ |
| [303]: https://datatracker.ietf.org/doc/html/rfc5487 |
| [304]: https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/connectivity/overnet/README.md |
| [305]: https://github.com/AFLplusplus/AFLplusplus |
| [306]: https://github.com/AFLplusplus/LibAFL |
| [307]: https://github.com/AFLplusplus/LibAFL-legacy |
| [308]: https://github.com/Battelle/afl-unicorn |
| [309]: https://github.com/google/AFL |
| [310]: https://github.com/google/AFL/blob/stable/dictionaries/README.dictionaries |
| [311]: https://github.com/google/honggfuzz/blob/2.4/docs/PersistentFuzzing.md |
| [312]: https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/sanitizer_common/sanitizer_fuchsia.cpp |
| [313]: https://github.com/llvm/llvm-project/tree/main/compiler-rt/test/fuzzer |
| [314]: https://github.com/nccgroup/TriforceAFL |
| [315]: https://google.github.io/clusterfuzz/ |
| [316]: https://google.github.io/fuzzbench/ |
| [317]: https://google.github.io/googletest/primer.html |
| [318]: https://llvm.org/docs/BitCodeFormat.html |
| [319]: https://llvm.org/docs/LibFuzzer.html |
| [320]: https://llvm.org/docs/LibFuzzer.html#fuzz-target |
| [321]: https://llvm.org/doxygen/classllvm_1_1Module.html#details |
| [322]: https://reviews.llvm.org/D94523 |
| [323]: https://reviews.llvm.org/D94527 |
| [324]: https://reviews.llvm.org/D102447 |
| [325]: https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF |
| [326]: https://www.openssl.org/docs/man1.1.1/man3/SSL_accept.html |
| [327]: https://www.unicorn-engine.org/ |
| |
| |
| [4xx]: # "4xx links refer to image resources." |
| [401]: resources/0117_component_fuzzing/cross-process.png |
| [402]: resources/0117_component_fuzzing/design.png |
| [403]: resources/0117_component_fuzzing/development.png |
| [404]: resources/0117_component_fuzzing/in-process.png |
| [405]: resources/0117_component_fuzzing/libfuzzer.png |
| [406]: resources/0117_component_fuzzing/single-service.png |
| [407]: resources/0117_component_fuzzing/taxonomy.png |
| [408]: resources/0117_component_fuzzing/topology.png |