diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index 17383e8f..84215a39 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc @@ -963,14 +963,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, GetsSeedInputs) { CentipedeDefaultCallbacks callbacks(env, stop_condition); std::vector seeds; - EXPECT_EQ(callbacks.GetSeeds(10, seeds), 10); - EXPECT_THAT(seeds, testing::ContainerEq(std::vector{ - {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}})); - EXPECT_EQ(callbacks.GetSeeds(5, seeds), 10); - EXPECT_THAT(seeds, testing::ContainerEq( - std::vector{{0}, {1}, {2}, {3}, {4}})); - EXPECT_EQ(callbacks.GetSeeds(100, seeds), 10); - EXPECT_THAT(seeds, testing::ContainerEq(std::vector{ + callbacks.GetSeeds(10, seeds); + EXPECT_THAT(seeds, testing::IsSupersetOf(std::vector{ {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}})); } @@ -1400,8 +1394,9 @@ TEST_F(CentipedeWithTemporaryLocalDir, EngineWorksInWorkerMode) { env.test_name = "some_test"; env.populate_binary_info = false; env.fork_server = false; - env.persistent_mode = false; + env.persistent_mode = true; env.exit_on_crash = true; + env.stop_at = absl::Now() + absl::Seconds(10); fuzztest::internal::DefaultCallbacksFactory< fuzztest::internal::CentipedeDefaultCallbacks> callbacks; diff --git a/centipede/engine_worker.cc b/centipede/engine_worker.cc index 01030d1e..3683e5bf 100644 --- a/centipede/engine_worker.cc +++ b/centipede/engine_worker.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. #include +#include +#include #include #include @@ -105,7 +107,7 @@ struct WorkerFlags { }; // The first call of this function must be outside of signal handlers since it -// allocates memory (enforced by `GetWorkerFlagsEarly`). After that it would be +// allocates memory (enforced by `WorkerInitEarly`). After that it would be // signal-safe. // // The worker flags format is `:(NAME=VALUE|SWITCH:)+`. `GetWorkerFlags` @@ -137,10 +139,6 @@ const WorkerFlags& GetWorkerFlags() { return worker_flags; } -__attribute__((constructor(200))) void GetWorkerFlagsEarly() { - (void)GetWorkerFlags(); -} - // `header` should be in the form of `FLAG_NAME=`. // // Extracts "value" as a null-terminated string from "\0FLAG_NAME=value\0" in @@ -237,6 +235,9 @@ constexpr std::string_view kWorkerInputsBlobSequencePathFlagHeader = "arg1="; // TODO: Use better flag names when standardizing the protocol. constexpr std::string_view kWorkerOutputsBlobSequencePathFlagHeader = "arg2="; // TODO: Use better flag names when standardizing the protocol. +constexpr std::string_view kWorkerPersistentModeSocketPathFlagHeader = + "persistent_mode_socket="; // TODO: Use better flag names when + // standardizing the protocol. struct WorkerState { std::atomic has_failure_output = false; @@ -323,6 +324,64 @@ inline std::string_view ToStringView(const std::vector& bytes) { return {reinterpret_cast(bytes.data()), bytes.size()}; } +// Zero initialized. +static int persistent_mode_socket; + +__attribute__((constructor(200))) void WorkerInitEarly() { + const char* persistent_mode_socket_path = + GetWorkerFlag(kWorkerPersistentModeSocketPathFlagHeader); + if (persistent_mode_socket_path == nullptr) return; + persistent_mode_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (persistent_mode_socket < 0) { + WorkerLog( + "Failed to create persistent mode socket - not running persistent " + "mode.", + LogLnSync{}); + return; + } + + struct sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + const size_t socket_path_len = strlen(persistent_mode_socket_path); + WorkerCheck( + socket_path_len < sizeof(addr.sun_path), + "persistent mode socket path string must be fit in sockaddr_un.sun_path"); + std::memcpy(addr.sun_path, persistent_mode_socket_path, socket_path_len); + + int connect_ret = 0; + do { + connect_ret = + connect(persistent_mode_socket, (struct sockaddr*)&addr, sizeof(addr)); + } while (connect_ret == -1 && errno == EINTR); + if (connect_ret == -1) { + WorkerLog("Failed to connect the persistent mode socket to ", + persistent_mode_socket_path, LogLnSync{}); + (void)close(persistent_mode_socket); + persistent_mode_socket = -1; + } + + int flags = fcntl(persistent_mode_socket, F_GETFD); + if (flags == -1) { + WorkerLog( + "fcntl(F_GETFD) failed on the persistent mode socket - exiting " + "persistent mode", + LogLnSync{}); + (void)close(persistent_mode_socket); + persistent_mode_socket = -1; + } + flags |= FD_CLOEXEC; + if (fcntl(persistent_mode_socket, F_SETFD, flags) == -1) { + WorkerLog( + "fcntl(F_SETFD) failed on the persistent mode socket - exiting " + "persistent mode", + LogLnSync{}); + (void)close(persistent_mode_socket); + persistent_mode_socket = -1; + } + WorkerLog("Persistent mode: connected to ", persistent_mode_socket_path, + LogLnSync{}); +} + BlobSequence* GetInputsBlobSequence() { static auto result = []() -> BlobSequence* { if (!HasWorkerSwitchFlag("shmem")) { @@ -695,6 +754,70 @@ const char* FuzzTestWorkerGetTestName() { return test_name; } +void HandlePersistentMode(const FuzzTestAdapter& adapter) { + auto* inputs_blobseq = GetInputsBlobSequence(); + auto* outputs_blobseq = GetOutputsBlobSequence(); + bool first = true; + while (true) { + PersistentModeRequest req; + if (!ReadAll(persistent_mode_socket, reinterpret_cast(&req), 1)) { + WorkerLog("Failed to read request from persistent mode socket: ", + LogErrNo{}, LogLnSync{}); + return; + } + if (first) { + first = false; + WorkerLog("FuzzTest engine worker enter persistent mode", LogLnSync{}); + } else { + // Reset stdout/stderr. + for (int fd = 1; fd <= 2; fd++) { + lseek(fd, 0, SEEK_SET); + // NOTE: Allow ftruncate() to fail by ignoring its return; that's okay + // to happen when the stdout/stderr are not redirected to a file. + (void)ftruncate(fd, 0); + } + WorkerLog( + "FuzzTest engine worker (", + req == PersistentModeRequest::kExit ? "exiting persistent mode" + : "persistent mode batch", + "); flags: ", + GetWorkerFlags().present + ? std::string_view{GetWorkerFlags().str, GetWorkerFlags().len} + : "", + LogLnSync{}); + } + if (req == PersistentModeRequest::kExit) break; + WorkerCheck(req == PersistentModeRequest::kRunBatch, + "Unknown persistent mode request"); + + inputs_blobseq->Reset(); + outputs_blobseq->Reset(); + + // Read the first blob. It indicates what further actions to take. + auto request_type_blob = inputs_blobseq->Read(); + if (IsMutationRequest(request_type_blob)) { + inputs_blobseq->Reset(); + WorkerDoMutate(adapter); + } else if (IsExecutionRequest(request_type_blob)) { + inputs_blobseq->Reset(); + WorkerDoExecute(adapter); + } else { + WorkerCheck(false, "Unknown shmem request"); + } + + const int result = + GetWorkerState().has_finding.load(std::memory_order_relaxed) + ? EXIT_FAILURE + : EXIT_SUCCESS; + if (!WriteAll(persistent_mode_socket, + reinterpret_cast(&result), sizeof(result))) { + WorkerLog("Failed to write response to the persistent mode socket: ", + LogErrNo{}, LogLnSync{}); + return; + } + } +} + FuzzTestWorkerStatus WorkerRun(const FuzzTestAdapterManager& manager) { const auto& flags = GetWorkerFlags(); WorkerCheck(flags.present, "worker flags must present"); @@ -764,7 +887,9 @@ FuzzTestWorkerStatus WorkerRun(const FuzzTestAdapterManager& manager) { WorkerCheck(adapter.FreeInput != nullptr, "FreeInput must be defined"); WorkerCheck(adapter.FreeCtx != nullptr, "FreeCtx must be defined"); - if (action == WorkerAction::kTestGetSeeds) { + if (persistent_mode_socket > 0) { + HandlePersistentMode(adapter); + } else if (action == WorkerAction::kTestGetSeeds) { WorkerDoGetSeeds(adapter); } else if (action == WorkerAction::kTestMutate) { WorkerDoMutate(adapter); diff --git a/centipede/runner.cc b/centipede/runner.cc index e085b54e..441251b5 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -322,7 +322,11 @@ static void PrepareCoverage(bool full_clear) { PrepareSancov(full_clear); } -void RunnerCallbacks::GetSeeds(std::function seed_callback) { +void RunnerCallbacks::GetPresetSeedInputs( + std::function seed_callback) {} + +void RunnerCallbacks::GetRandomSeedInput( + std::function seed_callback) { seed_callback({0}); } @@ -529,7 +533,7 @@ static int ExecuteInputsFromShmem(BlobSequence &inputs_blobseq, // Dumps seed inputs to `output_dir`. Also see `GetSeedsViaExternalBinary()`. static void DumpSeedsToDir(RunnerCallbacks &callbacks, const char *output_dir) { size_t seed_index = 0; - callbacks.GetSeeds([&](ByteSpan seed) { + auto dump_seed_callback = [&](ByteSpan seed) { // Cap seed index within 9 digits. If this was triggered, the dumping would // take forever.. if (seed_index >= 1000000000) return; @@ -545,7 +549,14 @@ static void DumpSeedsToDir(RunnerCallbacks &callbacks, const char *output_dir) { "wrong number of bytes written for cf table"); fclose(output_file); ++seed_index; - }); + }; + callbacks.GetPresetSeedInputs(dump_seed_callback); + while (seed_index < 32) { + const auto prev_index = seed_index; + callbacks.GetRandomSeedInput(dump_seed_callback); + RunnerCheck(seed_index == prev_index + 1, + "GetRandomSeedInput must provide at least one input"); + } } // Dumps serialized target config to `output_file_path`. Also see diff --git a/centipede/runner_interface.h b/centipede/runner_interface.h index c622daf2..28373a56 100644 --- a/centipede/runner_interface.h +++ b/centipede/runner_interface.h @@ -141,9 +141,12 @@ class RunnerCallbacks { // Attempts to execute the test logic using `input`, and returns false if the // input should be ignored from the corpus, true otherwise. virtual bool Execute(ByteSpan input) = 0; - // Generates seed inputs by calling `seed_callback` for each input. + // Provide the preset seed inputs by calling `seed_callback` on each of them. + // The default implementation provides no inputs. + virtual void GetPresetSeedInputs(std::function seed_callback); + // Generates a random seed input by calling `seed_callback` on it. // The default implementation generates a single-byte input {0}. - virtual void GetSeeds(std::function seed_callback); + virtual void GetRandomSeedInput(std::function seed_callback); // Returns the serialized configuration from the test target. The default // implementation returns the empty string. virtual std::string GetSerializedTargetConfig(); diff --git a/centipede/testing/seeded_fuzz_target.cc b/centipede/testing/seeded_fuzz_target.cc index 0530109d..645b0734 100644 --- a/centipede/testing/seeded_fuzz_target.cc +++ b/centipede/testing/seeded_fuzz_target.cc @@ -29,7 +29,8 @@ class SeededRunnerCallbacks : public fuzztest::internal::RunnerCallbacks { return true; } - void GetSeeds(std::function seed_callback) override { + void GetPresetSeedInputs( + std::function seed_callback) override { constexpr size_t kNumAvailSeeds = 10; for (size_t i = 0; i < kNumAvailSeeds; ++i) seed_callback({static_cast(i)}); diff --git a/centipede/testing/test_binary_for_engine_testing.cc b/centipede/testing/test_binary_for_engine_testing.cc index 75c1a42a..071f2232 100644 --- a/centipede/testing/test_binary_for_engine_testing.cc +++ b/centipede/testing/test_binary_for_engine_testing.cc @@ -202,10 +202,10 @@ int main(int argc, char** argv) { return worker_status == kFuzzTestWorkerSuccess ? EXIT_SUCCESS : EXIT_FAILURE; } - return ControllerRun(&manager, {absl::StrCat("--binary=", argv[0]), - "--test_name=some_test", - "--populate_binary_info=0", "--fork_server=0", - "--persistent_mode=0", "--exit_on_crash"}) == + return ControllerRun(&manager, + {absl::StrCat("--binary=", argv[0]), + "--test_name=some_test", "--populate_binary_info=0", + "--fork_server=0", "--exit_on_crash"}) == kFuzzTestControllerSuccess ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index 7c23407e..448a37c3 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc @@ -531,15 +531,10 @@ class CentipedeAdaptorRunnerCallbacks return false; } - void GetSeeds(std::function seed_callback) - override { + void GetPresetSeedInputs(std::function + seed_callback) override { std::vector seeds = fuzzer_impl_.fixture_driver_->GetSeeds(); - constexpr int kInitialValuesInSeeds = 32; - for (int i = 0; i < kInitialValuesInSeeds; ++i) { - seeds.push_back(fuzzer_impl_.params_domain_.Init(prng_)); - } - absl::c_shuffle(seeds, prng_); for (const auto& seed : seeds) { const auto seed_serialized = SerializeIRObject(fuzzer_impl_.params_domain_.SerializeCorpus(seed)); @@ -547,6 +542,14 @@ class CentipedeAdaptorRunnerCallbacks } } + void GetRandomSeedInput(std::function + seed_callback) override { + auto input = fuzzer_impl_.params_domain_.Init(prng_); + const auto serialized = + SerializeIRObject(fuzzer_impl_.params_domain_.SerializeCorpus(input)); + seed_callback(fuzztest::internal::AsByteSpan(serialized)); + } + std::string GetSerializedTargetConfig() override { return configuration_.Serialize(); }