diff --git a/centipede/runner.cc b/centipede/runner.cc index 83b87333..8b8065d9 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -104,21 +104,25 @@ __thread ThreadLocalRunnerState tls; static void WriteFailureDescription(const char *description) { // TODO(b/264715830): Remove I/O error logging once the bug is fixed? if (state.failure_description_path == nullptr) return; - FILE *f = fopen(state.failure_description_path, "w"); - if (f == nullptr) { - perror("FAILURE: fopen()"); - return; - } - const auto len = strlen(description); - if (fwrite(description, 1, len, f) != len) { - perror("FAILURE: fwrite()"); - } - if (fflush(f) != 0) { - perror("FAILURE: fflush()"); - } - if (fclose(f) != 0) { - perror("FAILURE: fclose()"); - } + // Make sure that the write is atomic and only happens once. + [[maybe_unused]] static int write_once = [=] { + FILE *f = fopen(state.failure_description_path, "w"); + if (f == nullptr) { + perror("FAILURE: fopen()"); + return 0; + } + const auto len = strlen(description); + if (fwrite(description, 1, len, f) != len) { + perror("FAILURE: fwrite()"); + } + if (fflush(f) != 0) { + perror("FAILURE: fflush()"); + } + if (fclose(f) != 0) { + perror("FAILURE: fclose()"); + } + return 0; + }(); } void ThreadLocalRunnerState::TraceMemCmp(uintptr_t caller_pc, const uint8_t *s1, diff --git a/e2e_tests/corpus_database_test.cc b/e2e_tests/corpus_database_test.cc index ed01e875..0e78ae60 100644 --- a/e2e_tests/corpus_database_test.cc +++ b/e2e_tests/corpus_database_test.cc @@ -27,6 +27,7 @@ namespace fuzztest::internal { namespace { +using ::testing::ContainsRegex; using ::testing::HasSubstr; class UpdateCorpusDatabaseTest : public testing::Test { @@ -88,7 +89,8 @@ absl::NoDestructor UpdateCorpusDatabaseTest::centipede_std_err_{}; TEST_F(UpdateCorpusDatabaseTest, RunsFuzzTests) { EXPECT_THAT(GetCentipedeStdErr(), - HasSubstr("Fuzzing FuzzTest.FailsInTwoWays")); + AllOf(HasSubstr("Fuzzing FuzzTest.FailsInTwoWays"), + HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow"))); } TEST_F(UpdateCorpusDatabaseTest, UsesMultipleShardsForFuzzingAndDistillation) { @@ -99,5 +101,13 @@ TEST_F(UpdateCorpusDatabaseTest, UsesMultipleShardsForFuzzingAndDistillation) { HasSubstr("DISTILL[S.1]: Distilling to output shard 1"))); } +TEST_F(UpdateCorpusDatabaseTest, FindsAllCrashes) { + EXPECT_THAT( + GetCentipedeStdErr(), + 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"))); +} + } // namespace } // namespace fuzztest::internal diff --git a/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc b/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc index a2e1359e..8d97055a 100644 --- a/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc +++ b/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "gtest/gtest.h" #include "./fuzztest/fuzztest.h" @@ -29,4 +31,20 @@ void FailsInTwoWays(const std::vector& v) { } FUZZ_TEST(FuzzTest, FailsInTwoWays); +int ReachStackOverflow(int n) { + // Use volatile to prevent the compiler from inlining the recursion. + volatile auto f = ReachStackOverflow; + return n > 0 ? 1 + f(n - 1) : 0; +} + +// Stack frame consists of at least one word. +constexpr size_t kStackFrameSizeLowerBound = sizeof(void*); +// Default stack limit is 128 KiB. +constexpr int kDepthToReachStackOverflow = + 128 * 1024 / kStackFrameSizeLowerBound; + +void FailsWithStackOverflow(int n) { ReachStackOverflow(n); } +FUZZ_TEST(FuzzTest, FailsWithStackOverflow) + .WithDomains(fuzztest::Just(kDepthToReachStackOverflow)); + } // namespace