blob: 513edb3accc33e9dee376ecda58cf1250e653eb6 [file] [log] [blame]
# 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, "*")
}