Skip to content

Commit

Permalink
Ensure that the Centipede runner writes the failure atomically and on…
Browse files Browse the repository at this point in the history
…ly once.

The atomicity is needed because the function `WriteFailureDescription()` may
be called from multiple threads: from the main thread, e.g. for a GoogleTest
assertion failure, and from the watchdog thread, e.g. for an OOM.

The function should write the failure only once because for failures like stack
overflow, it gets called twice: the first time for the stack overflow itself,
and the second time from the SIGABRT handler handling the abort that follows the
detected stack overflow.

PiperOrigin-RevId: 695434225
  • Loading branch information
fniksic authored and copybara-github committed Nov 11, 2024
1 parent 63b0966 commit f0969ce
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 16 deletions.
34 changes: 19 additions & 15 deletions centipede/runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 11 additions & 1 deletion e2e_tests/corpus_database_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
namespace fuzztest::internal {
namespace {

using ::testing::ContainsRegex;
using ::testing::HasSubstr;

class UpdateCorpusDatabaseTest : public testing::Test {
Expand Down Expand Up @@ -88,7 +89,8 @@ absl::NoDestructor<std::string> 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) {
Expand All @@ -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
18 changes: 18 additions & 0 deletions e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstddef>

#include "gtest/gtest.h"
#include "./fuzztest/fuzztest.h"

Expand All @@ -29,4 +31,20 @@ void FailsInTwoWays(const std::vector<int>& 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

0 comments on commit f0969ce

Please sign in to comment.