[build][fuzzing] Allow fuzzers to customize sandbox

fxrev.dev/428892 removed the unused `cmx` property from the `fuzzer()`
template, but in some cases a fuzzer still needs to request specific
features and services in its sandbox. This CL addresses that by adding
back narrowly-scoped `features` and `services` properties to the
template.

Additionally, an `example-fuzzers/extended_sandbox` fuzzer is added as a
basic test for this feature.

Test: fx test extended_sandbox_test
Change-Id: Ia594e26390cc6b0abf96813c670dbf20dd67f268
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/450935
Reviewed-by: Aaron Green <aarongreen@google.com>
Testability-Review: Aaron Green <aarongreen@google.com>
Commit-Queue: Cameron Finucane <eep@google.com>
diff --git a/build/fuzzing/fuzzer.gni b/build/fuzzing/fuzzer.gni
index c5a4178d..ff6bff7 100644
--- a/build/fuzzing/fuzzer.gni
+++ b/build/fuzzing/fuzzer.gni
@@ -44,6 +44,9 @@
 #     [file] If specified, a file containing quoted inputs, one per line, that the fuzzer will use
 #       to generate new mutations.
 #
+#   features, services (optional)
+#     [list of strings] Extra (v1) sandbox features/services for the fuzzer and test components.
+#
 #   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
@@ -82,8 +85,10 @@
   exclude_from_mass_forwards = [
     "corpus",
     "dictionary",
+    "features",
     "options",
     "output_name",
+    "services",
     "target_name",
     "visibility",
   ]
@@ -131,7 +136,9 @@
   fuzzer_component(component_target) {
     forward_variables_from(invoker,
                            [
+                             "features",
                              "options",
+                             "services",
                              "visibility",
                            ])
     fuzzer = fuzzer_name
@@ -155,7 +162,9 @@
   fuzzer_test_component(test_component_target) {
     forward_variables_from(invoker,
                            [
+                             "features",
                              "corpus",
+                             "services",
                              "visibility",
                            ])
     fuzzer = fuzzer_name
diff --git a/build/fuzzing/fuzzer_component.gni b/build/fuzzing/fuzzer_component.gni
index aa47c12..4da7cae 100644
--- a/build/fuzzing/fuzzer_component.gni
+++ b/build/fuzzing/fuzzer_component.gni
@@ -28,6 +28,9 @@
 #       includes the fuzzer binary. It may also include the fuzzer corpus and dictionary, when
 #       present.
 #
+#   features, services (optional)
+#     [list of strings] Extra (v1) sandbox features/services for the fuzzer and test components.
+#
 #   options (optional)
 #     [list of strings] Key-value pairs representing libFuzzer options, e.g. "foo=bar". See
 #       See https://llvm.org/docs/LibFuzzer.html#options.
@@ -51,11 +54,22 @@
         args += [ "-$option" ]
       }
     }
-    services = [
+    forward_variables_from(invoker,
+                           [
+                             "features",
+                             "services",
+                           ])
+    if (!defined(features)) {
+      features = []
+    }
+    if (!defined(services)) {
+      services = []
+    }
+    services += [
       "fuchsia.logger.LogSink",
       "fuchsia.process.Launcher",
     ]
-    features = [
+    features += [
       "isolated-persistent-storage",
       "isolated-temp",
     ]
diff --git a/build/fuzzing/fuzzer_test_component.gni b/build/fuzzing/fuzzer_test_component.gni
index acfcf9a..64ff6be 100644
--- a/build/fuzzing/fuzzer_test_component.gni
+++ b/build/fuzzing/fuzzer_test_component.gni
@@ -30,6 +30,9 @@
 #     [label] If present, includes program arguments in the component manifest
 #       to use the corpus when testing.
 #
+#   features, services (optional)
+#     [list of strings] Extra (v1) sandbox features/services for the fuzzer and test components.
+#
 template("fuzzer_test_component") {
   assert(defined(invoker.fuzzer),
          "missing 'fuzzer' for fuzzer_test_component($target_name)")
@@ -54,8 +57,19 @@
       args += [ "pkg/data/" + rebase_path(corpus_path, "//") ]
     }
     args += [ "data/corpus" ]
-    services = [ "fuchsia.logger.LogSink" ]
-    features = [ "isolated-persistent-storage" ]
+    forward_variables_from(invoker,
+                           [
+                             "features",
+                             "services",
+                           ])
+    if (!defined(features)) {
+      features = []
+    }
+    if (!defined(services)) {
+      services = []
+    }
+    services += [ "fuchsia.logger.LogSink" ]
+    features += [ "isolated-persistent-storage" ]
     metadata = {
       fuzz_spec = [
         {
diff --git a/examples/fuzzers/BUILD.gn b/examples/fuzzers/BUILD.gn
index 5cd438a..070ed7a 100644
--- a/examples/fuzzers/BUILD.gn
+++ b/examples/fuzzers/BUILD.gn
@@ -34,6 +34,10 @@
     "cpp:corpus_fuzzer_without_corpus",
     "cpp:dictionary_fuzzer",
     "cpp:dictionary_fuzzer_without_dictionary",
+    {
+      label = "cpp:extended_sandbox"
+      fuzz_host = false
+    },
     "cpp:fuzzed_data_provider_fuzzer",
     "cpp:noop_fuzzer",
     {
diff --git a/examples/fuzzers/cpp/BUILD.gn b/examples/fuzzers/cpp/BUILD.gn
index bb86210..9b67128 100644
--- a/examples/fuzzers/cpp/BUILD.gn
+++ b/examples/fuzzers/cpp/BUILD.gn
@@ -44,6 +44,12 @@
   sources = [ "noop.cc" ]
 }
 
+fuzzer("extended_sandbox") {
+  sources = [ "extended_sandbox.cc" ]
+  deps = [ "//src/lib/files" ]
+  features = [ "isolated-cache-storage" ]
+}
+
 # Uses non-default output name. See the corresponding scope in //examples/fuzzer:fuzzing-examples
 fuzzer("oom_fuzzer") {
   output_name = "out_of_memory_fuzzer"
diff --git a/examples/fuzzers/cpp/extended_sandbox.cc b/examples/fuzzers/cpp/extended_sandbox.cc
new file mode 100644
index 0000000..10b4ca4
--- /dev/null
+++ b/examples/fuzzers/cpp/extended_sandbox.cc
@@ -0,0 +1,17 @@
+// Copyright 2020 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.
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "src/lib/files/directory.h"
+
+// A simple fuzzer that needs the non-default `isolated-cache-storage` sandbox feature to run
+// correctly.
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  assert(files::IsDirectory("/cache"));
+  return 0;
+}