| # 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. |
| # |
| # corpus (optional) |
| # [path] If specified, a directory containing elements of a seed corpus. These should be a |
| # combination of hand-crafted fuzzer inputs that increase coverage, and artifacts from |
| # previous fuzzing runs, e.g. fuzzer inputs that triggered crashes. These will be used to seed |
| # new fuzzing runs, and as regression unit tests. |
| # |
| # 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") { |
| base_output_name = target_name |
| if (defined(invoker.output_name)) { |
| base_output_name = invoker.output_name |
| } |
| |
| # Generate the component binary |
| executable(target_name) { |
| forward_variables_from(invoker, |
| "*", |
| [ |
| "cmx", |
| "corpus", |
| "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 |
| cmx_target = "${target_name}_cmx" |
| cmx_output = "$target_gen_dir/${base_output_name}.cmx" |
| generated_file(cmx_target) { |
| if (defined(invoker.cmx)) { |
| cmx = read_file(invoker.cmx, "json") |
| } else { |
| cmx = { |
| program = { |
| } |
| sandbox = { |
| } |
| } |
| } |
| contents = { |
| program = { |
| forward_variables_from(cmx.program, "*") |
| binary = "bin/$base_output_name" |
| } |
| sandbox = { |
| services = [] |
| features = [] |
| forward_variables_from(cmx.sandbox, "*") |
| services += [ "fuchsia.process.Launcher" ] |
| features += [ |
| "isolated-persistent-storage", |
| "isolated-temp", |
| ] |
| } |
| forward_variables_from(cmx, |
| [ |
| "dev", |
| "runner", |
| "facets", |
| ]) |
| } |
| outputs = [ cmx_output ] |
| output_conversion = "json" |
| } |
| |
| # Generate seed corpus. See https://llvm.org/docs/LibFuzzer.html#corpus |
| corpus_target = "${target_name}_corpus" |
| corpus_output = "${target_gen_dir}/${base_output_name}/corpus.manifest" |
| corpus_gensrc = "${target_gen_dir}/${base_output_name}/corpus.cc" |
| action(corpus_target) { |
| script = "//build/fuzzing/add_corpus.py" |
| depfile = "$corpus_output.d" |
| outputs = [ |
| corpus_output, |
| corpus_gensrc, |
| ] |
| args = [ |
| "--fuzzer", |
| base_output_name, |
| "--depfile", |
| rebase_path(depfile, root_build_dir), |
| "--manifest", |
| rebase_path(corpus_output, root_build_dir), |
| "--unittest", |
| rebase_path(corpus_gensrc, root_build_dir), |
| ] |
| if (defined(invoker.corpus)) { |
| args += [ |
| "--corpus", |
| rebase_path(invoker.corpus, root_build_dir), |
| ] |
| } |
| } |
| |
| # Generate dictionary. See https://llvm.org/docs/LibFuzzer.html#dictionaries |
| dictionary_target = "${target_name}_dictionary" |
| dictionary_output = "${target_gen_dir}/${base_output_name}/dictionary" |
| if (defined(invoker.dictionary)) { |
| copy(dictionary_target) { |
| sources = [ invoker.dictionary ] |
| outputs = [ dictionary_output ] |
| } |
| } else { |
| generated_file(dictionary_target) { |
| contents = [ "# empty dictionary" ] |
| outputs = [ dictionary_output ] |
| } |
| } |
| |
| # Generate options. See https://llvm.org/docs/LibFuzzer.html#options |
| options_target = "${target_name}_options" |
| options_output = "${target_gen_dir}/${base_output_name}/options" |
| generated_file(options_target) { |
| contents = [] |
| if (defined(invoker.options)) { |
| contents = invoker.options |
| } |
| outputs = [ options_output ] |
| } |
| |
| # Generate a unit test for the fuzzer. |
| test_target = "${target_name}_test" |
| test_output = "${base_output_name}_test" |
| test(test_target) { |
| output_name = test_output |
| sources = [] |
| deps = [] |
| forward_variables_from(invoker, |
| "*", |
| [ |
| "cmx", |
| "corpus", |
| "dictionary", |
| "options", |
| "output_name", |
| "target_name", |
| "visibility", |
| ]) |
| sources += [ corpus_gensrc ] |
| |
| # Explicitly forward visibility for nested scopes. |
| forward_variables_from(invoker, [ "visibility" ]) |
| deps += [ |
| ":$corpus_target", |
| "//src/lib/fuzzing/cpp:fuzzer_test", |
| "//src/lib/fxl/test:gtest_main", |
| ] |
| } |
| |
| # Generate the fuzzer test component manifest |
| test_cmx_target = "${test_target}_cmx" |
| test_cmx_output = "$target_gen_dir/${test_output}.cmx" |
| generated_file(test_cmx_target) { |
| contents = { |
| program = { |
| binary = "test/$test_output" |
| } |
| } |
| outputs = [ test_cmx_output ] |
| output_conversion = "json" |
| deps = [] |
| } |
| } |
| |
| # See https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode |
| set_defaults("fuzzer") { |
| configs = default_executable_configs + |
| [ "//build/fuzzing:fuzzing_build_mode_unsafe_for_production" ] |
| } |
| |
| # TODO(48118): Add LSan |
| _cpp_sanitizers = [ |
| "asan", |
| "ubsan", |
| ] |
| |
| # Currently, Go doesn't have any *San specific callbacks beyond those in the dynamic linker. Still, |
| # all of the C/C++ sanitizers should link and run. |
| _go_sanitizers = _cpp_sanitizers |
| |
| _rust_sanitizers = [ "asan" ] |
| |
| # Defines a package of fuzzers |
| # |
| # The `fuzzers_package` template is used to bundle several fuzzers and their associated data into a |
| # single Fuchsia package. See //examples/fuzzer/BUILD.gn for examples. |
| # |
| # Parameters |
| # 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) |
| # cpp_fuzzers (optional) |
| # go_fuzzers (optional) |
| # rust_fuzzers (optional) |
| # [list] A list of fuzzers for targets in the indicated language. Each list is asscoiated with |
| # the corresponding list of sanitizers, e.g. each label in `cpp_fuzzers` is built as a fuzzer |
| # when the toolchain variant selected for that label matches an element of `_cpp_sanitizers`. |
| # `fuzzers` (with no language prefix) imples C++, and is folded into `cpp_fuzzers`. |
| # |
| # Each element of `fuzzers` is either a label or a scope. Label elements act as "shortcuts" |
| # that use default values for each field in a scope element. The scope elements can include |
| # the following fields: |
| # |
| # label (required) |
| # [label] The fuzzer() target to include. |
| # |
| # name (optional) |
| # output_name (optional) |
| # [string] Same as for fuzzer(), and should match the value(s) for the associated `label`. |
| # For label elements, these are both set to simple name of the label. |
| # |
| # sanitizers (optional) |
| # [list of strings] Overrides the default, per-language sanitizers. This should be omitted |
| # unless necessary. |
| # |
| # fuzz_host (optional) |
| # [boolean] Overrides the value of `fuzz_host` in the outer scope. |
| # |
| # host_only (optional) |
| # [boolean] Overrides the value of `host_only` in the outer scope. |
| # |
| # fuzzers_manifest (optional) |
| # [scope] Specifies how to read fuzzer names from a manifest file. 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") { |
| # 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 = [] |
| |
| # Step 1: Gather up lists of fuzzers by language, and associated them with that language's |
| # sanitizers. |
| by_language = [] |
| invoker_fuzz_host = defined(invoker.fuzz_host) && invoker.fuzz_host |
| invoker_host_only = defined(invoker.host_only) && invoker.host_only |
| if (defined(invoker.fuzzers)) { |
| by_language += [ |
| { |
| fuzzers = invoker.fuzzers |
| fuzz_host = invoker_fuzz_host |
| host_only = invoker_host_only |
| sanitizers = _cpp_sanitizers |
| }, |
| ] |
| } |
| if (defined(invoker.cpp_fuzzers)) { |
| by_language += [ |
| { |
| fuzzers = invoker.cpp_fuzzers |
| fuzz_host = invoker_fuzz_host |
| host_only = invoker_host_only |
| sanitizers = _cpp_sanitizers |
| }, |
| ] |
| } |
| if (defined(invoker.go_fuzzers)) { |
| not_needed([ |
| "invoker_fuzz_host", |
| "invoker_host_only", |
| ]) |
| by_language += [ |
| { |
| fuzzers = invoker.go_fuzzers |
| |
| # TODO(44551): go host fuzzers fail to link on mac-x64 |
| fuzz_host = false |
| host_only = false |
| sanitizers = _go_sanitizers |
| }, |
| ] |
| } |
| if (defined(invoker.rust_fuzzers)) { |
| not_needed([ |
| "invoker_fuzz_host", |
| "invoker_host_only", |
| ]) |
| by_language += [ |
| { |
| fuzzers = invoker.rust_fuzzers |
| |
| # TODO(48624): rustc segfaults when building hostlibs. |
| fuzz_host = false |
| host_only = false |
| sanitizers = _rust_sanitizers |
| }, |
| ] |
| } |
| assert(by_language != [], "no fuzzers specified") |
| |
| # Step 2: "Normalize" the fuzzers by making each one into a scope with the same fields. |
| normalized = [] |
| foreach(language, by_language) { |
| foreach(fuzzer, language.fuzzers) { |
| if (fuzzer == "$fuzzer") { |
| fuzzer = { |
| label = fuzzer |
| } |
| } else { |
| assert(defined(fuzzer.label), "missing fuzzer label") |
| } |
| normalized += [ |
| { |
| # Convert labels to scopes, if needed |
| label = get_label_info(fuzzer.label, "label_no_toolchain") |
| |
| # Get the name early in order to set the default `output_name`. |
| if (defined(fuzzer.name)) { |
| name = fuzzer.name |
| } else { |
| name = get_label_info(label, "name") |
| } |
| |
| # Set defaults |
| output_name = name |
| forward_variables_from(language, "*", [ "fuzzers" ]) |
| |
| # Allow overrides |
| forward_variables_from(fuzzer, |
| "*", |
| [ |
| "label", |
| "name", |
| ]) |
| }, |
| ] |
| } |
| } |
| |
| # Step 3: Assign each fuzzer to one or more of the following by matching the fuzzer scope against |
| # the currently selected toolchain variants: |
| # - A fuzzers package, either as binaries or unit tests |
| # - A set of host fuzzer tools |
| # - A set of host unit tests for the host fuzzer tools |
| fuzzed = [] |
| tested = [] |
| host_fuzzed = [] |
| host_tested = [] |
| foreach(fuzzer, normalized) { |
| fuzz = false |
| |
| # 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.sanitizers) { |
| if (!fuzz && selector.variant == "${sanitizer}-fuzzer") { |
| fuzz = |
| (defined(selector.target_type) && |
| selector.target_type == [ "fuzzed_executable" ]) || |
| (defined(selector.output_name) && |
| selector.output_name == [ fuzzer.output_name ]) || |
| (defined(selector.label) && selector.label == [ fuzzer.label ]) || |
| (defined(selector.name) && selector.name == [ fuzzer.name ]) |
| } |
| } |
| } |
| |
| # Add the fuzzers and tests from this profile to the aggregated lists. |
| if (!fuzzer.host_only) { |
| if (fuzz) { |
| fuzzed += [ fuzzer ] |
| } else { |
| tested += [ fuzzer ] |
| } |
| } |
| if (fuzzer.fuzz_host || fuzzer.host_only) { |
| if (fuzz) { |
| host_fuzzed += [ fuzzer ] |
| } else { |
| host_tested += [ fuzzer ] |
| } |
| } |
| } |
| |
| # 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") |
| } |
| |
| # Step 4: Assemble a Fuchsia package of fuzzers and/or unit tests. Many of the package parameter |
| # values need to match the '*_target' and '*_output' values in the `fuzzer` template above. |
| # Ideally this would use `get_target_outputs` to only need the former, but fuzzers and packages |
| # are not required to be defined in the same GN file. |
| 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. |
| output_names = [] |
| foreach(fuzzer, fuzzed) { |
| fuzzer_path = get_label_info(fuzzer.label, "target_gen_dir") |
| meta += [ |
| { |
| path = "${fuzzer_path}/${fuzzer.output_name}.cmx" |
| dest = "${fuzzer.output_name}.cmx" |
| }, |
| ] |
| binaries += [ |
| { |
| name = "${fuzzer.output_name}" |
| }, |
| ] |
| extra += [ "${fuzzer_path}/${fuzzer.output_name}/corpus.manifest" ] |
| resources += [ |
| { |
| path = "${fuzzer_path}/${fuzzer.output_name}/dictionary" |
| dest = "${fuzzer.output_name}/dictionary" |
| }, |
| { |
| path = "${fuzzer_path}/${fuzzer.output_name}/options" |
| dest = "${fuzzer.output_name}/options" |
| }, |
| ] |
| deps += [ |
| "${fuzzer.label}", |
| "${fuzzer.label}_cmx", |
| "${fuzzer.label}_corpus", |
| "${fuzzer.label}_dictionary", |
| "${fuzzer.label}_options", |
| ] |
| output_names += [ fuzzer.output_name ] |
| } |
| foreach(fuzzer, prebuilt_fuzzed) { |
| output_names += [ get_label_info(fuzzer, "name") ] |
| } |
| |
| # Add fuzzer tests for those not selected. |
| foreach(fuzzer, tested) { |
| fuzzer_path = get_label_info(fuzzer.label, "target_gen_dir") |
| meta += [ |
| { |
| path = "${fuzzer_path}/${fuzzer.output_name}_test.cmx" |
| dest = "${fuzzer.output_name}_test.cmx" |
| }, |
| ] |
| tests += [ |
| { |
| name = "${fuzzer.output_name}_test" |
| }, |
| ] |
| extra += [ "${fuzzer_path}/${fuzzer.output_name}/corpus.manifest" ] |
| deps += [ |
| "${fuzzer.label}_corpus", |
| "${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 = output_names |
| }, |
| ] |
| } |
| } |
| group_deps += [ ":$package_target_name" ] |
| } |
| |
| # Step 5: 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 += [ |
| "${fuzzer.label}", |
| "${fuzzer.label}_corpus", |
| ] |
| outputs += [ fuzzer.output_name ] |
| } |
| } |
| group_deps += [ ":$host_fuzzers_target_name" ] |
| } |
| |
| # Step 6: For host fuzzer tests, ensure the deps build with the correct toolchain. |
| if (host_tested != []) { |
| host_tests_target_name = target_name + "_host_tests" |
| group(host_tests_target_name) { |
| testonly = true |
| visibility = [ ":*" ] |
| deps = [] |
| foreach(fuzzer, host_tested) { |
| deps += [ "${fuzzer.label}_test($host_toolchain)" ] |
| } |
| } |
| group_deps += [ ":$host_tests_target_name" ] |
| } |
| |
| # Step 7: 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 |
| } |
| } |