Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions centipede/crash_deduplication.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "./centipede/crash_deduplication.h"

#include <cstddef>
#include <cstdlib>
#include <filesystem> // NOLINT
#include <string>
#include <string_view>
Expand Down Expand Up @@ -53,6 +54,8 @@ std::string GetInputFileName(std::string_view bug_id,

absl::flat_hash_map<std::string, CrashDetails> 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<std::string, CrashDetails> crashes;
for (size_t shard_idx = 0; shard_idx < total_shards; ++shard_idx) {
std::vector<std::string> crashing_input_paths =
Expand All @@ -79,16 +82,38 @@ absl::flat_hash_map<std::string, CrashDetails> 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 =
crash_metadata_dir / absl::StrCat(crashing_input_file_name, ".desc");
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)
Expand Down
56 changes: 56 additions & 0 deletions centipede/crash_deduplication_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
Loading