Skip to content
This repository has been archived by the owner on Oct 11, 2023. It is now read-only.

#Centipede support collecting coverage from a subprocess #511

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions puzzles/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ package(default_visibility = ["//visibility:public"])
"independent_compares",
"autodictionary_stress",
"paths",
"subprocess",
]]
95 changes: 95 additions & 0 deletions puzzles/subprocess.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2023 The Centipede Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Centipede puzzle: the fuzz function starts a subprocess which is
// the actual fuzz function. The input is passed to the subprocess
// and coverage is collected from the subprocess.
//
// RUN: Run --timeout_per_input=5 --batch_size=10 && SolutionIs FUZZ

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
int pid = fork();
if (pid < 0) {
perror("fork");
abort();
}
if (pid == 0) {
int fd = memfd_create("centipede-test", 0);
if (fd < 0) {
perror("memfd_create");
abort();
}
if (write(fd, data, size) != size) {
perror("write");
abort();
}
if (lseek(fd, 0, SEEK_SET) < 0) {
perror("fseek");
abort();
}
if (dup2(fd, STDIN_FILENO) < 0) {
perror("dup2");
abort();
}
const char* arg[] = {"exe", nullptr};
const char* env[] = {"CENTIPEDE_TEST_CHILD=1", nullptr, nullptr};
for (char** e = environ; *e; e++) {
if (strstr(*e, "CENTIPEDE_RUNNER_FLAGS=")) env[1] = *e;
}
execve("/proc/self/exe", const_cast<char**>(arg), const_cast<char**>(env));
abort();
}
int status;
while (waitpid(pid, &status, __WALL) != pid) {
}
if ((WIFEXITED(status) && WEXITSTATUS(status)) || WIFSIGNALED(status))
abort();
return 0;
}

extern "C" {
int CentipedeManualCoverage() { return 1; }
void CentipedeCollectCoverage(int exit_status);
__attribute__((weak)) void CentipedeIsPresent();
__attribute__((weak)) void __libfuzzer_is_present();
}

static __attribute__((constructor)) void ctor() {
if (!getenv("CENTIPEDE_TEST_CHILD")) return;
if (!CentipedeIsPresent || !__libfuzzer_is_present) {
fprintf(stderr, "Centipede is not present\n");
abort();
}
char data[16];
ssize_t size = read(STDIN_FILENO, data, sizeof(data));
if (size < 0) {
perror("read");
abort();
}
if (size == 4 && data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' &&
data[3] == 'Z') {
abort();
}
CentipedeCollectCoverage(0);
_exit(0);
}
36 changes: 27 additions & 9 deletions runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ static int ExecuteInputsFromShmem(
return EXIT_FAILURE;
if (!execution_request::IsNumInputs(inputs_blobseq.Read(), num_inputs))
return EXIT_FAILURE;
const bool collect_coverage = !CentipedeManualCoverage();
for (size_t i = 0; i < num_inputs; i++) {
auto blob = inputs_blobseq.Read();
// TODO(kcc): distinguish bad input from end of stream.
Expand All @@ -466,12 +467,14 @@ static int ExecuteInputsFromShmem(
// Copy from blob to data so that to not pass the shared memory further.
std::vector<uint8_t> data(blob.data, blob.data + size);

// Starting execution of one more input.
if (!StartSendingOutputsToEngine(outputs_blobseq)) break;
if (collect_coverage && !StartSendingOutputsToEngine(outputs_blobseq))
break;

// Starting execution of one more input.
RunOneInput(data.data(), data.size(), test_one_input_cb);

if (!FinishSendingOutputsToEngine(outputs_blobseq)) break;
if (collect_coverage && !FinishSendingOutputsToEngine(outputs_blobseq))
break;
}
return EXIT_SUCCESS;
}
Expand Down Expand Up @@ -721,16 +724,22 @@ GlobalRunnerState::GlobalRunnerState() {
MaybePopluateReversePCTable();
}

static void CollectCoverage(int exit_status) {
PostProcessCoverage(exit_status);
centipede::SharedMemoryBlobSequence outputs_blobseq(state.arg2);
outputs_blobseq.RewindToEnd();
StartSendingOutputsToEngine(outputs_blobseq);
FinishSendingOutputsToEngine(outputs_blobseq);
}

GlobalRunnerState::~GlobalRunnerState() {
// The process is winding down, but CentipedeRunnerMain did not run.
// This means, the binary is standalone with its own main(), and we need to
// report the coverage now.
if (!state.centipede_runner_main_executed && state.HasFlag(":shmem:")) {
int exit_status = EXIT_SUCCESS; // TODO(kcc): do we know our exit status?
PostProcessCoverage(exit_status);
centipede::SharedMemoryBlobSequence outputs_blobseq(state.arg2);
StartSendingOutputsToEngine(outputs_blobseq);
FinishSendingOutputsToEngine(outputs_blobseq);
if (!state.centipede_runner_main_executed && state.HasFlag(":shmem:") &&
!CentipedeManualCoverage()) {
// TODO(kcc): do we know our exit status?
CollectCoverage(EXIT_SUCCESS);
}
}

Expand Down Expand Up @@ -804,3 +813,12 @@ extern "C" int LLVMFuzzerRunDriver(

extern "C" __attribute__((used)) void CentipedeIsPresent() {}
extern "C" __attribute__((used)) void __libfuzzer_is_present() {}

extern "C" int CentipedeManualCoverage() { return 0; }

extern "C" void CentipedeCollectCoverage(int exit_status) {
centipede::PrintErrorAndExitIf(
!CentipedeManualCoverage() || !centipede::state.arg2,
"invalid call to CentipedeCollectCoverage");
centipede::CollectCoverage(exit_status);
}
6 changes: 6 additions & 0 deletions runner_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,10 @@ extern "C" int LLVMFuzzerRunDriver(
extern "C" __attribute__((weak)) void CentipedeIsPresent();
extern "C" __attribute__((weak)) void __libfuzzer_is_present();

// Experimental interface that allows to collect coverage from subprocesses.
// To use it a runner needs to define CentipedeManualCoverage function that
// returns 1 and call CentipedeCollectCoverage from the target subprocesses.
extern "C" __attribute__((weak)) int CentipedeManualCoverage();
extern "C" void CentipedeCollectCoverage(int exit_status);

#endif // THIRD_PARTY_CENTIPEDE_RUNNER_INTERFACE_H_
13 changes: 12 additions & 1 deletion shared_memory_blob_sequence.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ static void ErrorOnFailure(bool condition, const char *text) {
abort();
}

constexpr size_t kInvalidBlobSize =
sizeof(SharedMemoryBlobSequence::Blob::size) +
sizeof(SharedMemoryBlobSequence::Blob::tag);

SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char *name,
size_t size)
: size_(size) {
Expand Down Expand Up @@ -74,6 +78,13 @@ void SharedMemoryBlobSequence::Reset() {
had_writes_after_reset_ = false;
}

void SharedMemoryBlobSequence::RewindToEnd() {
while (Read().IsValid()) {
}
if (offset_ >= kInvalidBlobSize) offset_ -= kInvalidBlobSize;
had_reads_after_reset_ = false;
}

void SharedMemoryBlobSequence::ReleaseSharedMemory() {
// Setting size to 0 releases the memory to OS.
ErrorOnFailure(ftruncate(fd_, 0) != 0, "ftruncate(0) failed)");
Expand Down Expand Up @@ -103,7 +114,7 @@ bool SharedMemoryBlobSequence::Write(Blob blob) {
// Write data.
memcpy(data_ + offset_, blob.data, blob.size);
offset_ += blob.size;
if (offset_ + sizeof(blob.size) + sizeof(blob.tag) <= size_) {
if (offset_ + kInvalidBlobSize <= size_) {
// Write zero tag/size to data_+offset_ but don't change the offset.
// This is required to overwrite any stale bits in data_.
Blob invalid_blob; // invalid.
Expand Down
3 changes: 3 additions & 0 deletions shared_memory_blob_sequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ class SharedMemoryBlobSequence {
// Does not affect the contents of the shared memory.
void Reset();

// Rewinds the current position to skip all already written blobs.
void RewindToEnd();

// Releases shared memory used by `this`.
void ReleaseSharedMemory();

Expand Down
24 changes: 24 additions & 0 deletions shared_memory_blob_sequence_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,28 @@ TEST(SharedMemoryBlobSequence, ReleaseSharedMemory) {
EXPECT_GT(blobseq.NumBytesUsed(), 5);
}

// Test RewindToEnd method.
TEST(SharedMemoryBlobSequence, RewindToEnd) {
SharedMemoryBlobSequence blobseq(ShmemName().c_str(), 1 << 20);
for (int iter = 0; iter < 3; iter++) {
printf("iter %d\n", iter);
const uint8_t kBlobs = 10;
for (uint8_t i = 0; i < kBlobs; i++) {
SharedMemoryBlobSequence tempseq(ShmemName().c_str());
tempseq.RewindToEnd();
EXPECT_TRUE(tempseq.Write(Blob({i}, i + 1)));
}
for (uint8_t i = 0; i < kBlobs; i++) {
printf("blob %d\n", i);
auto blob = blobseq.Read();
ASSERT_TRUE(blob.IsValid());
ASSERT_EQ(blob.tag, i + 1);
ASSERT_EQ(Vec(blob), std::vector<uint8_t>{i});
}
ASSERT_FALSE(blobseq.Read().IsValid());
blobseq.ReleaseSharedMemory();
blobseq.Reset();
}
}

} // namespace centipede