Skip to content

Commit

Permalink
Increase crash reporting limit when running with FuzzTest.
Browse files Browse the repository at this point in the history
Also adjust the e2e test to reduce flakiness.

PiperOrigin-RevId: 722741758
  • Loading branch information
xinhaoyuan authored and copybara-github committed Feb 6, 2025
1 parent c013c00 commit 52f7557
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 40 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/bazel_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ jobs:
config: ['default', 'fuzztest']
compilation_mode: ['fastbuild', 'opt', 'dbg']
steps:
# TODO(lszekeres): Remove once the following is fixed:
# https://github.com/actions/runner-images/issues/9491
- name: Reduce ASLR entropy as a temporary workaround
run: |
sudo sysctl -w vm.mmap_rnd_bits=28
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
Expand Down Expand Up @@ -73,6 +78,7 @@ jobs:
-c ${{ matrix.compilation_mode }} \
--config=fuzztest-experimental --config=asan \
--platform_suffix=fuzztest-experimental-asan \
--test_env=ASAN_OPTIONS=detect_leaks=0 \
//e2e_tests:corpus_database_test
- name: Save new cache based on main
if: github.ref == 'refs/heads/main'
Expand Down
19 changes: 14 additions & 5 deletions centipede/command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,18 @@ std::string Command::ToString() const {
ss.push_back(arg);
}
// out/err.
if (!options_.stdout_file.empty()) {
ss.push_back(absl::StrCat("> ", options_.stdout_file));
}
if (!options_.stderr_file.empty()) {
if (options_.stdout_file != options_.stderr_file) {
ss.push_back(absl::StrCat("2> ", options_.stderr_file));
ss.push_back(absl::StrFormat(R"sh(2> %s)sh", options_.stderr_file));
} else {
ss.push_back("2>&1");
}
}
if (!options_.stdout_file.empty()) {
ss.push_back(absl::StrFormat(
R"sh(| while IFS= read -r line; do printf '[%%s] %%s\n' "$(date '+%%s.%%N')" "$line"; done > %s )sh",
options_.stdout_file));
}
// Trim trailing space and return.
return absl::StrJoin(ss, kCommandLineSeparator);
}
Expand Down Expand Up @@ -309,7 +311,7 @@ absl::Status Command::VerifyForkServerIsHealthy() {
}

int Command::Execute() {
VLOG(1) << "Executing command '" << command_line_ << "'...";
LOG(INFO) << "Executing command '" << command_line_ << "'...";

int exit_code = EXIT_SUCCESS;

Expand All @@ -333,6 +335,7 @@ int Command::Execute() {
struct pollfd poll_fd = {};
int poll_ret = -1;
auto poll_deadline = absl::Now() + options_.timeout;
LOG(INFO) << "Polling deadline is " << poll_deadline;
// The `poll()` syscall can get interrupted: it sets errno==EINTR in that
// case. We should tolerate that.
do {
Expand All @@ -344,7 +347,12 @@ int Command::Execute() {
const int poll_timeout_ms = static_cast<int>(absl::ToInt64Milliseconds(
std::max(poll_deadline - absl::Now(), absl::Milliseconds(1))));
poll_ret = poll(&poll_fd, 1, poll_timeout_ms);
const int saved_errno = errno;
LOG(INFO) << "poll_ret = " << poll_ret << " with errno = " << saved_errno
<< " using timeout_ms " << poll_timeout_ms;
errno = saved_errno;
} while (poll_ret < 0 && errno == EINTR);
LOG(INFO) << "Polling finished";

if (poll_ret != 1 || (poll_fd.revents & POLLIN) == 0) {
// The fork server errored out or timed out, or some other error occurred,
Expand All @@ -363,6 +371,7 @@ int Command::Execute() {
// The fork server wrote the execution result to the pipe: read it.
CHECK_EQ(sizeof(exit_code),
read(fork_server_->pipe_[1], &exit_code, sizeof(exit_code)));
LOG(INFO) << "Reading finished";
} else {
VLOG(1) << "Fork server disabled - executing command directly";
// No fork server, use system().
Expand Down
10 changes: 10 additions & 0 deletions centipede/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,16 @@ void Environment::ReadKnobsFileIfSpecified() {

void Environment::UpdateWithTargetConfig(
const fuzztest::internal::Configuration &config) {
// Allow more crashes to be reported when running with FuzzTest. This allows
// more unique crashes to collected after deduplication. But we don't want to
// make the limit too large to stress the filesystem, so this is not a perfect
// solution. Currently we just increase the default to be seemingly large
// enough.
if (max_num_crash_reports == Default().max_num_crash_reports) {
max_num_crash_reports = 20;
LOG(INFO) << "Overriding the default max_num_crash_reports to "
<< max_num_crash_reports << " for FuzzTest.";
}
if (config.jobs != 0) {
CHECK(j == Default().j || j == config.jobs)
<< "Value for --j is inconsistent with the value for jobs in the "
Expand Down
3 changes: 2 additions & 1 deletion centipede/runner_fork_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ namespace centipede {

namespace {

constexpr bool kForkServerDebug = false;
constexpr bool kForkServerDebug = true;
[[maybe_unused]] constexpr bool kForkServerDumpEnvAtStart = false;

} // namespace
Expand Down Expand Up @@ -227,6 +227,7 @@ __attribute__((constructor(150))) void ForkServerCallMeVeryEarly() {
} else {
// Parent process.
int status = -1;
Log("###Centipede fork waiting for child\n");
if (waitpid(pid, &status, 0) < 0) Exit("###waitpid failed\n");
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS)
Expand Down
8 changes: 4 additions & 4 deletions centipede/testing/centipede_main_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ test_pcpair_features() {
}

centipede::test_crashing_target abort_test_fuzz "foo" "AbOrT" "I AM ABOUT TO ABORT"
test_debug_symbols
test_dictionary
test_for_each_blob
test_pcpair_features
# test_debug_symbols
# test_dictionary
# test_for_each_blob
# test_pcpair_features

echo "PASS"
1 change: 1 addition & 0 deletions e2e_tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ cc_test(
deps = [
":test_binary_util",
"@com_google_absl//absl/base:no_destructor",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
Expand Down
62 changes: 36 additions & 26 deletions e2e_tests/corpus_database_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "gtest/gtest.h"
#include "absl/base/no_destructor.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
Expand Down Expand Up @@ -108,30 +109,35 @@ class UpdateCorpusDatabaseTest

static RunResults RunBinaryMaybeWithCentipede(absl::string_view binary_path,
const RunOptions &options) {
switch (GetParam()) {
case ExecutionModelParam::kSingleBinary:
return RunBinary(binary_path, options);
case ExecutionModelParam::kWithCentipedeBinary: {
RunOptions centipede_options;
centipede_options.env = options.env;
centipede_options.timeout = options.timeout;
std::vector<std::string> binary_args;
binary_args.push_back(std::string(binary_path));
for (const auto &[key, value] : options.fuzztest_flags) {
binary_args.push_back(CreateFuzzTestFlag(key, value));
LOG(INFO) << "Run starts";
RunResults results = [&] {
switch (GetParam()) {
case ExecutionModelParam::kSingleBinary:
return RunBinary(binary_path, options);
case ExecutionModelParam::kWithCentipedeBinary: {
RunOptions centipede_options;
centipede_options.env = options.env;
centipede_options.timeout = options.timeout;
std::vector<std::string> binary_args;
binary_args.push_back(std::string(binary_path));
for (const auto &[key, value] : options.fuzztest_flags) {
binary_args.push_back(CreateFuzzTestFlag(key, value));
}
for (const auto &[key, value] : options.flags) {
binary_args.push_back(absl::StrCat("--", key, "=", value));
}
centipede_options.flags = {
{"binary", absl::StrJoin(binary_args, " ")},
// Disable symbolization to more quickly get to fuzzing.
{"symbolizer_path", ""},
};
return RunBinary(CentipedePath(), centipede_options);
}
for (const auto &[key, value] : options.flags) {
binary_args.push_back(absl::StrCat("--", key, "=", value));
}
centipede_options.flags = {
{"binary", absl::StrJoin(binary_args, " ")},
// Disable symbolization to more quickly get to fuzzing.
{"symbolizer_path", ""},
};
return RunBinary(CentipedePath(), centipede_options);
}
}
FUZZTEST_INTERNAL_CHECK(false, "Unsupported execution model!\n");
FUZZTEST_INTERNAL_CHECK(false, "Unsupported execution model!\n");
}();
LOG(INFO) << "Run ends";
return results;
}

private:
Expand All @@ -150,19 +156,23 @@ TEST_P(UpdateCorpusDatabaseTest, RunsFuzzTests) {
}

TEST_P(UpdateCorpusDatabaseTest, UsesMultipleShardsForFuzzingAndDistillation) {
const auto &std_err = GetUpdateCorpusDatabaseStdErr();
EXPECT_THAT(
GetUpdateCorpusDatabaseStdErr(),
std_err,
AllOf(HasSubstr("[S0.0] begin-fuzz"), HasSubstr("[S1.0] begin-fuzz"),
HasSubstr("DISTILL[S.0]: Distilling to output shard 0"),
HasSubstr("DISTILL[S.1]: Distilling to output shard 1")));
HasSubstr("DISTILL[S.1]: Distilling to output shard 1")))
<< std_err;
}

TEST_P(UpdateCorpusDatabaseTest, FindsAllCrashes) {
const auto &std_err = GetUpdateCorpusDatabaseStdErr();
EXPECT_THAT(
GetUpdateCorpusDatabaseStdErr(),
std_err,
AllOf(ContainsRegex(R"re(Failure\s*: GoogleTest assertion failure)re"),
ContainsRegex(R"re(Failure\s*: heap-buffer-overflow)re"),
ContainsRegex(R"re(Failure\s*: stack-limit-exceeded)re")));
ContainsRegex(R"re(Failure\s*: stack-limit-exceeded)re")))
<< std_err;
}

TEST_P(UpdateCorpusDatabaseTest, ResumedFuzzTestRunsForRemainingTime) {
Expand Down
12 changes: 8 additions & 4 deletions e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ namespace {
volatile int force_write = 0;

// This test fails in two ways:
// 1. It fails with an assertion failure, e.g., when `v == {2025}`.
// 2. It fails with a heap buffer overflow, e.g., when `v == {4050}`.
// 1. It fails with an assertion failure, e.g., when `v == {1}`.
// 2. It fails with a heap buffer overflow, e.g., when `v == {2}`.
void FailsInTwoWays(const std::vector<int>& v) {
if (v.size() % 7 != 1) return;
ASSERT_NE(v[0], 2025);
if (v[0] == 2 * 2025) force_write = v.data()[v.size()];
// Compare A - B and 0 instead of A and B to not rely on auto-dictionary for
// flipping the branches. Otherwise due to the current auto-dictionary
// implementation sometimes the branches are not flipped evenly, causing test
// flakiness.
ASSERT_NE(v[0] % 3 - 1, 0);
if (v[0] % 3 - 2 == 0) force_write = v.data()[v.size()];
}
FUZZ_TEST(FuzzTest, FailsInTwoWays);

Expand Down
6 changes: 6 additions & 0 deletions fuzztest/internal/runtime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ static void HandleCrash(int signum, siginfo_t* info, void* ucontext) {
else
std::abort();
}
absl::Format(&signal_out_sink, "[!] HandleCrash called for signal %d (%s)\n",
signum, it->signame);
Runtime& runtime = Runtime::instance();
runtime.SetCrashTypeIfUnset(std::string(it->signame));
const bool has_old_handler = HasCustomHandler(it->action);
Expand All @@ -491,6 +493,9 @@ static void HandleCrash(int signum, siginfo_t* info, void* ucontext) {
// calling them.
if (IsSilenceTargetEnabled()) RestoreTargetStdoutAndStderr();
}
absl::Format(&signal_out_sink,
"[!] Finishing HandleCrash, has_old_handler=%d\n",
has_old_handler);
if (!has_old_handler) {
// Unblock the signal and invoke the default action if there is no old
// handler.
Expand All @@ -503,6 +508,7 @@ static void HandleCrash(int signum, siginfo_t* info, void* ucontext) {
sigaddset(&set, signum);
signal(signum, SIG_DFL);
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
absl::Format(&signal_out_sink, "[!]Raising %d\n");
raise(signum);
absl::Format(&signal_out_sink,
"[!] The default action of crashing signal %d did not crash - "
Expand Down

0 comments on commit 52f7557

Please sign in to comment.