Fuzzing FIDL Servers with LibFuzzer on Fuchsia

Quick-start guide

An example fuzzer is defined in //garnet/examples/fidl/echo_server_cpp/fuzzer. If you are not familiar with fuzzers, see LibFuzzer on Fuchsia. 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.
  2. 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 //garnet/examples/fidl/echo_server_cpp/fuzzer 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).
      • 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.
  3. 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() gn target that fuzzes your server (see LibFuzzer on Fuchsia for details).
  4. Add fidl_protocol_fuzzer() target to fuzzers = [ ... ] in a new or existing fuzzers_package() gn target (see LibFuzzer on Fuchsia for details).

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::channels.
  • 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).
  • 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

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.

Future work

The following work is planned for improving fuzzing FIDL server implementations with LibFuzzer on Fuchsia:

  1. Language bindings/libraries for C/C++, rust, and go.
  2. Improved distribution of fuzz input data over method input signature. For example, some fuzz inputs should lead to using legitimate handles rather than treating handles as numeric types.
  3. Fuzzing multiple method invocations in the same test run.
  4. Fuzzing multiple connections in the same test run.