| # 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 |
| # fuzzer_profiles (optional) |
| # [list of scopes] Each fuzzer profile describes how a set of fuzzers can be built. This |
| # parameter is required if the `fuzzers` parameter is not provided. The scopes include the |
| # following fields: |
| # |
| # fuzzers (required) |
| # [list of labels] The fuzzer() labels to include. |
| # |
| # sanitizers (required) |
| # [list of variants] A set of sanitizer variants. The resulting package will contain a |
| # fuzzer binary when this list contains the toolchain variant selected for the fuzzer label, |
| # and a fuzzer unit test otherwise. |
| # |
| # fuzz_host (optional) |
| # [boolean] Indicates whether to also build fuzzer binaries on host. Defaults to false. |
| # |
| # host_only (optional) |
| # [boolean] Indicates whether to skip the Fuchsia fuzzers package. Implies `fuzz_host`. |
| # Defaults to false. |
| # |
| # fuzzers (optional) |
| # fuzz_host (optional) |
| # host_only (optional) |
| # Same meaning as in `fuzzer_profiles`. When provided, these create a "default" fuzzer profile |
| # with an implied sanitizer list of [ "asan", "ubsan" ]. `fuzzers` is required if either |
| # `fuzz_host` or `host_only` is present. |
| # |
| # fuzzers_manifest (optional) |
| # [scope] Specifies how to read fuzzer names from a manifest file. The fuzzers are added to the |
| # "default" fuzzer profile. This parameter is useful for including fuzzers from another build, |
| # e.g. pre-unification Zircon fuzzers. The scope has two fields: |
| # |
| # target (required) |
| # [label] The GN target that produces the manifest. |
| # |
| # output (required) |
| # [path] The path to the generated manifest file. |
| # |
| # 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) |
| # Usual GN meanings. |
| template("fuzzers_package") { |
| assert(defined(invoker.fuzzers) || defined(invoker.fuzzer_profiles), |
| "`fuzzers` or `fuzzer_profiles` must be defined for $target_name}") |
| |
| # This target will create a group with up to three parts: a package of fuzzers and/or fuzzer |
| # tests, a set of host fuzzers treated as host tools, and a group of host fuzzer tests. Which |
| # fuzzers are included in each part depends of the associated fuzzer profile and current |
| # toolchain variant. |
| group_target_name = target_name |
| group_deps = [] |
| fuzzed = [] |
| tested = [] |
| host_fuzzed = [] |
| host_tested = [] |
| |
| # Add the default C/C++ fuzzer profile, if needed. |
| fuzzer_profiles = [] |
| if (defined(invoker.fuzzers)) { |
| fuzzer_profiles += [ |
| { |
| fuzzers = invoker.fuzzers |
| sanitizers = [ |
| "asan", |
| "ubsan", |
| ] |
| fuzz_host = defined(invoker.fuzz_host) && invoker.fuzz_host |
| host_only = defined(invoker.host_only) && invoker.host_only |
| }, |
| ] |
| } else { |
| assert(!defined(invoker.fuzz_host), |
| "`fuzz_host` without `fuzzers` has no effect") |
| assert(!defined(invoker.host_only), |
| "`host_only` without `fuzzers` has no effect") |
| } |
| if (defined(invoker.fuzzer_profiles)) { |
| fuzzer_profiles += invoker.fuzzer_profiles |
| } |
| |
| # Distribute fuzzers among the three parts depending based on the fuzzer profile and the variants |
| # selected in args.gn. |
| foreach(fuzzer_profile, fuzzer_profiles) { |
| assert(defined(fuzzer_profile.fuzzers), |
| "missing `fuzzers` in fuzzer profile in $target_name") |
| assert(defined(fuzzer_profile.sanitizers), |
| "missing `sanitizers` in fuzzer profile in $target_name") |
| fuzzed_by_profile = [] |
| tested_by_profile = [] |
| |
| # Compare each selected variant with a profile's supported sanitizers. Unselected fuzzers are |
| # built as uninstrumented unit tests instead; see `test("${fuzzer_name}_test")` above. |
| foreach(selector, select_variant_canonical) { |
| foreach(sanitizer, fuzzer_profile.sanitizers) { |
| if (selector.variant == "${sanitizer}-fuzzer") { |
| # Check if the variant is applied to all fuzzers. |
| if (defined(selector.target_type) && |
| selector.target_type == [ "fuzzed_executable" ]) { |
| fuzzed_by_profile += fuzzer_profile.fuzzers |
| } else { |
| # Check if the variant is applied to specific binary names. |
| foreach(fuzzer, fuzzer_profile.fuzzers) { |
| if ((defined(selector.name) && selector.name == [ fuzzer ]) || |
| (defined(selector.output_name) && |
| selector.output_name == [ fuzzer ])) { |
| fuzzed_by_profile += [ fuzzer ] |
| } else { |
| tested_by_profile += [ fuzzer ] |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| # If no sanitizer matched a select variant, all fuzzers become tests. |
| if (fuzzed_by_profile == []) { |
| tested_by_profile += fuzzer_profile.fuzzers |
| } |
| |
| # Add the fuzzers and tests from this profile to the aggregated lists. |
| host_only = defined(fuzzer_profile.host_only) && fuzzer_profile.host_only |
| if (!host_only) { |
| fuzzed += fuzzed_by_profile |
| tested += tested_by_profile |
| } |
| if ((defined(fuzzer_profile.fuzz_host) && fuzzer_profile.fuzz_host) || |
| host_only) { |
| host_fuzzed += fuzzed_by_profile |
| host_tested += tested_by_profile |
| } |
| } |
| |
| # Fuzzers imported via manifest are always selected. |
| forward_variables_from(invoker, [ "fuzzers_manifest" ]) |
| prebuilt_fuzzed = [] |
| if (defined(fuzzers_manifest)) { |
| prebuilt_fuzzed += read_file(fuzzers_manifest.output, "json") |
| } |
| |
| # Assemble a Fuchsia package of fuzzers and/or unit tests. |
| if (fuzzed != [] || prebuilt_fuzzed != [] || tested != []) { |
| package_target_name = target_name + "_pkg" |
| package(package_target_name) { |
| package_name = group_target_name |
| testonly = true |
| visibility = [ ":*" ] |
| |
| # Allow callers to set most package parameters. |
| meta = [] |
| binaries = [] |
| tests = [] |
| resources = [] |
| extra = [] |
| deps = [] |
| forward_variables_from(invoker, |
| "*", |
| [ |
| "fuzzers", |
| "fuzzers_manifest", |
| "fuzzer_profiles", |
| "metadata", |
| "sanitizers", |
| "testonly", |
| "visibility", |
| ]) |
| |
| # Add selected fuzzers |
| fuzzer_names = [] |
| foreach(fuzzer, fuzzed) { |
| 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") |
| |
| meta += [ |
| { |
| path = "${fuzzer_path}/${fuzzer_name}.cmx" |
| dest = "${fuzzer_name}.cmx" |
| }, |
| ] |
| binaries += [ |
| { |
| name = "${fuzzer_name}" |
| }, |
| ] |
| resources += [ |
| { |
| path = "${fuzzer_path}/${fuzzer_name}/dictionary" |
| dest = "${fuzzer_name}/dictionary" |
| }, |
| { |
| path = "${fuzzer_path}/${fuzzer_name}/options" |
| dest = "${fuzzer_name}/options" |
| }, |
| ] |
| deps += [ |
| "${fuzzer_label}", |
| "${fuzzer_label}.cmx", |
| "${fuzzer_label}_dictionary", |
| "${fuzzer_label}_options", |
| ] |
| fuzzer_names += [ fuzzer_name ] |
| } |
| foreach(fuzzer, prebuilt_fuzzed) { |
| fuzzer_names += [ get_label_info(fuzzer, "name") ] |
| } |
| |
| # Add fuzzer tests for those not selected. |
| foreach(fuzzer, tested) { |
| 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") |
| |
| meta += [ |
| { |
| path = "${fuzzer_path}/${fuzzer_name}_test.cmx" |
| dest = "${fuzzer_name}_test.cmx" |
| }, |
| ] |
| tests += [ |
| { |
| name = "${fuzzer_name}_test" |
| }, |
| ] |
| deps += [ |
| "${fuzzer_label}_test", |
| "${fuzzer_label}_test.cmx", |
| ] |
| } |
| |
| if (defined(fuzzers_manifest)) { |
| deps += [ fuzzers_manifest.target ] |
| extra += get_target_outputs(fuzzers_manifest.target) |
| } |
| |
| # 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 = package_name |
| fuzzers = fuzzer_names |
| fuzz_host = host_fuzzed != [] |
| }, |
| ] |
| } |
| } |
| group_deps += [ ":$package_target_name" ] |
| } |
| |
| # 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`. |
| if (host_fuzzed != []) { |
| host_fuzzers_target_name = target_name + "_host" |
| install_host_tools(host_fuzzers_target_name) { |
| testonly = true |
| visibility = [ ":*" ] |
| deps = [] |
| outputs = [] |
| foreach(fuzzer, host_fuzzed) { |
| deps += [ get_label_info(fuzzer, "label_no_toolchain") ] |
| outputs += [ get_label_info(fuzzer, "name") ] |
| } |
| } |
| group_deps += [ ":$host_fuzzers_target_name" ] |
| } |
| |
| # For host fuzzer tests, just ensure the deps build. |
| if (host_tested != []) { |
| host_tests_target_name = target_name + "_host_tests" |
| group(host_tests_target_name) { |
| testonly = true |
| visibility = [ ":*" ] |
| deps = [] |
| foreach(fuzzer, host_tested) { |
| fuzzer_label = get_label_info(fuzzer, "label_no_toolchain") |
| deps += [ "${fuzzer_label}_test($host_toolchain)" ] |
| } |
| } |
| group_deps += [ ":$host_tests_target_name" ] |
| } |
| |
| # Include everything in the group |
| group(group_target_name) { |
| forward_variables_from(invoker, [ "visibility" ]) |
| testonly = true |
| deps = group_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 += [ |
| "${invoker.fidl}_libfuzzer_${protocol_suffix}", |
| "//sdk/lib/fidl/cpp/fuzzing", |
| ] |
| |
| 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(44458): 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, "*") |
| } |