From 98b09707c47e5d48ec73548bf75d80dfe0edd3a4 Mon Sep 17 00:00:00 2001 From: Filip Niksic Date: Wed, 11 Feb 2026 18:08:24 -0800 Subject: [PATCH] Handle empty crash signatures and descriptions in crash deduplication. This change adds checks for empty crash signatures and descriptions. By default, inputs with empty metadata are now ignored with an error log. An environment variable, FUZZTEST_FAIL_ON_EMPTY_CRASH_METADATA, can be set to make these empty metadata cases fatal. Additionally, failures to read crash descriptions now cause the corresponding input to be ignored. PiperOrigin-RevId: 868929414 --- centipede/crash_deduplication.cc | 31 +++++++++++++-- centipede/crash_deduplication_test.cc | 56 +++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/centipede/crash_deduplication.cc b/centipede/crash_deduplication.cc index 417a0b61..545eb2dc 100644 --- a/centipede/crash_deduplication.cc +++ b/centipede/crash_deduplication.cc @@ -15,6 +15,7 @@ #include "./centipede/crash_deduplication.h" #include +#include #include // NOLINT #include #include @@ -53,6 +54,8 @@ std::string GetInputFileName(std::string_view bug_id, absl::flat_hash_map GetCrashesFromWorkdir( const WorkDir& workdir, size_t total_shards) { + const bool fail_on_empty_crash_metadata = + std::getenv("FUZZTEST_FAIL_ON_EMPTY_CRASH_METADATA") != nullptr; absl::flat_hash_map crashes; for (size_t shard_idx = 0; shard_idx < total_shards; ++shard_idx) { std::vector crashing_input_paths = @@ -79,6 +82,15 @@ absl::flat_hash_map GetCrashesFromWorkdir( << " due to failure to read the crash signature: " << status; continue; } + if (crash_signature.empty()) { + FUZZTEST_LOG_IF(FATAL, fail_on_empty_crash_metadata) + << "Empty crash signature for " << crashing_input_file_name; + FUZZTEST_LOG(ERROR) + << "Ignoring crashing input " << crashing_input_file_name + << " due to empty crash signature. This is an internal error; " + "please report it to the FuzzTest team!"; + continue; + } if (crashes.contains(crash_signature)) continue; const std::string crash_description_path = @@ -86,9 +98,22 @@ absl::flat_hash_map GetCrashesFromWorkdir( std::string crash_description; const absl::Status description_status = RemoteFileGetContents(crash_description_path, crash_description); - FUZZTEST_LOG_IF(WARNING, !description_status.ok()) - << "Failed to read crash description for " << crashing_input_file_name - << ".Status: " << description_status; + if (!description_status.ok()) { + FUZZTEST_LOG(WARNING) + << "Ignoring crashing input " << crashing_input_file_name + << " due to failure to read the crash description: " + << description_status; + continue; + } + if (crash_description.empty()) { + FUZZTEST_LOG_IF(FATAL, fail_on_empty_crash_metadata) + << "Empty crash description for " << crashing_input_file_name; + FUZZTEST_LOG(ERROR) + << "Ignoring crashing input " << crashing_input_file_name + << " due to empty crash description. This is an internal error; " + "please report it to the FuzzTest team!"; + continue; + } crashes.insert( {std::move(crash_signature), // Centipede uses the input signature (i.e., the hash of the input) diff --git a/centipede/crash_deduplication_test.cc b/centipede/crash_deduplication_test.cc index e73e7c54..bd1966e4 100644 --- a/centipede/crash_deduplication_test.cc +++ b/centipede/crash_deduplication_test.cc @@ -91,6 +91,16 @@ TEST(GetCrashesFromWorkdirTest, ReturnsOneCrashPerCrashSignature) { // `isig4` lacks `.sig` and `.desc` files and should be ignored. SetContentsAndGetPath(crashes1, "isig4", "input4"); + // `isig5` has empty crash signature and should be ignored. + auto input5_path = SetContentsAndGetPath(crashes1, "isig5", "input5"); + SetContentsAndGetPath(crash_metadata1, "isig5.sig", ""); + SetContentsAndGetPath(crash_metadata1, "isig5.desc", "desc5"); + + // `isig6` has empty crash description and should be ignored. + auto input6_path = SetContentsAndGetPath(crashes1, "isig6", "input6"); + SetContentsAndGetPath(crash_metadata1, "isig6.sig", "csig6"); + SetContentsAndGetPath(crash_metadata1, "isig6.desc", ""); + const auto crashes = GetCrashesFromWorkdir(workdir, /*total_shards=*/2); EXPECT_THAT( crashes, @@ -100,6 +110,52 @@ TEST(GetCrashesFromWorkdirTest, ReturnsOneCrashPerCrashSignature) { Pair("csig2", FieldsAre("isig2", "desc2", input2_path)))); } +TEST(GetCrashesFromWorkdirTest, FailsOnEmptyCrashSignatureIfEnvVarSet) { + TempDir test_dir; + const std::string workdir_path = test_dir.path(); + WorkDir workdir{workdir_path, "binary_name", "binary_hash", + /*my_shard_index=*/0}; + + const std::filesystem::path crashes = + workdir.CrashReproducerDirPaths().Shard(0); + const std::filesystem::path crash_metadata = + workdir.CrashMetadataDirPaths().Shard(0); + std::filesystem::create_directories(crashes); + std::filesystem::create_directories(crash_metadata); + + auto input_path = SetContentsAndGetPath(crashes, "isig", "input"); + SetContentsAndGetPath(crash_metadata, "isig.sig", ""); + SetContentsAndGetPath(crash_metadata, "isig.desc", "desc"); + + setenv("FUZZTEST_FAIL_ON_EMPTY_CRASH_METADATA", "1", /*overwrite=*/1); + EXPECT_DEATH(GetCrashesFromWorkdir(workdir, /*total_shards=*/1), + "Empty crash signature"); + unsetenv("FUZZTEST_FAIL_ON_EMPTY_CRASH_METADATA"); +} + +TEST(GetCrashesFromWorkdirTest, FailsOnEmptyCrashDescriptionIfEnvVarSet) { + TempDir test_dir; + const std::string workdir_path = test_dir.path(); + WorkDir workdir{workdir_path, "binary_name", "binary_hash", + /*my_shard_index=*/0}; + + const std::filesystem::path crashes = + workdir.CrashReproducerDirPaths().Shard(0); + const std::filesystem::path crash_metadata = + workdir.CrashMetadataDirPaths().Shard(0); + std::filesystem::create_directories(crashes); + std::filesystem::create_directories(crash_metadata); + + auto input_path = SetContentsAndGetPath(crashes, "isig", "input"); + SetContentsAndGetPath(crash_metadata, "isig.sig", "csig"); + SetContentsAndGetPath(crash_metadata, "isig.desc", ""); + + setenv("FUZZTEST_FAIL_ON_EMPTY_CRASH_METADATA", "1", /*overwrite=*/1); + EXPECT_DEATH(GetCrashesFromWorkdir(workdir, /*total_shards=*/1), + "Empty crash description"); + unsetenv("FUZZTEST_FAIL_ON_EMPTY_CRASH_METADATA"); +} + class FakeCentipedeCallbacks : public CentipedeCallbacks { public: struct Crash {