| # Copyright 2018 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import("//build/cpp/fidl_cpp.gni") |
| import("//build/host.gni") |
| import("//build/package.gni") |
| import("//build/test.gni") |
| |
| # TODO(aarongreen): SEC-224. Add tests to catch fuzzer building/packaging |
| # regressions. |
| |
| # Defines a fuzzer binary. |
| # |
| # The fuzzer template is used to create components containing binaries |
| # which leverage LLVM's libFuzzer to perform fuzz testing. |
| # |
| # Parameters are precisely those of an `executable`, along with: |
| # |
| # cmx (optional) |
| # [file] If specified, a file containing a component manifest to start from |
| # when generating manifests for fuzzers. |
| # |
| # dictionary (optional) |
| # [file] If specified, a file containing quoted inputs, one per line, that |
| # the fuzzer will use to generate new mutations. |
| # |
| # options (optional) |
| # [list of strings] Each option is of the form "key=value" and indicates |
| # command line options that the fuzzer should be invoked with. Valid keys |
| # are libFuzzer options (https://llvm.org/docs/LibFuzzer.html#options). |
| # |
| template("fuzzer") { |
| fuzzer_name = target_name |
| |
| # Generate the component binary |
| executable(fuzzer_name) { |
| forward_variables_from(invoker, |
| "*", |
| [ |
| "cmx", |
| "dictionary", |
| "options", |
| "visibility", |
| ]) |
| |
| # Explicitly forward visibility for nested scopes. |
| forward_variables_from(invoker, [ "visibility" ]) |
| testonly = true |
| _target_type = "fuzzed_executable" |
| } |
| |
| # Generate the fuzzer component manifest |
| generated_file("$fuzzer_name.cmx") { |
| if (defined(invoker.cmx)) { |
| cmx = read_file(invoker.cmx, "json") |
| } else { |
| cmx = { |
| program = { |
| } |
| sandbox = { |
| } |
| } |
| } |
| contents = { |
| program = { |
| forward_variables_from(cmx.program, "*") |
| binary = "bin/${fuzzer_name}" |
| } |
| sandbox = { |
| services = [] |
| features = [] |
| forward_variables_from(cmx.sandbox, "*") |
| services += [ "fuchsia.process.Launcher" ] |
| features += [ |
| "deprecated-ambient-replace-as-executable", |
| "isolated-persistent-storage", |
| "isolated-temp", |
| ] |
| } |
| forward_variables_from(cmx, |
| [ |
| "dev", |
| "runner", |
| "facets", |
| ]) |
| } |
| outputs = [ |
| "$target_gen_dir/$target_name", |
| ] |
| output_conversion = "json" |
| } |
| |
| # Generate a unit test for the fuzzer. |
| test("${fuzzer_name}_test") { |
| deps = [] |
| forward_variables_from(invoker, |
| "*", |
| [ |
| "dictionary", |
| "options", |
| "target_name", |
| "visibility", |
| ]) |
| |
| # Explicitly forward visibility for nested scopes. |
| forward_variables_from(invoker, [ "visibility" ]) |
| deps += [ |
| "//src/lib/fuzzing/cpp:fuzzer_test", |
| "//third_party/googletest:gtest_main", |
| ] |
| } |
| |
| # Generate the fuzzer test component manifest |
| generated_file("${fuzzer_name}_test.cmx") { |
| contents = { |
| program = { |
| binary = "test/${fuzzer_name}_test" |
| } |
| sandbox = { |
| services = [ "fuchsia.process.Launcher" ] |
| features = [ |
| "deprecated-ambient-replace-as-executable", |
| "isolated-persistent-storage", |
| "isolated-temp", |
| ] |
| } |
| } |
| outputs = [ |
| "$target_gen_dir/$target_name", |
| ] |
| output_conversion = "json" |
| } |
| |
| # Generate data files needed at runtime |
| output_dictionary = "${target_gen_dir}/${fuzzer_name}/dictionary" |
| if (defined(invoker.dictionary)) { |
| copy("${fuzzer_name}_dictionary") { |
| sources = [ |
| invoker.dictionary, |
| ] |
| outputs = [ |
| output_dictionary, |
| ] |
| } |
| } else { |
| generated_file("${fuzzer_name}_dictionary") { |
| contents = [] |
| outputs = [ |
| output_dictionary, |
| ] |
| } |
| } |
| generated_file("${fuzzer_name}_options") { |
| contents = [] |
| if (defined(invoker.options)) { |
| contents = invoker.options |
| } |
| outputs = [ |
| "${target_gen_dir}/${fuzzer_name}/options", |
| ] |
| } |
| } |
| |
| set_defaults("fuzzer") { |
| configs = default_executable_configs + |
| [ "//build/fuzzing:fuzzing_build_mode_unsafe_for_production" ] |
| } |
| |
| # Defines a package of fuzzers |
| # |
| # The fuzzers_package template is used to bundle several fuzzers and their |
| # associated data into a single Fuchsia package. |
| # |
| # Parameters |
| # |
| # fuzzers (required) |
| # [list of labels] The fuzzer() labels to include in this package. |
| # |
| # sanitizers (optional) |
| # [list of variants] A set of sanitizer variants. The resulting package |
| # will contain binaries for each sanitizer/fuzzer combination. Defaults to |
| # [ "asan", "ubsan" ] |
| # |
| # fuzz_host (optional) |
| # [boolean] Indicates whether to also build fuzzer binaries on host. |
| # Defaults to false. |
| # |
| # generated_test_package (optional) |
| # [string] The name of a package of fuzzer tests to create. |
| # |
| # meta (optional) |
| # binaries (optional) |
| # components (optional) |
| # tests (optional) |
| # drivers (optional) |
| # loadable_modules (optional) |
| # resources (optional) |
| # extra (optional) |
| # Same meanings as in //build/package.gni |
| # |
| # deps (optional) |
| # public_deps (optional) |
| # data_deps (optional) |
| # testonly (optional) |
| # Usual GN meanings. |
| template("fuzzers_package") { |
| assert(defined(invoker.fuzzers), "fuzzers must be defined for $target_name}") |
| |
| if (defined(invoker.sanitizers)) { |
| sanitizers = invoker.sanitizers |
| } else { |
| sanitizers = [ |
| "asan", |
| "ubsan", |
| ] |
| } |
| |
| pkg = { |
| name = target_name |
| meta = [] |
| binaries = [] |
| resources = [] |
| deps = [] |
| fuzzers = [] |
| host = defined(invoker.fuzz_host) && invoker.fuzz_host |
| host_deps = [] |
| host_outputs = [] |
| tests = [] |
| test_meta = [] |
| test_deps = [] |
| } |
| |
| # Collect the selected fuzzers listed in this package based on the variants |
| # selected in args.gn and filtered by the package's list of supported |
| # sanitizers. |
| foreach(fuzzer, invoker.fuzzers) { |
| selected = false |
| fuzzer_name = get_label_info(fuzzer, "name") |
| fuzzer_path = get_label_info(fuzzer, "target_gen_dir") |
| fuzzer_label = get_label_info(fuzzer, "label_no_toolchain") |
| foreach(sanitizer, sanitizers) { |
| foreach(selector, select_variant_canonical) { |
| if (!selected && selector.variant == "${sanitizer}-fuzzer" && |
| ((defined(selector.target_type) && |
| selector.target_type == [ "fuzzed_executable" ]) || |
| (defined(selector.name) && selector.name == [ fuzzer ]) || |
| (defined(selector.output_name) && |
| selector.output_name == [ fuzzer ]))) { |
| selected = true |
| |
| # Package details. |
| pkg.meta += [ |
| { |
| path = "${fuzzer_path}/${fuzzer_name}.cmx" |
| dest = "${fuzzer_name}.cmx" |
| }, |
| ] |
| pkg.binaries += [ |
| { |
| name = "${fuzzer_name}" |
| }, |
| ] |
| pkg.resources += [ |
| { |
| path = "${fuzzer_path}/${fuzzer_name}/dictionary" |
| dest = "${fuzzer_name}/dictionary" |
| }, |
| { |
| path = "${fuzzer_path}/${fuzzer_name}/options" |
| dest = "${fuzzer_name}/options" |
| }, |
| ] |
| pkg.deps += [ |
| "${fuzzer_label}", |
| "${fuzzer_label}.cmx", |
| "${fuzzer_label}_dictionary", |
| "${fuzzer_label}_options", |
| ] |
| pkg.fuzzers += [ fuzzer_name ] |
| |
| # Host fuzzers |
| pkg.host_deps += [ fuzzer_label ] |
| pkg.host_outputs += [ fuzzer_name ] |
| } |
| } |
| } |
| |
| # Fuzzer tests |
| pkg.tests += [ |
| { |
| name = "${fuzzer_name}_test" |
| }, |
| ] |
| pkg.test_meta += [ |
| { |
| path = "${fuzzer_path}/${fuzzer_name}_test.cmx" |
| dest = "${fuzzer_name}_test.cmx" |
| }, |
| ] |
| pkg.test_deps += [ |
| "${fuzzer_label}_test", |
| "${fuzzer_label}_test.cmx", |
| ] |
| not_needed([ "selected" ]) |
| } |
| |
| if (pkg.deps == []) { |
| # Dummy package for non-fuzzed builds |
| group(target_name) { |
| } |
| not_needed(invoker, "*") |
| } else { |
| if (pkg.host) { |
| # Treat host fuzzers as tools. If we get to the point of having name |
| # collisions, we'll need to extend `install_host_tools` to allow copying to |
| # specific subdirectories of `host_tools_dir` |
| install_host_tools("host_${target_name}") { |
| testonly = true |
| deps = pkg.host_deps |
| outputs = pkg.host_outputs |
| } |
| pkg.deps += [ ":host_${pkg.name}" ] |
| } |
| |
| # Assemble the Fuchsia package |
| package(target_name) { |
| meta = [] |
| binaries = [] |
| resources = [] |
| deps = [] |
| forward_variables_from(invoker, |
| "*", |
| [ |
| "fuzzers", |
| "generated_test_package", |
| "metadata", |
| "sanitizers", |
| "visibility", |
| ]) |
| |
| # Explicitly forward visibility for nested scopes. |
| forward_variables_from(invoker, [ "visibility" ]) |
| testonly = true |
| meta += pkg.meta |
| binaries = pkg.binaries |
| resources = pkg.resources |
| deps = pkg.deps |
| |
| # This metadata will be used to generate out/default/fuzzers.json |
| metadata = { |
| if (defined(invoker.metadata)) { |
| forward_variables_from(invoker.metadata, "*", [ "fuzz_spec" ]) |
| } |
| fuzz_spec = [ |
| { |
| fuzzers_package = pkg.name |
| fuzzers = pkg.fuzzers |
| fuzz_host = pkg.host |
| }, |
| ] |
| } |
| } |
| } |
| |
| # TODO(aarongreen): Soft transition from the optional $generated_test_package |
| # to the required $test_package_name. Once template consumers are updated; |
| # drop support for $generated_test_package. |
| if (defined(invoker.test_package_name)) { |
| test_package_name = invoker.test_package_name |
| } else if (defined(invoker.generated_test_package)) { |
| test_package_name = invoker.generated_test_package |
| } else { |
| test_package_name = "${pkg.name}_tests" |
| } |
| |
| # Include host unit tests as appropriate |
| if (pkg.host) { |
| group("host_$test_package_name") { |
| visibility = [ ":*" ] |
| testonly = true |
| deps = [] |
| foreach(test_dep, pkg.test_deps) { |
| deps += [ "$test_dep($host_toolchain)" ] |
| } |
| } |
| pkg.test_deps += [ ":host_$test_package_name" ] |
| } |
| |
| # Assemble the Fuchsia test package. This uses `package` instead of |
| # `test_package`, as the latter only provides an inconvenient constraint on |
| # where the cmx file must come from. |
| package(test_package_name) { |
| forward_variables_from(invoker, [ "visibility" ]) |
| testonly = true |
| meta = pkg.test_meta |
| tests = pkg.tests |
| deps = pkg.test_deps |
| } |
| } |
| |
| # Defines service provider for a generated FIDL fuzzer |
| # |
| # The fidl() template supports a list of `fuzzers` that contain a `protocol` |
| # string and optional list of `methods`. Each of the fidl()'s `fuzzers` |
| # generates a library target of the form: |
| # |
| # [fidl() target name]_libfuzzer_[protocol]_[method1 name]_[method2 name]... |
| # |
| # The target contains the core fuzzer logic, but relies on symbols that must be |
| # defined by the FIDL service implementer for providing an instance of the |
| # service to fuzz. |
| # |
| # This template is a helper for tying together the above-mentioned target and |
| # the sources and/or deps necessary to provide the above-mentioned symbols. |
| # |
| # NOTE: The `protocol` and `methods` passed to this template must _exactly_ |
| # match one of the `fuzzers` defined on the corresponding fidl() rule. |
| # |
| # Parameters |
| # |
| # fidl (required) |
| # [label] The `fidl()` label that includes the protocol to be fuzzed. |
| # |
| # protocol (required) |
| # [fully-qualified FIDL protocol name] The fully-qualified name of the FIDL |
| # protocol to be fuzzed. |
| # |
| # methods (optional) |
| # [list of strings] The names of the methods to be fuzzed, as they appear in |
| # the FIDL file. These are translated into defines that enable fuzzing code |
| # for the appropriate methods. Defaults to special define value for fuzzing |
| # all methods of the specified protocol. |
| # |
| # Other parameters are precisely those of an `executable`, with their usual GN |
| # meanings; these parameters are forwarded to the generated fuzzer() template. |
| template("fidl_protocol_fuzzer") { |
| assert( |
| defined(invoker.protocol), |
| "FIDL fuzzer service provider must set protocol: the fully-qualified name of the protocol to be fuzzed.") |
| assert( |
| defined(invoker.fidl), |
| "FIDL fuzzer service provider must set fidl: the fidl() target defining the corresponding fuzzer.") |
| |
| protocol_suffix = string_replace(invoker.protocol, ".", "_") |
| if (defined(invoker.methods)) { |
| foreach(method, invoker.methods) { |
| protocol_suffix = "${protocol_suffix}_${method}" |
| } |
| } |
| |
| if (defined(invoker.deps)) { |
| fuzzer_deps = invoker.deps |
| } else { |
| fuzzer_deps = [] |
| } |
| fuzzer_deps += [ |
| "//sdk/lib/fidl/cpp/fuzzing", |
| "${invoker.fidl}_libfuzzer_${protocol_suffix}", |
| ] |
| |
| fuzzer(target_name) { |
| forward_variables_from(invoker, |
| [ |
| "aliased_deps", |
| "all_dependent_configs", |
| "asmflags", |
| "cflags_c", |
| "cflags_cc", |
| "cflags_objc", |
| "cflags_objcc", |
| "cflags", |
| "check_includes", |
| "cmx", |
| "configs", |
| "crate_name", |
| "crate_root", |
| "data_deps", |
| "data", |
| "dictionary", |
| "edition", |
| "friend", |
| "include_dirs", |
| "inputs", |
| "inputs", |
| "ldflags", |
| "lib_dirs", |
| "libs", |
| "metadata", |
| "options", |
| "output_extension", |
| "output_name", |
| "precompiled_header", |
| "precompiled_source", |
| "public_configs", |
| "public_deps", |
| "public", |
| "rustenv", |
| "rustflags", |
| "sources", |
| "testonly", |
| "visibility", |
| ]) |
| deps = fuzzer_deps |
| } |
| } |
| |
| # TODO(aarongreen): Complete soft transition and remove. |
| # These templates are the old names for `fuzzer` and `fuzzers_package`. They |
| # are included to allow third_party projects to transition softly. |
| template("fuzz_target") { |
| source_set(target_name) { |
| } |
| not_needed(invoker, "*") |
| } |
| |
| set_defaults("fuzz_target") { |
| configs = [] |
| } |
| |
| template("fuzz_package") { |
| group(target_name) { |
| } |
| not_needed(invoker, "*") |
| } |