No public description PiperOrigin-RevId: 947185670
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index 17383e8..c485781 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc
@@ -86,7 +86,7 @@ // i-th element is the number of bytes with the value 'i' in the input. // `counters` is converted to FeatureVec and added to // `batch_result.results()`. - for (auto &input : inputs) { + for (auto& input : inputs) { ByteArray counters(256); for (uint8_t byte : input) { counters[byte]++; @@ -496,7 +496,7 @@ // Must contain normal mutants, but not the ones from crossover. const auto mutant_data = GetDataFromMutants(result.mutants()); EXPECT_THAT(mutant_data, IsSupersetOf(some_of_expected_mutants)); - for (const auto &crossover_mutant : expected_crossover_mutants) { + for (const auto& crossover_mutant : expected_crossover_mutants) { EXPECT_THAT(mutant_data, Not(Contains(crossover_mutant))); } } @@ -525,7 +525,7 @@ std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants) override { std::vector<Mutant> mutants(num_mutants); - for (auto &mutant : mutants) { + for (auto& mutant : mutants) { mutant.data.resize(1); mutant.data[0] = ++number_of_mutations_; mutant.origin = Mutant::kOriginNone; @@ -611,7 +611,7 @@ // Sets the inputs to one of 3 pre-defined values. std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants) override { - for (auto &input : inputs) { + for (auto& input : inputs) { if (!seed_inputs_.contains(input.data)) { observed_inputs_.insert(input.data); } @@ -628,8 +628,8 @@ // Returns one of 3 pre-defined values, that trigger different code paths in // the test target. static ByteArray GetMutant(size_t idx) { - const char *mutants[3] = {"func1", "func2-A", "foo"}; - const char *mutant = mutants[idx % 3]; + const char* mutants[3] = {"func1", "func2-A", "foo"}; + const char* mutant = mutants[idx % 3]; return {mutant, mutant + strlen(mutant)}; } @@ -646,7 +646,7 @@ // Runs a short fuzzing session with the provided `function_filter`. // Returns a sorted array of observed inputs. static std::vector<ByteArray> RunWithFunctionFilter( - std::string_view function_filter, const TempDir &tmp_dir) { + std::string_view function_filter, const TempDir& tmp_dir) { Environment env; env.workdir = tmp_dir.path(); env.seed = 1; // make the runs predictable. @@ -717,9 +717,9 @@ bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs, BatchResult& batch_result) override { bool res = true; - for (const auto &input : inputs) { + for (const auto& input : inputs) { if (input.size() != 1) continue; - for (const Crash &crash : crashes_) { + for (const Crash& crash : crashes_) { if (binary == crash.binary && input[0] == crash.input) { batch_result.exit_code() = EXIT_FAILURE; batch_result.failure_description() = crash.description; @@ -736,7 +736,7 @@ std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants) override { std::vector<Mutant> mutants(num_mutants); - for (auto &mutant : mutants) { + for (auto& mutant : mutants) { mutant.data.resize(1); mutant.data[0] = ++number_of_mutations_; mutant.origin = Mutant::kOriginNone; @@ -754,20 +754,20 @@ std::string file; std::string contents; - bool operator==(const FileAndContents &other) const { + bool operator==(const FileAndContents& other) const { return file == other.file && contents == other.contents; } template <typename Sink> - friend void AbslStringify(Sink &sink, const FileAndContents &f) { + friend void AbslStringify(Sink& sink, const FileAndContents& f) { absl::Format(&sink, "FileAndContents{%s, \"%s\"}", f.file, f.contents); } }; MATCHER_P(HasFilesWithContents, expected_files_and_contents, "") { - const std::string &dir_path = arg; + const std::string& dir_path = arg; std::vector<FileAndContents> files_and_contents; - for (const auto &dir_ent : std::filesystem::directory_iterator(dir_path)) { + for (const auto& dir_ent : std::filesystem::directory_iterator(dir_path)) { auto file_and_contents = FileAndContents{dir_ent.path().filename()}; ReadFromLocalFile(dir_ent.path().c_str(), file_and_contents.contents); files_and_contents.push_back(std::move(file_and_contents)); @@ -843,7 +843,7 @@ if (!first_pass_) { num_inputs_triaged_ += inputs.size(); } - for (const auto &input : inputs) { + for (const auto& input : inputs) { FUZZTEST_CHECK_EQ(input.size(), 1); // By construction in `Mutate()`. // The contents of each mutant is its sequential number. if (input[0] == crashing_input_idx_) { @@ -933,7 +933,7 @@ absl::StrCat("crashing_batch-", crashing_input_hash); EXPECT_TRUE(std::filesystem::exists(crashes_dir_path)) << crashes_dir_path; std::vector<std::string> found_crash_file_names; - for (auto const &dir_ent : + for (auto const& dir_ent : std::filesystem::directory_iterator(crashes_dir_path)) { found_crash_file_names.push_back(dir_ent.path().filename()); } @@ -1400,8 +1400,9 @@ 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 01030d1..bf19708 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 <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> #include <unistd.h> #include <algorithm> @@ -105,7 +107,7 @@ }; // 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 @@ 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 @@ "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<bool> has_failure_output = false; @@ -244,6 +245,11 @@ std::atomic<bool> in_adapter_execute = false; std::atomic<bool> has_finding = false; std::atomic<bool> saved_binary_id = false; + + void ResetForPersistentMode() { + has_failure_output.store(false, std::memory_order_relaxed); + has_finding.store(false, std::memory_order_relaxed); + } }; WorkerState& GetWorkerState() { @@ -323,6 +329,67 @@ return {reinterpret_cast<const char*>(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; + return; + } + + 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; + return; + } + 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; + return; + } + WorkerLog("Persistent mode: connected to ", persistent_mode_socket_path, + LogLnSync{}); +} + BlobSequence* GetInputsBlobSequence() { static auto result = []() -> BlobSequence* { if (!HasWorkerSwitchFlag("shmem")) { @@ -695,6 +762,72 @@ 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<char*>(&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(); + + GetWorkerState().ResetForPersistentMode(); + + // 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<const char*>(&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 +897,9 @@ 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/testing/test_binary_for_engine_testing.cc b/centipede/testing/test_binary_for_engine_testing.cc index 75c1a42..071f223 100644 --- a/centipede/testing/test_binary_for_engine_testing.cc +++ b/centipede/testing/test_binary_for_engine_testing.cc
@@ -202,10 +202,10 @@ 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;