No public description

PiperOrigin-RevId: 1007273069
diff --git a/centipede/centipede_flags.inc b/centipede/centipede_flags.inc
index 2a21f1e..de646e7 100644
--- a/centipede/centipede_flags.inc
+++ b/centipede/centipede_flags.inc
@@ -507,6 +507,11 @@
 CENTIPEDE_FLAG(std::string, fuzztest_workdir_root, "",
                "The root directory for Centipede workdirs for working on the "
                "corpus database.")
+CENTIPEDE_FLAG(
+    std::vector<std::string>, fuzztest_seed_corpus_databases, {},
+    "A comma-separated list of read-only corpus databases. When fuzzing with "
+    "the corpus database, the coverage inputs of the test in these databases "
+    "are used as additional seeds.")
 CENTIPEDE_FLAG(bool, fuzztest_only_replay, false,
                "If set, further steps are skipped after replaying with the "
                "corpus database.")
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc
index fea652e..4ed11a0 100644
--- a/centipede/centipede_interface.cc
+++ b/centipede/centipede_interface.cc
@@ -330,11 +330,13 @@
 }
 
 // Seeds the corpus files in `env.workdir` with the inputs in `regression_dir`
-// (always used) and the previously distilled corpus files from `coverage_dir`
-// (used if non-empty).
-SeedCorpusConfig GetSeedCorpusConfig(const Environment& env,
-                                     std::string_view regression_dir,
-                                     std::string_view coverage_dir) {
+// (always used), the previously distilled corpus files from `coverage_dir`
+// (used if non-empty), and the inputs in `seed_coverage_dirs`, which are the
+// coverage directories in read-only seed corpus databases.
+SeedCorpusConfig GetSeedCorpusConfig(
+    const Environment& env, std::string_view regression_dir,
+    std::string_view coverage_dir,
+    const std::vector<std::string>& seed_coverage_dirs) {
   const WorkDir workdir{env};
   SeedCorpusSource regression;
   regression.dir_glob = std::string(regression_dir);
@@ -342,9 +344,9 @@
   regression.individual_input_rel_glob = "*";
   regression.sampled_fraction_or_count = 1.0f;
   std::vector<SeedCorpusSource> sources = {std::move(regression)};
-  if (!coverage_dir.empty()) {
+  const auto make_coverage_source = [&workdir](std::string_view dir) {
     SeedCorpusSource coverage;
-    coverage.dir_glob = std::string(coverage_dir);
+    coverage.dir_glob = std::string(dir);
     coverage.num_recent_dirs = 1;
     // We're using the previously distilled corpus files as seeds.
     coverage.shard_rel_glob =
@@ -353,7 +355,13 @@
             .filename();
     coverage.individual_input_rel_glob = "*";
     coverage.sampled_fraction_or_count = 1.0f;
-    sources.push_back(std::move(coverage));
+    return coverage;
+  };
+  if (!coverage_dir.empty()) {
+    sources.push_back(make_coverage_source(coverage_dir));
+  }
+  for (const std::string& seed_coverage_dir : seed_coverage_dirs) {
+    sources.push_back(make_coverage_source(seed_coverage_dir));
   }
   SeedCorpusDestination destination;
   destination.dir_path = env.workdir;
@@ -403,10 +411,12 @@
   std::filesystem::path incubating_dir;
 };
 
-DatabasePaths GetDatabasePaths(const Environment& env) {
-  const auto fuzztest_db_path =
-      std::filesystem::path(env.fuzztest_corpus_database) /
-      env.fuzztest_binary_identifier / env.test_name;
+// Returns the paths for the fuzz test `env.test_name` in the test binary
+// `env.fuzztest_binary_identifier` in the corpus database `corpus_database`.
+DatabasePaths GetDatabasePaths(std::string_view corpus_database,
+                               const Environment& env) {
+  const auto fuzztest_db_path = std::filesystem::path(corpus_database) /
+                                env.fuzztest_binary_identifier / env.test_name;
   return {
       fuzztest_db_path,
       fuzztest_db_path / "regression",
@@ -416,6 +426,34 @@
   };
 }
 
+DatabasePaths GetDatabasePaths(const Environment& env) {
+  return GetDatabasePaths(env.fuzztest_corpus_database, env);
+}
+
+// Returns the coverage directories of the fuzz test in the seed corpus
+// databases. Only used when fuzzing. Directories that don't exist or are
+// inaccessible are skipped, so that the seed corpus databases never break
+// fuzzing.
+std::vector<std::string> GetSeedCoverageDirs(const Environment& env) {
+  std::vector<std::string> seed_coverage_dirs;
+  if (env.fuzztest_only_replay) return seed_coverage_dirs;
+  for (const std::string& seed_corpus_database :
+       env.fuzztest_seed_corpus_databases) {
+    if (seed_corpus_database.empty()) continue;
+    const std::string coverage_dir =
+        GetDatabasePaths(seed_corpus_database, env).coverage_dir;
+    if (!RemotePathIsDirectory(coverage_dir)) {
+      FUZZTEST_VLOG(1) << "Skipping seed corpus database coverage dir "
+                       << coverage_dir << ": not found or inaccessible";
+      continue;
+    }
+    FUZZTEST_LOG(INFO) << "Using seed corpus database coverage dir "
+                       << coverage_dir;
+    seed_coverage_dirs.push_back(coverage_dir);
+  }
+  return seed_coverage_dirs;
+}
+
 void RecordFuzzingResults(const Environment& env, const DatabasePaths& db_paths,
                           CentipedeCallbacksFactory& callbacks_factory,
                           StopCondition& stop_condition) {
@@ -612,7 +650,8 @@
         GetSeedCorpusConfig(env, db_paths.regression_dir.c_str(),
                             env.fuzztest_replay_coverage_inputs
                                 ? db_paths.coverage_dir.c_str()
-                                : ""),
+                                : "",
+                            GetSeedCoverageDirs(env)),
         env.binary_name, env.binary_hash))
         << "while generating the seed corpus";
   }
diff --git a/centipede/environment.cc b/centipede/environment.cc
index f17f751..8fb2a35 100644
--- a/centipede/environment.cc
+++ b/centipede/environment.cc
@@ -245,6 +245,7 @@
   fuzztest_binary_identifier = config.binary_identifier;
   fuzztest_stats_root = config.stats_root;
   fuzztest_workdir_root = config.workdir_root;
+  fuzztest_seed_corpus_databases = config.seed_corpus_databases;
   fuzztest_only_replay = config.only_replay;
   fuzztest_update_corpus_database = config.update_corpus_database;
   fuzztest_execution_id = config.execution_id.value_or("");
diff --git a/centipede/environment_test.cc b/centipede/environment_test.cc
index 8589e9e..b956765 100644
--- a/centipede/environment_test.cc
+++ b/centipede/environment_test.cc
@@ -15,7 +15,9 @@
 #include "./centipede/environment.h"
 
 #include <cstddef>
+#include <string>
 #include <string_view>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/time/time.h"
@@ -193,6 +195,7 @@
   config.binary_identifier = "bin";
   config.stats_root = "stats";
   config.workdir_root = "workdir";
+  config.seed_corpus_databases = {"seed_db1", "seed_db2"};
   config.only_replay = true;
   config.execution_id = "exec";
   config.replay_coverage_inputs = true;
@@ -205,6 +208,8 @@
   EXPECT_EQ(env.fuzztest_binary_identifier, "bin");
   EXPECT_EQ(env.fuzztest_stats_root, "stats");
   EXPECT_EQ(env.fuzztest_workdir_root, "workdir");
+  EXPECT_EQ(env.fuzztest_seed_corpus_databases,
+            (std::vector<std::string>{"seed_db1", "seed_db2"}));
   EXPECT_TRUE(env.fuzztest_only_replay);
   EXPECT_EQ(env.fuzztest_execution_id, "exec");
   EXPECT_TRUE(env.fuzztest_replay_coverage_inputs);
diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc
index 74c1bc5..82d404a 100644
--- a/fuzztest/init_fuzztest.cc
+++ b/fuzztest/init_fuzztest.cc
@@ -405,6 +405,7 @@
       corpus_database,
       /*stats_root=*/"",
       /*workdir_root=*/"",
+      /*seed_corpus_databases=*/{},
       std::string(binary_identifier),
       /*fuzz_tests=*/ListRegisteredTests(),
       /*fuzz_tests_in_current_shard=*/ListRegisteredTests(),
diff --git a/fuzztest/internal/configuration.cc b/fuzztest/internal/configuration.cc
index c2de6ca..4ca302f 100644
--- a/fuzztest/internal/configuration.cc
+++ b/fuzztest/internal/configuration.cc
@@ -206,9 +206,9 @@
   std::string out;
   out.resize(
       SpaceFor(corpus_database) + SpaceFor(stats_root) +
-      SpaceFor(workdir_root) + SpaceFor(binary_identifier) +
-      SpaceFor(fuzz_tests) + SpaceFor(fuzz_tests_in_current_shard) +
-      SpaceFor(continue_after_crash) +
+      SpaceFor(workdir_root) + SpaceFor(seed_corpus_databases) +
+      SpaceFor(binary_identifier) + SpaceFor(fuzz_tests) +
+      SpaceFor(fuzz_tests_in_current_shard) + SpaceFor(continue_after_crash) +
       SpaceFor(reproduce_findings_as_separate_tests) +
       SpaceFor(replay_coverage_inputs) + SpaceFor(only_replay) +
       SpaceFor(update_corpus_database) + SpaceFor(replay_in_single_process) +
@@ -223,6 +223,7 @@
   offset = WriteString(out, offset, corpus_database);
   offset = WriteString(out, offset, stats_root);
   offset = WriteString(out, offset, workdir_root);
+  offset = WriteVectorOfStrings(out, offset, seed_corpus_databases);
   offset = WriteString(out, offset, binary_identifier);
   offset = WriteVectorOfStrings(out, offset, fuzz_tests);
   offset = WriteVectorOfStrings(out, offset, fuzz_tests_in_current_shard);
@@ -255,6 +256,7 @@
     ASSIGN_OR_RETURN(corpus_database, ConsumeString(serialized));
     ASSIGN_OR_RETURN(stats_root, ConsumeString(serialized));
     ASSIGN_OR_RETURN(workdir_root, ConsumeString(serialized));
+    ASSIGN_OR_RETURN(seed_corpus_databases, ConsumeVectorOfStrings(serialized));
     ASSIGN_OR_RETURN(binary_identifier, ConsumeString(serialized));
     ASSIGN_OR_RETURN(fuzz_tests, ConsumeVectorOfStrings(serialized));
     ASSIGN_OR_RETURN(fuzz_tests_in_current_shard,
@@ -295,6 +297,7 @@
     return Configuration{*std::move(corpus_database),
                          *std::move(stats_root),
                          *std::move(workdir_root),
+                         *std::move(seed_corpus_databases),
                          *std::move(binary_identifier),
                          *std::move(fuzz_tests),
                          *std::move(fuzz_tests_in_current_shard),
diff --git a/fuzztest/internal/configuration.h b/fuzztest/internal/configuration.h
index 0aceee9..f4c5083 100644
--- a/fuzztest/internal/configuration.h
+++ b/fuzztest/internal/configuration.h
@@ -52,6 +52,10 @@
   // The root directory for Centipede workdirs, with a layout similar to
   // `corpus_database`.
   std::string workdir_root;
+  // Read-only corpus databases with the same layout as `corpus_database`. When
+  // fuzzing, the coverage inputs of each fuzz test in these databases are used
+  // as additional seeds. These databases are never updated.
+  std::vector<std::string> seed_corpus_databases;
   // The identifier of the test binary in the corpus database (eg.,
   // relative/path/to/binary).
   std::string binary_identifier;
diff --git a/fuzztest/internal/configuration_test.cc b/fuzztest/internal/configuration_test.cc
index a2ccf21..70f9e3a 100644
--- a/fuzztest/internal/configuration_test.cc
+++ b/fuzztest/internal/configuration_test.cc
@@ -16,6 +16,7 @@
   return other.ok() && config.corpus_database == other->corpus_database &&
          config.stats_root == other->stats_root &&
          config.workdir_root == other->workdir_root &&
+         config.seed_corpus_databases == other->seed_corpus_databases &&
          config.binary_identifier == other->binary_identifier &&
          config.fuzz_tests == other->fuzz_tests &&
          config.fuzz_tests_in_current_shard ==
@@ -50,6 +51,7 @@
   Configuration configuration{"corpus_database",
                               "stats_root",
                               "workdir_root",
+                              /*seed_corpus_databases=*/{},
                               "binary_identifier",
                               /*fuzz_tests=*/{},
                               /*fuzz_tests_in_current_shard=*/{},
@@ -82,6 +84,7 @@
   Configuration configuration{"corpus_database",
                               "stats_root",
                               "workdir_root",
+                              {"seed_db1", "seed_db2"},
                               "binary_identifier",
                               {"FuzzTest1", "FuzzTest2"},
                               {"FuzzTest1"},