diff --git a/puzzles/BUILD b/puzzles/BUILD index cde97fc..1bfd9d9 100644 --- a/puzzles/BUILD +++ b/puzzles/BUILD @@ -39,4 +39,5 @@ package(default_visibility = ["//visibility:public"]) "independent_compares", "autodictionary_stress", "paths", + "subprocess", ]] diff --git a/puzzles/subprocess.cc b/puzzles/subprocess.cc new file mode 100644 index 0000000..553d5c9 --- /dev/null +++ b/puzzles/subprocess.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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(arg), const_cast(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); +} diff --git a/runner.cc b/runner.cc index 67d5a3c..d7b38f9 100644 --- a/runner.cc +++ b/runner.cc @@ -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. @@ -466,12 +467,14 @@ static int ExecuteInputsFromShmem( // Copy from blob to data so that to not pass the shared memory further. std::vector 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; } @@ -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); } } @@ -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); +} diff --git a/runner_interface.h b/runner_interface.h index b5272f2..b24ff95 100644 --- a/runner_interface.h +++ b/runner_interface.h @@ -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_ diff --git a/shared_memory_blob_sequence.cc b/shared_memory_blob_sequence.cc index 4e89b74..c13992f 100644 --- a/shared_memory_blob_sequence.cc +++ b/shared_memory_blob_sequence.cc @@ -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) { @@ -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)"); @@ -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. diff --git a/shared_memory_blob_sequence.h b/shared_memory_blob_sequence.h index a7b8bb6..535d29a 100644 --- a/shared_memory_blob_sequence.h +++ b/shared_memory_blob_sequence.h @@ -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(); diff --git a/shared_memory_blob_sequence_test.cc b/shared_memory_blob_sequence_test.cc index ae1f2c6..9845030 100644 --- a/shared_memory_blob_sequence_test.cc +++ b/shared_memory_blob_sequence_test.cc @@ -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{i}); + } + ASSERT_FALSE(blobseq.Read().IsValid()); + blobseq.ReleaseSharedMemory(); + blobseq.Reset(); + } +} + } // namespace centipede