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"},