| # Fuzzing FIDL |
| |
| Note: This is an area of active development. The instructions and results below should be considered |
| experimental. |
| |
| ## Fuzzing FIDL servers with libFuzzer on Fuchsia |
| |
| Fuchsia includes experimental support for writing FIDL fuzzers in the style of FIDL unit tests. |
| |
| ### Quick-start guide |
| |
| An example fuzzer is defined in [`//examples/fuzzers/fidl`][example]. If you |
| are not familiar with fuzzers, see the [overview](overview.md). Fuzzing FIDL servers with libFuzzer |
| on Fuchsia requires GN targets that will generate a fuzz target and writing some code to provide an |
| instance of the server to be fuzzed. |
| |
| 1. Add `fuzzers = {protocol = "fully.qualified.fidl.ProtocolName"}` to your `fidl()` GN target. |
| 1. Depending on the language you are using: |
| * At the C++ level (easy mode): |
| Use the `FIDL_FUZZER_DEFINITION()` macro in `//sdk/lib/fidl/cpp/fuzzing/server_provider.h` to |
| define a server provider for your interface and server implementation class. This will |
| automatically define the C symbols described below. See |
| `//examples/fuzzers/fidl` for a reference example. |
| * At the C level (hard mode): |
| Implement a library that defines the following symbols: |
| * `zx_status_t fuzzer_init()`: Instantiate server implementation. |
| * `zx_status_t fuzzer_connect(zx_handle_t, async_dispatcher_t*)`: Bind server implementation |
| to the channel handle. Optionally, use the dispatcher if your server can be fuzzed on the |
| same thread as fuzzer clients (see [note on threading](#a-note-about-threading)). |
| * `zx_status_t fuzzer_disconnect(zx_handle_t, async_dispatcher_t*)`: Unbind server |
| implementation from the channel handle. |
| * `zx_status_t fuzzer_clean_up()`: Clean up server implementation. |
| If any of these returns a status other than `ZX_OK`, then the fuzzer will cleanup and halt. |
| 1. Define a `fidl_protocol_fuzzer()` GN target. Specify: |
| * `fidl = //path/to:fidl_gn_target` (the `fidl()` target mentioned above). |
| * `protocol = "fully.qualified.fidl.ProtocolName"`. |
| * `deps = [... :your_library ...]` (the one mentioned above, defining `fuzzer_...` symbols). |
| * Anything else needed for a [`fuzzer`](build-a-fuzzer.md#fuzzer) GN target that fuzzes your |
| server. |
| 1. Add `fidl_protocol_fuzzer()` target to `fuzzers = [ ... ]` in a new or existing |
| [`fuzzer_package`](build-a-fuzzer.md#fuzzer_package) GN target. |
| |
| ### Implementation details |
| |
| The bulk of a FIDL server implementation fuzz target is C++ code generated by `fidlgen` that expects |
| a handful of C symbols to provide an API to the FIDL server implementation. The generated code |
| contains a global `async::Loop`, bound to its initial thread, that is reused for the client side of |
| a FIDL connection on each run of the fuzz target. `LibFuzzer` repeatedly invokes the same fuzz |
| target with different inputs. The generated fuzz target code will: |
| |
| * Invoke `fuzzer_init()`, initializing the server to be fuzzed. |
| * Instantiates a pair of `zx::channel`s. |
| * Initializes an `fidl::InterfacePtr` of the appropriate type, bound to a channel end and the loop's |
| dispatcher. |
| * Invoke `fuzzer_connect(raw_server_channel_handle, loop->dispatcher())`, establishing a connection |
| with the server and allowing the server to opt in to using the same dispatcher as the client if |
| its API is compatible with such a scheme (see [note on threading](#a-note-about-threading)). |
| * Invoke a method through its `fidl::InterfacePtr`. |
| * Set its `async::Loop` to run-until-idle. |
| * Synchronize with the method's callback via a `zx::event`. |
| * Invoke `fuzzer_disconnect(raw_server_channel_handle, loop->dispatcher())`, allowing the server to |
| clean up its end of the connection. |
| * Invoke `fuzzer_clean_up()` to tear down the server instance. |
| |
| #### Allocation of fuzz target input to FIDL messages |
| |
| In broad strokes, the first two bytes are used to select a protocol and method pair from among those |
| defined in the FIDL source file. In the case of a FIDL file that contains many protocols, but only |
| one is enabled in the fuzzer, discovery of meaningful inputs relies on `LibFuzzer`'s coverage-guided |
| engine to deduce that the first bytes of a certain form cause the fuzzer to exercise almost no code. |
| |
| After a protocol and method pair are identified, the remaining bytes are carved up as follows: |
| |
| * Each type has a trait that defines the minimum number of bytes it requires. |
| * If insufficient bytes are input, the fuzz target exits immediately. |
| * Otherwise the "slack" bytes that exceed the minimum required for the method parameters are divided |
| evenly among parameters and an allocation trait for each type is invoked to construct an object of |
| the appropriate type using at most `MinForParam + SlackForParam` bytes. |
| |
| In terms of allocation trait details, collection and numeric types have relatively natural |
| interpretations based on the collection of bytes to be transformed into an object. Handles are |
| treated like numeric types, which can be expected to lead to errors when the server attempts to |
| exercise them. |
| |
| #### A note about threading |
| {#a-note-about-threading} |
| |
| It is **highly** desirable to keep the fuzz target single-threaded. That is, use |
| `ServerProviderDispatcherMode::kFromCaller` in C++ or use the `async_dispatcher_t*` passed to |
| `fuzzer_connect` in C. This is preferred because it increases the likelihood that bugs found by the |
| fuzzer will be consistently reproducible. |
| |
| ## Fuzzing the FIDL host tools with AFL |
| |
| Additionally, past experimental efforts have fuzzed the FIDL compiler using |
| [afl-fuzz](http://lcamtuf.coredump.cx/afl/). |
| |
| ### Build afl-fuzz |
| |
| Download and build it, then `export AFL_PATH` to be whatever path you downloaded and built it with. |
| |
| ### Patch the parser to not trap on invalid syntax |
| |
| afl-fuzz treats crashes as interesting but the parser currently calls `__builtin_trap()` |
| when it encounters invalid syntax. Remove that line in |
| [parser.h](/tools/fidl/fidlc/include/fidl/parser.h) - it's in the `Parser::Fail()` method. |
| |
| ### Build the `fidl` tool with afl-fuzz's instrumentation |
| |
| Clear any existing build and then build with the afl-fuzz compiler wrappers. |
| |
| ``` |
| cd $ZIRCON_DIR |
| rm -fr build-x86 |
| PATH=$PWD/prebuilt/downloads/clang+llvm-x86_64-linux/bin/:$PATH:$AFL_PATH make \ |
| build-x86/tools/fidl HOST_TOOLCHAIN_PREFIX=afl- |
| ``` |
| |
| Adjust if you're not building on x86 Linux, etc. |
| |
| ### Run the fuzzer |
| |
| The parser includes some examples to use as inputs. |
| As FIDL becomes adopted we can expand our inputs to include all of the different protocols |
| declared across our tree, but for now we use what's in `tools/fidl/examples`. |
| |
| ``` |
| $AFL_PATH/afl-fuzz -i tools/fidl/examples -o fidl-fuzz-out build-x86/tools/fidl dump '@@' |
| ``` |
| |
| ### Results |
| |
| Running against the source from early May 2017, there were no crashes or hangs after two days |
| of fuzzing on a fairly fast machine. It ran over 300 million executions. |
| |
| [example]: /examples/fuzzers/fidl |