Skip to content

Commit

Permalink
No public description
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 703558942
  • Loading branch information
xinhaoyuan authored and copybara-github committed Dec 13, 2024
1 parent 4c740a0 commit 0ac149f
Show file tree
Hide file tree
Showing 26 changed files with 1,089 additions and 326 deletions.
5 changes: 5 additions & 0 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,7 @@ cc_library(
":environment",
":mutation_input",
":runner_result",
":stop",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_fuzztest//common:defs",
Expand Down Expand Up @@ -1121,6 +1122,7 @@ cc_library(
":thread_pool",
":util",
":workdir",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/random",
Expand Down Expand Up @@ -1688,8 +1690,11 @@ cc_test(
":feature",
":seed_corpus_maker_lib",
":workdir",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/strings",
"@com_google_fuzztest//common:defs",
"@com_google_fuzztest//common:logging",
"@com_google_fuzztest//common:remote_file",
"@com_google_fuzztest//common:test_util",
"@com_google_googletest//:gtest_main",
],
Expand Down
13 changes: 12 additions & 1 deletion centipede/centipede_callbacks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ int CentipedeCallbacks::ExecuteCentipedeSancovBinaryWithShmem(
<< env_.shmem_size_mb;
}

if (env_.print_runner_log) PrintExecutionLog();
if (env_.print_runner_log) {
LOG(INFO) << "Runner execution log:";
PrintExecutionLog();
}

if (retval != EXIT_SUCCESS) {
ReadFromLocalFile(execute_log_path_, batch_result.log());
Expand Down Expand Up @@ -264,6 +267,11 @@ bool CentipedeCallbacks::GetSeedsViaExternalBinary(
.temp_file_path = temp_input_file_path_}};
const int retval = cmd.Execute();

if (env_.print_runner_log) {
LOG(INFO) << "Getting seeds via external binary returns " << retval;
PrintExecutionLog();
}

std::vector<std::string> seed_input_filenames;
for (const auto &dir_ent : std::filesystem::directory_iterator(output_dir)) {
seed_input_filenames.push_back(dir_ent.path().filename());
Expand Down Expand Up @@ -340,6 +348,9 @@ bool CentipedeCallbacks::MutateViaExternalBinary(
}
if (env_.print_runner_log || retval != EXIT_SUCCESS) {
PrintExecutionLog();
} else if (env_.print_runner_log) {
LOG(INFO) << "Custom mutator log:";
PrintExecutionLog();
}

// Read all mutants.
Expand Down
6 changes: 6 additions & 0 deletions centipede/centipede_default_callbacks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "./centipede/environment.h"
#include "./centipede/mutation_input.h"
#include "./centipede/runner_result.h"
#include "./centipede/stop.h"
#include "./common/defs.h"
#include "./common/logging.h" // IWYU pragma: keep

Expand Down Expand Up @@ -83,6 +84,11 @@ void CentipedeDefaultCallbacks::Mutate(
LOG_FIRST_N(WARNING, 5)
<< "Custom mutator returned no mutants: falling back to internal "
"default mutator";
} else if (ShouldStop()) {
LOG(WARNING) << "Custom mutator failed, but ignored since the stop "
"condition it met. Possibly what triggered the stop "
"condition also interrupted the mutator.";
return;
} else {
LOG(WARNING) << "Custom mutator undetected or misbehaving:";
CHECK(!custom_mutator_is_usable_.has_value())
Expand Down
193 changes: 157 additions & 36 deletions centipede/centipede_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ BinaryInfo PopulateBinaryInfoAndSavePCsIfNecessary(
LOG(INFO) << "Serializing binary info to: " << binary_info_dir;
binary_info.Write(binary_info_dir);
}
if (binary_info.uses_legacy_trace_pc_instrumentation) {
if (binary_info.uses_legacy_trace_pc_instrumentation &&
!binary_info.pc_table.empty()) {
pcs_file_path = std::filesystem::path(TemporaryLocalDirPath()) / "pcs";
SavePCTableToFile(binary_info.pc_table, pcs_file_path);
}
Expand Down Expand Up @@ -423,21 +424,35 @@ void DeduplicateAndStoreNewCrashes(
}

// Seeds the corpus files in `env.workdir` with the previously distilled corpus
// files from `src_dir`.
// files from `coverage_dir` (if non-empty)
SeedCorpusConfig GetSeedCorpusConfig(const Environment &env,
std::string_view src_dir) {
std::string_view regression_dir,
std::string_view coverage_dir) {
const WorkDir workdir{env};
std::vector<SeedCorpusSource> sources;
if (!regression_dir.empty()) {
sources.push_back(SeedCorpusSource{
.dir_glob = std::string(regression_dir),
.num_recent_dirs = 1,
.individual_input_rel_glob = "*",
.sampled_fraction_or_count = 1.0f,
});
}
if (!coverage_dir.empty()) {
sources.push_back(SeedCorpusSource{
.dir_glob = std::string(coverage_dir),
.num_recent_dirs = 1,
// We're using the previously distilled corpus files as seeds.
.shard_rel_glob =
std::filesystem::path{
workdir.DistilledCorpusFilePaths().AllShardsGlob()}
.filename(),
.individual_input_rel_glob = "*",
.sampled_fraction_or_count = 1.0f,
});
}
return {
.sources = {SeedCorpusSource{
.dir_glob = std::string(src_dir),
.num_recent_dirs = 1,
// We're using the previously distilled corpus files as seeds.
.shard_rel_glob =
std::filesystem::path{
workdir.DistilledCorpusFilePaths().AllShardsGlob()}
.filename(),
.sampled_fraction_or_count = 1.0f,
}},
.sources = std::move(sources),
.destination =
{
.dir_path = env.workdir,
Expand Down Expand Up @@ -505,10 +520,31 @@ int UpdateCorpusDatabaseForFuzzTests(
absl::FormatTime("%Y-%m-%d-%H-%M-%S", absl::Now(), absl::UTCTimeZone());
return stamp;
}();
const auto fuzz_tests_to_run = [fuzztest_config, test_shard_index,
total_test_shards] {
if (getenv("FUZZTEST_CENTIPEDE_BINARY") == nullptr) {
CHECK(fuzztest_config.fuzz_tests_in_current_shard.size() == 1)
<< "Must select exactly one fuzz test when running in the unified "
"exeuction model.";
return fuzztest_config.fuzz_tests_in_current_shard;
}
std::vector<std::string> results;
for (int i = 0; i < fuzztest_config.fuzz_tests.size(); ++i) {
if (i % total_test_shards == test_shard_index) {
results.push_back(fuzztest_config.fuzz_tests[i]);
}
}
return results;
}();

const bool is_workdir_separate = !env.workdir.empty();
// the full workdir paths will be formed by appending the fuzz test names to
// the base workdir path.
const auto base_workdir_path =
corpus_database_path / absl::StrFormat("workdir.%03d", test_shard_index);
is_workdir_separate
? std::filesystem::path(env.workdir)
: corpus_database_path /
absl::StrFormat("workdir.%03d", test_shard_index);
// There's no point in saving the binary info to the workdir, since the
// workdir is deleted at the end.
env.save_binary_info = false;
Expand All @@ -523,9 +559,8 @@ int UpdateCorpusDatabaseForFuzzTests(
// Find the last index of a fuzz test for which we already have a workdir.
bool is_resuming = false;
int resuming_fuzztest_idx = 0;
for (int i = 0; i < fuzztest_config.fuzz_tests.size(); ++i) {
if (i % total_test_shards != test_shard_index) continue;
env.workdir = base_workdir_path / fuzztest_config.fuzz_tests[i];
for (int i = 0; i < fuzz_tests_to_run.size(); ++i) {
env.workdir = base_workdir_path / fuzz_tests_to_run[i];
// Check the existence of the coverage path to not only make sure the
// workdir exists, but also that it was created for the same binary as in
// this run.
Expand All @@ -536,19 +571,17 @@ int UpdateCorpusDatabaseForFuzzTests(
}

LOG_IF(INFO, is_resuming) << "Resuming from the fuzz test "
<< fuzztest_config.fuzz_tests[resuming_fuzztest_idx]
<< fuzz_tests_to_run[resuming_fuzztest_idx]
<< " (index: " << resuming_fuzztest_idx << ")";

// Step 3: Iterate over the fuzz tests and run them.
const std::string binary = env.binary;
for (int i = resuming_fuzztest_idx; i < fuzztest_config.fuzz_tests.size();
++i) {
if (i % total_test_shards != test_shard_index) continue;
for (int i = resuming_fuzztest_idx; i < fuzz_tests_to_run.size(); ++i) {
ReportErrorWhenNotEnoughTimeToRunEverything(
start_time, fuzztest_config.time_limit,
/*executed_tests_in_shard=*/i / total_test_shards,
fuzztest_config.fuzz_tests.size(), total_test_shards);
env.workdir = base_workdir_path / fuzztest_config.fuzz_tests[i];
env.workdir = base_workdir_path / fuzz_tests_to_run[i];
if (RemotePathExists(env.workdir) && !is_resuming) {
// This could be a workdir from a failed run that used a different version
// of the binary. We delete it so that we don't have to deal with the
Expand All @@ -559,21 +592,31 @@ int UpdateCorpusDatabaseForFuzzTests(
CHECK_OK(RemoteMkdir(
workdir.CoverageDirPath())); // Implicitly creates the workdir

// Seed the fuzzing session with the latest coverage corpus from the
// previous fuzzing session.
const std::filesystem::path fuzztest_db_path =
corpus_database_path / fuzztest_config.fuzz_tests[i];
corpus_database_path / fuzz_tests_to_run[i];
const std::filesystem::path regression_dir =
fuzztest_db_path / "regression";
const std::filesystem::path coverage_dir = fuzztest_db_path / "coverage";
if (RemotePathExists(coverage_dir.c_str()) && !is_resuming) {
CHECK_OK(GenerateSeedCorpusFromConfig(
GetSeedCorpusConfig(env, coverage_dir.c_str()), env.binary_name,
env.binary_hash));

// Seed the fuzzing session with the latest coverage corpus and regression
// inputs from the previous fuzzing session.
if (!is_resuming) {
if (const auto status = GenerateSeedCorpusFromConfig(
GetSeedCorpusConfig(
env, regression_dir.c_str(),
fuzztest_config.replay_corpus ? coverage_dir.c_str() : ""),
env.binary_name, env.binary_hash);
!status.ok()) {
LOG(ERROR) << "Got error while generating the seed corpus: " << status;
}
}

// TODO: b/338217594 - Call the FuzzTest binary in a flag-agnostic way.
constexpr std::string_view kFuzzTestFuzzFlag = "--fuzz=";
env.binary = absl::StrCat(binary, " ", kFuzzTestFuzzFlag,
fuzztest_config.fuzz_tests[i]);
if (getenv("FUZZTEST_CENTIPEDE_BINARY")) {
// TODO: b/338217594 - Call the FuzzTest binary in a flag-agnostic way.
constexpr std::string_view kFuzzTestFuzzFlag = "--fuzz=";
env.binary =
absl::StrCat(binary, " ", kFuzzTestFuzzFlag, fuzz_tests_to_run[i]);
}

absl::Duration time_limit = fuzztest_config.GetTimeLimitPerTest();
absl::Duration time_spent = absl::ZeroDuration();
Expand All @@ -585,8 +628,8 @@ int UpdateCorpusDatabaseForFuzzTests(
}
is_resuming = false;

LOG(INFO) << "Fuzzing " << fuzztest_config.fuzz_tests[i] << " for "
<< time_limit << "\n\tTest binary: " << env.binary;
LOG(INFO) << "Fuzzing " << fuzz_tests_to_run[i] << " for " << time_limit
<< "\n\tTest binary: " << env.binary;

const absl::Time start_time = absl::Now();
ClearEarlyStopRequestAndSetStopTime(/*stop_time=*/start_time + time_limit);
Expand All @@ -597,14 +640,16 @@ int UpdateCorpusDatabaseForFuzzTests(
record_fuzzing_time.Stop();

if (!stats_root_path.empty()) {
const auto stats_dir = stats_root_path / fuzztest_config.fuzz_tests[i];
const auto stats_dir = stats_root_path / fuzz_tests_to_run[i];
CHECK_OK(RemoteMkdir(stats_dir.c_str()));
CHECK_OK(RemotePathRename(
workdir.FuzzingStatsPath(),
(stats_dir / absl::StrCat("fuzzing_stats_", execution_stamp))
.c_str()));
}

if (fuzztest_config.replay_only || is_workdir_separate) continue;

// Distill and store the coverage corpus.
Distill(env);
if (RemotePathExists(coverage_dir.c_str())) {
Expand Down Expand Up @@ -639,6 +684,28 @@ int UpdateCorpusDatabaseForFuzzTests(
return EXIT_SUCCESS;
}

absl::Status ListCrashIdsToFile(
const fuzztest::internal::Configuration &target_config,
std::string_view output_file) {
std::vector<std::string> results;
std::vector<std::string> crash_path_strs;
const auto glob = std::filesystem::path(target_config.corpus_database) /
target_config.binary_identifier / "*" / "crashing" / "*";
const absl::Status match_status =
RemoteGlobMatch(glob.string(), crash_path_strs);
if (!match_status.ok() && !absl::IsNotFound(match_status))
return match_status;
for (const auto &crash_path_str : crash_path_strs) {
const std::filesystem::path crash_path = {crash_path_str};
const std::string crash_id = crash_path.filename();
const std::string test_name =
crash_path.parent_path().parent_path().filename();
results.push_back(absl::StrCat(test_name, "/", crash_id));
}
CHECK_OK(RemoteFileSetContents(output_file, absl::StrJoin(results, "\n")));
return absl::OkStatus();
}

} // namespace

int CentipedeMain(const Environment &env,
Expand Down Expand Up @@ -694,6 +761,60 @@ int CentipedeMain(const Environment &env,
CHECK_OK(target_config.status())
<< "Failed to deserialize target configuration";
if (!target_config->corpus_database.empty()) {
if (!env.list_crash_ids_to_file.empty()) {
CHECK_OK(
ListCrashIdsToFile(*target_config, env.list_crash_ids_to_file));
return EXIT_SUCCESS;
} else if (!env.run_crash_id.empty()) {
CHECK(target_config->fuzz_tests_in_current_shard.size() == 1)
<< "Expecting exactly one test for run_crash_id";
const auto crash_dir =
std::filesystem::path(target_config->corpus_database) /
target_config->binary_identifier /
target_config->fuzz_tests_in_current_shard[0] / "crashing";
if (env.fetch_crash_to_file.empty()) {
const WorkDir workdir{env};
CHECK_OK(GenerateSeedCorpusFromConfig(
SeedCorpusConfig{
.sources = {{
.dir_glob = crash_dir.string(),
.num_recent_dirs = 1,
.individual_input_rel_glob = env.run_crash_id,
.sampled_fraction_or_count = 1.0f,
}},
.destination =
{.dir_path = env.workdir,
.shard_rel_glob =
std::filesystem::path{
workdir.CorpusFilePaths().AllShardsGlob()}
.filename(),
.shard_index_digits = WorkDir::kDigitsInShardIndex,
.num_shards = 1},
},
env.binary_name, env.binary_hash));
return Fuzz(env, {}, "", callbacks_factory);
} else {
std::string crash_contents;
const auto read_status = RemoteFileGetContents(
(crash_dir / env.run_crash_id).string(), crash_contents);
if (!read_status.ok()) {
LOG(ERROR) << "Failed reading the crash " << env.run_crash_id
<< " from " << crash_dir.string() << ": "
<< read_status;
return EXIT_FAILURE;
}
const auto write_status =
RemoteFileSetContents(env.fetch_crash_to_file, crash_contents);
if (!write_status.ok()) {
LOG(ERROR) << "Failed write the crash " << env.run_crash_id
<< " to " << env.fetch_crash_to_file << ": "
<< write_status;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
}

const auto time_limit_per_test = target_config->GetTimeLimitPerTest();
CHECK(time_limit_per_test < absl::InfiniteDuration())
<< "Updating corpus database requires specifying time limit per "
Expand Down
3 changes: 3 additions & 0 deletions centipede/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ struct Environment {
bool first_corpus_dir_output_only = false;
// If set, load/merge shards without fuzzing new inputs.
bool load_shards_only = false;
std::string list_crash_ids_to_file;
std::string run_crash_id;
std::string fetch_crash_to_file;

// Command line-related fields -----------------------------------------------

Expand Down
Loading

0 comments on commit 0ac149f

Please sign in to comment.