blob: 46e8cf31cf2c5256895f4e0e6090af66104ac370 [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.
#
# 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
}
}