| // Copyright 2022 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. |
| |
| // Fuzz target runner (engine) for Centipede. |
| // Reads the input files and feeds their contents to |
| // the fuzz target (RunnerCallbacks::Execute), then dumps the coverage data. |
| // If the input path is "/path/to/foo", |
| // the coverage features are dumped to "/path/to/foo-features" |
| // |
| // WARNING: please avoid any C++ libraries here, such as Absl and (most of) STL, |
| // in order to avoid creating new coverage edges in the binary. |
| #include "./centipede/runner.h" |
| |
| #include <fcntl.h> |
| #include <pthread.h> // NOLINT: use pthread to avoid extra dependencies. |
| #include <sched.h> |
| #include <sys/resource.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <atomic> |
| #include <cerrno> |
| #include <cinttypes> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <ctime> |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/nullability.h" |
| #include "absl/base/optimization.h" |
| #include "absl/types/span.h" |
| #include "./centipede/byte_array_mutator.h" |
| #include "./centipede/engine_abi.h" |
| #include "./centipede/engine_worker_abi.h" |
| #include "./centipede/execution_metadata.h" |
| #include "./centipede/feature.h" |
| #include "./centipede/mutation_data.h" |
| #include "./centipede/runner_interface.h" |
| #include "./centipede/runner_request.h" |
| #include "./centipede/runner_result.h" |
| #include "./centipede/runner_utils.h" |
| #include "./centipede/sancov_runtime.h" |
| #include "./centipede/sancov_state.h" |
| #include "./centipede/shared_memory_blob_sequence.h" |
| #include "./common/defs.h" |
| |
| namespace fuzztest::internal { |
| |
| ExplicitLifetime<GlobalRunnerState> state; |
| |
| namespace { |
| |
| struct GlobalRunnerStateManager { |
| GlobalRunnerStateManager() { state.Construct(); } |
| |
| ~GlobalRunnerStateManager() { state->OnTermination(); } |
| }; |
| |
| GlobalRunnerStateManager state_manager __attribute__((init_priority(200))); |
| |
| class SpinlockGuard { |
| public: |
| SpinlockGuard(const SpinlockGuard&) = delete; |
| SpinlockGuard& operator=(const SpinlockGuard&) = delete; |
| SpinlockGuard(SpinlockGuard&& other) : lock_(other.lock_) { |
| other.lock_ = nullptr; |
| } |
| SpinlockGuard& operator=(SpinlockGuard&& other) = delete; |
| |
| static SpinlockGuard Acquire(std::atomic<bool>& lock) { |
| while (lock.exchange(true, std::memory_order_acquire)) { |
| sched_yield(); |
| } |
| return SpinlockGuard{&lock}; |
| } |
| |
| static SpinlockGuard TryAcquire(std::atomic<bool>& lock) { |
| if (lock.exchange(true, std::memory_order_acquire)) { |
| return SpinlockGuard{nullptr}; |
| } |
| return SpinlockGuard{&lock}; |
| } |
| |
| operator bool() const { return lock_ != nullptr; } |
| |
| ~SpinlockGuard() { |
| if (lock_ != nullptr) { |
| lock_->store(false, std::memory_order_release); |
| } |
| } |
| |
| private: |
| explicit SpinlockGuard(std::atomic<bool>* lock) : lock_(lock) {} |
| std::atomic<bool>* lock_; |
| }; |
| |
| } // namespace |
| |
| static size_t GetPeakRSSMb() { |
| struct rusage usage = {}; |
| if (getrusage(RUSAGE_SELF, &usage) != 0) return 0; |
| #ifdef __APPLE__ |
| // On MacOS, the unit seems to be byte according to experiment, while some |
| // documents mentioned KiB. This could depend on OS variants. |
| return usage.ru_maxrss >> 20; |
| #else // __APPLE__ |
| // On Linux, ru_maxrss is in KiB |
| return usage.ru_maxrss >> 10; |
| #endif // __APPLE__ |
| } |
| |
| // Returns the current time in microseconds. |
| static uint64_t TimeInUsec() { |
| struct timeval tv = {}; |
| constexpr size_t kUsecInSec = 1000000; |
| gettimeofday(&tv, nullptr); |
| return tv.tv_sec * kUsecInSec + tv.tv_usec; |
| } |
| |
| // Atomic flags to make sure that (a) watchdog failure is reported only for |
| // the current input, and (b) only one thread is handling watchdog failures. |
| |
| // True if the watchdog thread is detecting failures, false otherwise. |
| static std::atomic<bool> watchdog_thread_busy = false; |
| // True if a watchdog failure is found, false otherwise. |
| static std::atomic<bool> watchdog_failure_found = false; |
| |
| static void WaitWatchdogThreadIdle() { |
| while (ABSL_PREDICT_FALSE(watchdog_thread_busy.load())) { |
| if (ABSL_PREDICT_FALSE(watchdog_failure_found.load())) { |
| // A failure is found - wait for the process to terminate. |
| sleep(1); // NOLINT |
| } else { |
| // Busy-wait for the detection. |
| sleep(0); // NOLINT |
| } |
| } |
| } |
| |
| static void CheckWatchdogLimits() { |
| const uint64_t curr_time = time(nullptr); |
| struct Resource { |
| const char* what; |
| const char* units; |
| uint64_t value; |
| uint64_t limit; |
| bool ignore_report; |
| const char* failure; |
| }; |
| const uint64_t input_start_time = state->input_start_time; |
| if (input_start_time == 0) return; |
| const Resource resources[] = { |
| {Resource{ |
| /*what=*/"Per-input timeout", |
| /*units=*/"sec", |
| /*value=*/curr_time - input_start_time, |
| /*limit=*/state->run_time_flags.timeout_per_input, |
| /*ignore_report=*/ |
| state->run_time_flags.ignore_timeout_reports != 0, |
| /*failure=*/kExecutionFailurePerInputTimeout.data(), |
| }}, |
| {Resource{ |
| /*what=*/"RSS limit", |
| /*units=*/"MB", |
| /*value=*/GetPeakRSSMb(), |
| /*limit=*/state->run_time_flags.rss_limit_mb, |
| /*ignore_report=*/false, |
| /*failure=*/kExecutionFailureRssLimitExceeded.data(), |
| }}, |
| }; |
| for (const auto& resource : resources) { |
| if (resource.limit != 0 && resource.value > resource.limit) { |
| if (!watchdog_failure_found.exchange(true)) { |
| if (resource.ignore_report) { |
| fprintf(stderr, |
| "========= %s exceeded: %" PRIu64 " > %" PRIu64 |
| " (%s); exiting without reporting as an error\n", |
| resource.what, resource.value, resource.limit, |
| resource.units); |
| std::_Exit(0); |
| // should not return here. |
| } |
| fprintf(stderr, |
| "========= %s exceeded: %" PRIu64 " > %" PRIu64 |
| " (%s); exiting\n", |
| resource.what, resource.value, resource.limit, resource.units); |
| fprintf( |
| stderr, |
| "==============================================================" |
| "===\n" |
| "=== BUG FOUND!\n The %s is set to %" PRIu64 |
| " (%s), but it exceeded %" PRIu64 |
| ".\n" |
| "Find out how to adjust the resource limits at " |
| "https://github.com/google/fuzztest/tree/main/doc/flags-reference.md" |
| "\n", |
| resource.what, resource.limit, resource.units, resource.value); |
| CentipedeSetFailureDescription(resource.failure); |
| std::abort(); |
| } |
| } |
| } |
| } |
| |
| // Watchdog thread. Periodically checks if it's time to abort due to a |
| // timeout/OOM. |
| [[noreturn]] static void* WatchdogThread(void* unused) { |
| // Since the watchdog is internal and does not execute user code, disable |
| // SanCov tracing and TLS traversal. |
| tls.traced = false; |
| tls.ignore = true; |
| state->watchdog_thread_started = true; |
| while (true) { |
| sleep(1); |
| |
| // No calls to ResetInputTimer() yet: input execution hasn't started. |
| if (state->input_start_time == 0) continue; |
| |
| watchdog_thread_busy = true; |
| CheckWatchdogLimits(); |
| watchdog_thread_busy = false; |
| } |
| } |
| |
| __attribute__((noinline)) void CheckStackLimit(size_t stack_usage, |
| bool is_current_stack) { |
| static std::atomic_flag stack_limit_exceeded = ATOMIC_FLAG_INIT; |
| const size_t stack_limit = state->run_time_flags.stack_limit_kb.load() << 10; |
| // Check for the stack limit only if sp is inside the stack region. |
| if (stack_limit > 0 && stack_usage > stack_limit) { |
| const bool test_not_running = state->input_start_time == 0; |
| if (test_not_running && is_current_stack) return; |
| if (stack_limit_exceeded.test_and_set()) return; |
| fprintf(stderr, |
| "========= Stack limit exceeded: %zu" |
| " > %zu" |
| " (byte) in %s; aborting\n", |
| stack_usage, stack_limit, |
| is_current_stack ? "the current stack" : "a previous stack"); |
| CentipedeSetFailureDescription( |
| fuzztest::internal::kExecutionFailureStackLimitExceeded.data()); |
| std::abort(); |
| } |
| } |
| |
| void GlobalRunnerState::StartWatchdogThread() { |
| fprintf(stderr, |
| "Starting watchdog thread: timeout_per_input: %" PRIu64 |
| " sec; rss_limit_mb: %" PRIu64 " MB; stack_limit_kb: %" PRIu64 |
| " KB\n", |
| run_time_flags.timeout_per_input.load(), |
| run_time_flags.rss_limit_mb.load(), |
| state->run_time_flags.stack_limit_kb.load()); |
| pthread_t watchdog_thread; |
| pthread_create(&watchdog_thread, nullptr, WatchdogThread, nullptr); |
| pthread_detach(watchdog_thread); |
| // Wait until the watchdog actually starts and initializes itself. |
| while (!state->watchdog_thread_started) { |
| sleep(0); |
| } |
| } |
| |
| void GlobalRunnerState::ResetTimers() { |
| const auto curr_time = time(nullptr); |
| state->input_start_time = curr_time; |
| } |
| |
| // Returns a random seed. No need for a more sophisticated seed. |
| static unsigned GetRandomSeed() { return time(nullptr); } |
| |
| static void EnsureBuiltinMutator() { |
| if (state->byte_array_mutator == nullptr) { |
| static auto* mutator = new ByteArrayMutator(state->knobs, GetRandomSeed()); |
| state->byte_array_mutator = mutator; |
| } |
| } |
| |
| // Byte array mutation fallback for a custom mutator, as defined here: |
| // https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md |
| extern "C" __attribute__((weak)) size_t |
| CentipedeLLVMFuzzerMutateCallback(uint8_t* data, size_t size, size_t max_size) { |
| // TODO(kcc): [as-needed] fix the interface mismatch. |
| // LLVMFuzzerMutate is an array-based interface (for compatibility reasons) |
| // while ByteArray has a vector-based interface. |
| // This incompatibility causes us to do extra allocate/copy per mutation. |
| // It may not cause big problems in practice though. |
| if (max_size == 0) return 0; // just in case, not expected to happen. |
| if (size == 0) { |
| // Don't mutate empty data, just return a 1-byte result. |
| data[0] = 0; |
| return 1; |
| } |
| |
| ByteArray array(data, data + size); |
| EnsureBuiltinMutator(); |
| state->byte_array_mutator->set_max_len(max_size); |
| state->byte_array_mutator->Mutate(array); |
| if (array.size() > max_size) { |
| array.resize(max_size); |
| } |
| memcpy(data, array.data(), array.size()); |
| return array.size(); |
| } |
| |
| extern "C" size_t LLVMFuzzerMutate(uint8_t* data, size_t size, |
| size_t max_size) { |
| return CentipedeLLVMFuzzerMutateCallback(data, size, max_size); |
| } |
| |
| // An arbitrary large size for input data. |
| static const size_t kMaxDataSize = 1 << 20; |
| |
| static void WriteFeaturesToFile(FILE* file, const feature_t* features, |
| size_t size) { |
| if (!size) return; |
| auto bytes_written = fwrite(features, 1, sizeof(features[0]) * size, file); |
| PrintErrorAndExitIf(bytes_written != size * sizeof(features[0]), |
| "wrong number of bytes written for coverage"); |
| } |
| |
| // Clears all coverage data. |
| // We still need to clear all the thread-local data updated during execution. |
| // If `full_clear==true` clear all coverage anyway - useful to remove the |
| // coverage accumulated during startup. |
| __attribute__((noinline)) // so that we see it in profile. |
| static void PrepareCoverage(bool full_clear) { |
| CleanUpSancovTls(); |
| { |
| fuzztest::internal::LockGuard lock(state->execution_result_override_mu); |
| if (state->execution_result_override != nullptr) { |
| state->execution_result_override->ClearAndResize(0); |
| } |
| } |
| PrepareSancov(full_clear); |
| } |
| |
| void RunnerCallbacks::GetPresetSeedInputs( |
| const std::function<void(void*)>& seed_callback) {} |
| |
| void* RunnerCallbacks::GetRandomSeedInput() { return DeserializeInput({0}); } |
| |
| std::string RunnerCallbacks::GetSerializedTargetConfig() { return ""; } |
| |
| void* RunnerCallbacks::Mutate(void* origin, |
| const ExecutionMetadata& origin_metadata) { |
| RunnerCheck(HasCustomMutator(), |
| "RunnerCallbacks::Mutate called despite HasCustomMutator() " |
| "returning false. This is a runner bug!"); |
| RunnerCheck(!HasCustomMutator(), |
| "Class deriving from RunnerCallbacks must implement Mutate() if " |
| "HasCustomMutator() returns true."); |
| // Should be unreachable here, but still return something safe. |
| return DeserializeInput({0}); |
| } |
| |
| void* RunnerCallbacks::CrossOver(void* origin, |
| const ExecutionMetadata& origin_metadata, |
| void* other, |
| const ExecutionMetadata& other_metadata) { |
| return Mutate(origin, origin_metadata); |
| } |
| |
| class LegacyRunnerCallbacks : public RunnerCallbacks { |
| public: |
| LegacyRunnerCallbacks(FuzzerTestOneInputCallback test_one_input_cb, |
| FuzzerCustomMutatorCallback custom_mutator_cb, |
| FuzzerCustomCrossOverCallback custom_crossover_cb) |
| : test_one_input_cb_(test_one_input_cb), |
| custom_mutator_cb_(custom_mutator_cb), |
| custom_crossover_cb_(custom_crossover_cb) {} |
| |
| bool Execute(void* input) override { |
| PrintErrorAndExitIf(test_one_input_cb_ == nullptr, |
| "missing test_on_input_cb"); |
| const auto* input_ba = reinterpret_cast<const ByteArray*>(input); |
| const int retval = test_one_input_cb_(input_ba->data(), input_ba->size()); |
| PrintErrorAndExitIf( |
| retval != -1 && retval != 0, |
| "test_on_input_cb returns invalid value other than -1 and 0"); |
| return retval == 0; |
| } |
| |
| bool HasCustomMutator() const override { |
| return custom_mutator_cb_ != nullptr; |
| } |
| |
| void SerializeInput(void* input, |
| const std::function<void(ByteSpan)>& bytes_sink) override; |
| void* DeserializeInput(ByteSpan input_bytes) override; |
| void FreeInput(void* input) override; |
| |
| void* Mutate(void* origin, const ExecutionMetadata& origin_metadata) override; |
| void* CrossOver(void* origin, const ExecutionMetadata& origin_metadata, |
| void* other, |
| const ExecutionMetadata& other_metadata) override; |
| |
| private: |
| FuzzerTestOneInputCallback test_one_input_cb_; |
| FuzzerCustomMutatorCallback custom_mutator_cb_; |
| FuzzerCustomCrossOverCallback custom_crossover_cb_; |
| }; |
| |
| std::unique_ptr<RunnerCallbacks> CreateLegacyRunnerCallbacks( |
| FuzzerTestOneInputCallback test_one_input_cb, |
| FuzzerCustomMutatorCallback custom_mutator_cb, |
| FuzzerCustomCrossOverCallback custom_crossover_cb) { |
| return std::make_unique<LegacyRunnerCallbacks>( |
| test_one_input_cb, custom_mutator_cb, custom_crossover_cb); |
| } |
| |
| static void RunOneInput(void* input, RunnerCallbacks& callbacks) { |
| state->stats = {}; |
| size_t last_time_usec = 0; |
| auto UsecSinceLast = [&last_time_usec]() { |
| uint64_t t = TimeInUsec(); |
| uint64_t ret_val = t - last_time_usec; |
| last_time_usec = t; |
| return ret_val; |
| }; |
| UsecSinceLast(); |
| PrepareCoverage(/*full_clear=*/false); |
| state->stats.prep_time_usec = UsecSinceLast(); |
| state->ResetTimers(); |
| int target_return_value = callbacks.Execute(input) ? 0 : -1; |
| state->stats.exec_time_usec = UsecSinceLast(); |
| CheckWatchdogLimits(); |
| if (fuzztest::internal::state->input_start_time.exchange(0) != 0) { |
| PostProcessSancov(target_return_value == -1); |
| } |
| WaitWatchdogThreadIdle(); |
| state->stats.post_time_usec = UsecSinceLast(); |
| state->stats.peak_rss_mb = GetPeakRSSMb(); |
| } |
| |
| // Runs one input provided in file `input_path`. |
| // Produces coverage data in file `input_path`-features. |
| __attribute__((noinline)) // so that we see it in profile. |
| static void ReadOneInputExecuteItAndDumpCoverage(const char* input_path, |
| RunnerCallbacks& callbacks) { |
| // Read the input. |
| auto data = ReadBytesFromFilePath<uint8_t>(input_path); |
| |
| void* input = callbacks.DeserializeInput(data); |
| RunOneInput(input, callbacks); |
| callbacks.FreeInput(input); |
| |
| // Dump features to a file. |
| char features_file_path[PATH_MAX]; |
| snprintf(features_file_path, sizeof(features_file_path), "%s-features", |
| input_path); |
| FILE* features_file = fopen(features_file_path, "w"); |
| PrintErrorAndExitIf(features_file == nullptr, "can't open coverage file"); |
| |
| const SanCovRuntimeRawFeatureParts sancov_features = |
| SanCovRuntimeGetFeatures(); |
| WriteFeaturesToFile(features_file, sancov_features.features, |
| sancov_features.num_features); |
| fclose(features_file); |
| } |
| |
| // Starts sending the outputs (coverage, etc.) to `outputs_blobseq`. |
| // Returns true on success. |
| static bool StartSendingOutputsToEngine(BlobSequence& outputs_blobseq) { |
| return BatchResult::WriteInputBegin(outputs_blobseq); |
| } |
| |
| // Copy all the sancov features to `data` with given `capacity` in bytes. |
| // Returns the byte size of sancov features. |
| static size_t CopyFeatures(uint8_t* data, size_t capacity) { |
| const SanCovRuntimeRawFeatureParts sancov_features = |
| SanCovRuntimeGetFeatures(); |
| const size_t features_len_in_bytes = |
| sancov_features.num_features * sizeof(feature_t); |
| if (features_len_in_bytes > capacity) return 0; |
| memcpy(data, sancov_features.features, features_len_in_bytes); |
| return features_len_in_bytes; |
| } |
| |
| // Finishes sending the outputs (coverage, etc.) to `outputs_blobseq`. |
| // Returns true on success. |
| static bool FinishSendingOutputsToEngine(BlobSequence& outputs_blobseq) { |
| { |
| LockGuard lock(state->execution_result_override_mu); |
| bool has_overridden_execution_result = false; |
| if (state->execution_result_override != nullptr) { |
| RunnerCheck(state->execution_result_override->results().size() <= 1, |
| "unexpected number of overridden execution results"); |
| has_overridden_execution_result = |
| state->execution_result_override->results().size() == 1; |
| } |
| if (has_overridden_execution_result) { |
| const auto& result = state->execution_result_override->results()[0]; |
| return BatchResult::WriteOneFeatureVec(result.features().data(), |
| result.features().size(), |
| outputs_blobseq) && |
| BatchResult::WriteMetadata(result.metadata(), outputs_blobseq) && |
| BatchResult::WriteStats(result.stats(), outputs_blobseq) && |
| BatchResult::WriteInputEnd(outputs_blobseq); |
| } |
| } |
| |
| const SanCovRuntimeRawFeatureParts sancov_features = |
| SanCovRuntimeGetFeatures(); |
| // Copy features to shared memory. |
| if (!BatchResult::WriteOneFeatureVec(sancov_features.features, |
| sancov_features.num_features, |
| outputs_blobseq)) { |
| return false; |
| } |
| |
| if (!BatchResult::WriteMetadata(SanCovRuntimeGetExecutionMetadata(), |
| outputs_blobseq)) { |
| return false; |
| } |
| |
| // Write the stats. |
| if (!BatchResult::WriteStats(state->stats, outputs_blobseq)) return false; |
| // We are done with this input. |
| if (!BatchResult::WriteInputEnd(outputs_blobseq)) return false; |
| return true; |
| } |
| |
| // Dumps serialized target config to `output_file_path`. Also see |
| // `GetSerializedTargetConfigViaExternalBinary()`. |
| static void DumpSerializedTargetConfigToFile(RunnerCallbacks& callbacks, |
| const char* output_file_path) { |
| const std::string config = callbacks.GetSerializedTargetConfig(); |
| FILE* output_file = fopen(output_file_path, "w"); |
| const size_t num_bytes_written = |
| fwrite(config.data(), 1, config.size(), output_file); |
| PrintErrorAndExitIf( |
| num_bytes_written != config.size(), |
| "wrong number of bytes written for serialized target configuration"); |
| fclose(output_file); |
| } |
| |
| void LegacyRunnerCallbacks::SerializeInput( |
| void* input, const std::function<void(ByteSpan)>& bytes_sink) { |
| const auto* input_object = reinterpret_cast<const ByteArray*>(input); |
| bytes_sink(*input_object); |
| } |
| |
| void* LegacyRunnerCallbacks::DeserializeInput(ByteSpan input_bytes) { |
| return reinterpret_cast<void*>( |
| new ByteArray{input_bytes.begin(), input_bytes.end()}); |
| } |
| |
| void LegacyRunnerCallbacks::FreeInput(void* input) { |
| delete reinterpret_cast<const ByteArray*>(input); |
| } |
| |
| void* LegacyRunnerCallbacks::Mutate(void* origin, |
| const ExecutionMetadata& origin_metadata) { |
| const auto* origin_ba = reinterpret_cast<const ByteArray*>(origin); |
| EnsureBuiltinMutator(); |
| state->byte_array_mutator->SetMetadata(origin_metadata); |
| const size_t max_mutant_size = state->run_time_flags.max_len; |
| const size_t size = std::min(max_mutant_size, origin_ba->size()); |
| auto* mutant = new ByteArray(); |
| mutant->resize(max_mutant_size); |
| std::copy(origin_ba->begin(), origin_ba->begin() + size, mutant->begin()); |
| static unsigned int seed = GetRandomSeed(); |
| size_t new_size = |
| custom_mutator_cb_(mutant->data(), size, max_mutant_size, rand_r(&seed)); |
| if (new_size == 0) { |
| new_size = 1; |
| mutant->assign({0}); |
| } else { |
| if (new_size >= max_mutant_size) { |
| new_size = max_mutant_size; |
| } |
| mutant->resize(new_size); |
| } |
| return reinterpret_cast<void*>(mutant); |
| } |
| |
| void* LegacyRunnerCallbacks::CrossOver( |
| void* origin, const ExecutionMetadata& origin_metadata, void* other, |
| const ExecutionMetadata& other_metadata) { |
| if (custom_crossover_cb_ == nullptr) { |
| return Mutate(origin, origin_metadata); |
| } |
| const auto* origin_ba = reinterpret_cast<const ByteArray*>(origin); |
| const auto* other_ba = reinterpret_cast<const ByteArray*>(other); |
| static unsigned int seed = GetRandomSeed(); |
| EnsureBuiltinMutator(); |
| state->byte_array_mutator->SetMetadata((rand_r(&seed) & 1) ? origin_metadata |
| : other_metadata); |
| const size_t max_mutant_size = state->run_time_flags.max_len; |
| const size_t size = std::min(max_mutant_size, origin_ba->size()); |
| auto* mutant = new ByteArray(); |
| mutant->resize(max_mutant_size); |
| std::copy(origin_ba->begin(), origin_ba->begin() + size, mutant->begin()); |
| size_t new_size = custom_crossover_cb_( |
| origin_ba->data(), origin_ba->size(), other_ba->data(), other_ba->size(), |
| mutant->data(), max_mutant_size, rand_r(&seed)); |
| if (new_size == 0) { |
| new_size = 1; |
| mutant->assign({0}); |
| } else { |
| if (new_size >= max_mutant_size) { |
| new_size = max_mutant_size; |
| } |
| mutant->resize(new_size); |
| } |
| return reinterpret_cast<void*>(mutant); |
| } |
| |
| // Returns the current process VmSize, in bytes. |
| static size_t GetVmSizeInBytes() { |
| FILE* f = fopen("/proc/self/statm", "r"); // man proc |
| if (!f) return 0; |
| size_t vm_size = 0; |
| // NOTE: Ignore any (unlikely) failures to suppress a compiler warning. |
| (void)fscanf(f, "%zd", &vm_size); |
| fclose(f); |
| return vm_size * getpagesize(); // proc gives VmSize in pages. |
| } |
| |
| // Sets RLIMIT_CORE, RLIMIT_AS |
| static void SetLimits() { |
| // Disable core dumping. |
| struct rlimit core_limits; |
| getrlimit(RLIMIT_CORE, &core_limits); |
| core_limits.rlim_cur = 0; |
| core_limits.rlim_max = 0; |
| setrlimit(RLIMIT_CORE, &core_limits); |
| |
| // ASAN/TSAN/MSAN can not be used with RLIMIT_AS. |
| // We get the current VmSize, if it is greater than 1Tb, we assume we |
| // are running under one of ASAN/TSAN/MSAN and thus cannot use RLIMIT_AS. |
| constexpr size_t one_tb = 1ULL << 40; |
| size_t vm_size_in_bytes = GetVmSizeInBytes(); |
| // Set the address-space limit (RLIMIT_AS). |
| // No-op under ASAN/TSAN/MSAN - those may still rely on rss_limit_mb. |
| if (vm_size_in_bytes < one_tb) { |
| size_t address_space_limit_mb = |
| state->flag_helper.GetIntFlag("address_space_limit_mb=", 0); |
| if (address_space_limit_mb > 0) { |
| size_t limit_in_bytes = address_space_limit_mb << 20; |
| struct rlimit rlimit_as = {limit_in_bytes, limit_in_bytes}; |
| setrlimit(RLIMIT_AS, &rlimit_as); |
| } |
| } else { |
| fprintf(stderr, |
| "Not using RLIMIT_AS; " |
| "VmSize is %zdGb, suspecting ASAN/MSAN/TSAN\n", |
| vm_size_in_bytes >> 30); |
| } |
| } |
| |
| GlobalRunnerState::GlobalRunnerState() { |
| FuzzTestWorkerInitEarly(); |
| SancovRuntimeInitialize(); |
| |
| // TODO(kcc): move some code from CentipedeRunnerMain() here so that it works |
| // even if CentipedeRunnerMain() is not called. |
| state->StartWatchdogThread(); |
| |
| SetLimits(); |
| } |
| |
| void GlobalRunnerState::OnTermination() { |
| // 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->run_time_flags.shmem_size_mb != 0) { |
| PostProcessSancov(); // TODO(xinhaoyuan): do we know our exit status? |
| SharedMemoryBlobSequence outputs_blobseq( |
| sancov_state->arg2, state->run_time_flags.shmem_size_mb << 20); |
| StartSendingOutputsToEngine(outputs_blobseq); |
| FinishSendingOutputsToEngine(outputs_blobseq); |
| } |
| { |
| LockGuard lock(state->execution_result_override_mu); |
| if (state->execution_result_override != nullptr) { |
| delete state->execution_result_override; |
| state->execution_result_override = nullptr; |
| } |
| } |
| } |
| |
| namespace { |
| |
| struct Input { |
| void* content; |
| ExecutionMetadata metadata; |
| }; |
| |
| struct RunnerAdapterCtx { |
| bool need_full_cleanup; |
| RunnerCallbacks& callbacks; |
| }; |
| |
| void SetUpCoverageDomains(FuzzTestAdapterCtx* ctx, |
| const FuzzTestCoverageDomainRegistry* registry) { |
| SanCovRuntimeSetUpCoverageDomains(registry); |
| } |
| |
| void GetPresetSeedInputs(FuzzTestAdapterCtx* ctx, |
| const FuzzTestInputSink* sink) { |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| runner_ctx->callbacks.GetPresetSeedInputs([&](void* input) { |
| sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>( |
| new Input{/*content=*/input, |
| /*metadata=*/{}})); |
| }); |
| } |
| |
| void GetRandomSeedInput(FuzzTestAdapterCtx* ctx, |
| const FuzzTestInputSink* sink) { |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| sink->Emit(sink->ctx, |
| reinterpret_cast<FuzzTestInputHandle>(new Input{ |
| /*content=*/runner_ctx->callbacks.GetRandomSeedInput(), |
| /*metadata=*/{}})); |
| } |
| |
| void NotifyControllerAndExitIfNoCustomMutator(RunnerCallbacks& callbacks) { |
| if (!callbacks.HasCustomMutator()) { |
| // This is a ugly hack to let Centipede use the builtin mutator |
| // needed by the LLVM fuzzer runners (used by some engine tests). We |
| // cannot remove it until the LLVM fuzzers can be migrated to use |
| // the FuzzTest LLVM fuzzer wrapper. |
| SharedMemoryBlobSequence outputs_blobseq( |
| sancov_state->arg2, |
| sancov_state->flag_helper.GetIntFlag("shmem_size_mb=", 0) << 20); |
| RunnerCheck(MutationResult::WriteHasCustomMutator(false, outputs_blobseq), |
| "Failed to write the indicator for no custom mutator"); |
| std::_Exit(0); |
| } |
| } |
| |
| void Mutate(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle origin_handle, |
| int shrink, const FuzzTestInputSink* sink) { |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| runner_ctx->need_full_cleanup = true; |
| NotifyControllerAndExitIfNoCustomMutator(runner_ctx->callbacks); |
| const auto* origin = reinterpret_cast<Input*>(origin_handle); |
| auto mutant = new Input{ |
| /*content=*/runner_ctx->callbacks.Mutate(origin->content, |
| origin->metadata), |
| /*metadata=*/{}, |
| }; |
| sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(mutant)); |
| } |
| |
| void CrossOver(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle origin_handle, |
| FuzzTestInputHandle other_handle, |
| const FuzzTestInputSink* sink) { |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| runner_ctx->need_full_cleanup = true; |
| NotifyControllerAndExitIfNoCustomMutator(runner_ctx->callbacks); |
| const auto* origin = reinterpret_cast<Input*>(origin_handle); |
| const auto* other = reinterpret_cast<Input*>(other_handle); |
| auto mutant = new Input{ |
| /*content=*/runner_ctx->callbacks.CrossOver( |
| origin->content, origin->metadata, other->content, other->metadata), |
| /*metadata=*/{}, |
| }; |
| sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(mutant)); |
| } |
| |
| void Execute(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle, |
| const FuzzTestFeedbackSink* sink) { |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| auto* input = reinterpret_cast<Input*>(handle); |
| SanCovRuntimeClearCoverage(runner_ctx->need_full_cleanup); |
| if (runner_ctx->need_full_cleanup) { |
| runner_ctx->need_full_cleanup = false; |
| } |
| const int old_traced = CentipedeSetCurrentThreadTraced(/*traced=*/1); |
| RunOneInput(input->content, runner_ctx->callbacks); |
| CentipedeSetCurrentThreadTraced(old_traced); |
| { |
| LockGuard lock(state->execution_result_override_mu); |
| bool has_overridden_execution_result = false; |
| if (state->execution_result_override != nullptr) { |
| RunnerCheck(state->execution_result_override->results().size() <= 1, |
| "unexpected number of overridden execution results"); |
| has_overridden_execution_result = |
| state->execution_result_override->results().size() == 1; |
| } |
| if (has_overridden_execution_result) { |
| auto& result = state->execution_result_override->results()[0]; |
| SanCovRuntimeConvertToEngineFeatures(result.mutable_features().data(), |
| result.mutable_features().size()); |
| const FuzzTestUint64sView features = { |
| result.features().data(), |
| result.features().size(), |
| }; |
| sink->EmitCoverageFeatures(sink->ctx, &features); |
| input->metadata = result.metadata(); |
| return; |
| } |
| } |
| SanCovRuntimeEmitFeatures(sink); |
| input->metadata = SanCovRuntimeGetExecutionMetadata(); |
| } |
| |
| void DeserializeInputContent(FuzzTestAdapterCtx* ctx, |
| const FuzzTestBytesView* view, |
| const FuzzTestInputSink* sink) { |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| auto* input = new Input{ |
| /*content=*/runner_ctx->callbacks.DeserializeInput( |
| {view->data, view->size}), |
| /*metadata=*/{}, |
| }; |
| sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(input)); |
| } |
| |
| void UpdateInputMetadata(FuzzTestAdapterCtx* ctx, const FuzzTestBytesView* view, |
| FuzzTestInputHandle handle) { |
| auto* input = reinterpret_cast<Input*>(handle); |
| input->metadata.cmp_data = {view->data, view->data + view->size}; |
| } |
| |
| void SerializeInputContent(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle, |
| const FuzzTestBytesSink* sink) { |
| auto* input = reinterpret_cast<Input*>(handle); |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| runner_ctx->callbacks.SerializeInput(input->content, [&](ByteSpan bytes) { |
| const auto input_bytes = FuzzTestBytesView{ |
| reinterpret_cast<const uint8_t*>(bytes.data()), |
| bytes.size(), |
| }; |
| sink->Emit(sink->ctx, &input_bytes); |
| }); |
| } |
| |
| void SerializeInputMetadata(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle, |
| const FuzzTestBytesSink* sink) { |
| auto* input = reinterpret_cast<Input*>(handle); |
| const auto input_bytes = FuzzTestBytesView{ |
| reinterpret_cast<const uint8_t*>(input->metadata.cmp_data.data()), |
| input->metadata.cmp_data.size(), |
| }; |
| sink->Emit(sink->ctx, &input_bytes); |
| } |
| |
| void FreeInput(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle) { |
| auto* input = reinterpret_cast<Input*>(handle); |
| auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| runner_ctx->callbacks.FreeInput(input->content); |
| delete input; |
| } |
| |
| void FreeCtx(FuzzTestAdapterCtx* ctx) { |
| delete reinterpret_cast<RunnerAdapterCtx*>(ctx); |
| auto guard = SpinlockGuard::Acquire(state->diagnostic_sink_spinlock); |
| state->diagnostic_sink = nullptr; |
| } |
| |
| void GetTestName(FuzzTestAdapterManagerCtx* ctx, |
| const FuzzTestBytesSink* sink) { |
| // Provide the test name exactly specified from the flag. This is hacky |
| // but should work as the user of runner should call RunnerMain at most |
| // once. |
| static const char* test_name = state->flag_helper.GetStringFlag("test="); |
| if (test_name == nullptr) return; |
| static size_t len = strlen(test_name); |
| const auto bytes = FuzzTestBytesView{ |
| reinterpret_cast<const uint8_t*>(test_name), |
| len, |
| }; |
| sink->Emit(sink->ctx, &bytes); |
| } |
| |
| void ConstructAdapter(FuzzTestAdapterManagerCtx* ctx, |
| const FuzzTestDiagnosticSink* diagnostic_sink, |
| FuzzTestAdapter* adapter_out) { |
| { |
| auto guard = SpinlockGuard::Acquire(state->diagnostic_sink_spinlock); |
| state->diagnostic_sink = diagnostic_sink; |
| } |
| adapter_out->ctx = reinterpret_cast<FuzzTestAdapterCtx*>(new RunnerAdapterCtx{ |
| /*need_full_cleanup=*/true, *reinterpret_cast<RunnerCallbacks*>(ctx)}); |
| |
| adapter_out->SetUpCoverageDomains = SetUpCoverageDomains; |
| adapter_out->GetPresetSeedInputs = GetPresetSeedInputs; |
| adapter_out->GetRandomSeedInput = GetRandomSeedInput; |
| adapter_out->Mutate = Mutate; |
| adapter_out->CrossOver = CrossOver; |
| adapter_out->Execute = Execute; |
| adapter_out->DeserializeInputContent = DeserializeInputContent; |
| adapter_out->UpdateInputMetadata = UpdateInputMetadata; |
| adapter_out->SerializeInputContent = SerializeInputContent; |
| adapter_out->SerializeInputMetadata = SerializeInputMetadata; |
| adapter_out->FreeInput = FreeInput; |
| adapter_out->FreeCtx = FreeCtx; |
| } |
| |
| } // namespace |
| |
| // Constructs a FuzzTestAdapterManager and try to run as an engine worker. |
| // |
| // If engine worker is not required, calls |
| // ReadOneInputExecuteItAndDumpCoverage() for all input files from argc/argv. |
| int RunnerMain(int argc, char** argv, RunnerCallbacks& callbacks) { |
| state->centipede_runner_main_executed = true; |
| |
| fprintf(stderr, "Centipede fuzz target runner; argv[0]: %s flags: %s\n", |
| argv[0], |
| CentipedeGetRunnerFlags() ? CentipedeGetRunnerFlags() : "(unset)"); |
| |
| if (state->flag_helper.HasSwitchFlag("dump_configuration")) { |
| DumpSerializedTargetConfigToFile(callbacks, |
| /*output_file_path=*/sancov_state->arg1); |
| return EXIT_SUCCESS; |
| } |
| |
| FuzzTestAdapterManager manager = { |
| /*ctx=*/reinterpret_cast<FuzzTestAdapterManagerCtx*>(&callbacks), |
| /*GetBinaryId=*/nullptr, |
| /*GetTestName=*/GetTestName, |
| /*ConstructAdapter=*/ConstructAdapter, |
| }; |
| const int old_traced = CentipedeSetCurrentThreadTraced(/*traced=*/0); |
| const auto s = FuzzTestWorkerMaybeRun(&manager); |
| CentipedeSetCurrentThreadTraced(old_traced); |
| if (s == kFuzzTestWorkerNotRequired) { |
| for (int i = 1; i < argc; i++) { |
| ReadOneInputExecuteItAndDumpCoverage(argv[i], callbacks); |
| } |
| return EXIT_SUCCESS; |
| } |
| return s == kFuzzTestWorkerSuccess ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |
| |
| } // namespace fuzztest::internal |
| |
| extern "C" int LLVMFuzzerRunDriver( |
| int* absl_nonnull argc, char*** absl_nonnull argv, |
| FuzzerTestOneInputCallback test_one_input_cb) { |
| if (LLVMFuzzerInitialize) LLVMFuzzerInitialize(argc, argv); |
| return RunnerMain(*argc, *argv, |
| *fuzztest::internal::CreateLegacyRunnerCallbacks( |
| test_one_input_cb, LLVMFuzzerCustomMutator, |
| LLVMFuzzerCustomCrossOver)); |
| } |
| |
| extern "C" void CentipedeSetRssLimit(size_t rss_limit_mb) { |
| fprintf(stderr, "CentipedeSetRssLimit: changing rss_limit_mb to %zu\n", |
| rss_limit_mb); |
| fuzztest::internal::state->run_time_flags.rss_limit_mb = rss_limit_mb; |
| } |
| |
| extern "C" void CentipedeSetStackLimit(size_t stack_limit_kb) { |
| fprintf(stderr, "CentipedeSetStackLimit: changing stack_limit_kb to %zu\n", |
| stack_limit_kb); |
| fuzztest::internal::state->run_time_flags.stack_limit_kb = stack_limit_kb; |
| } |
| |
| extern "C" void CentipedeSetTimeoutPerInput(uint64_t timeout_per_input) { |
| fprintf(stderr, |
| "CentipedeSetTimeoutPerInput: changing timeout_per_input to %" PRIu64 |
| "\n", |
| timeout_per_input); |
| fuzztest::internal::state->run_time_flags.timeout_per_input = |
| timeout_per_input; |
| } |
| |
| extern "C" __attribute__((weak)) const char* absl_nullable |
| CentipedeGetRunnerFlags() { |
| static const char* flags = []() -> const char* { |
| if (const char* runner_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) { |
| return strdup(runner_flags_env); |
| } |
| return nullptr; |
| }(); |
| return flags; |
| } |
| |
| // TODO: xinhaoyuan - write test for this. |
| extern "C" const char* absl_nullable GetSancovFlags() { |
| return CentipedeGetRunnerFlags(); |
| } |
| |
| static std::atomic<bool> in_execution_batch = false; |
| |
| extern "C" void CentipedeBeginExecutionBatch() { |
| if (in_execution_batch) { |
| fprintf(stderr, |
| "CentipedeBeginExecutionBatch called twice without calling " |
| "CentipedeEndExecutionBatch in between\n"); |
| _exit(EXIT_FAILURE); |
| } |
| in_execution_batch = true; |
| fuzztest::internal::PrepareCoverage(/*full_clear=*/true); |
| } |
| |
| extern "C" void CentipedeEndExecutionBatch() { |
| if (!in_execution_batch) { |
| fprintf(stderr, |
| "CentipedeEndExecutionBatch called without calling " |
| "CentipedeBeginExecutionBatch before\n"); |
| _exit(EXIT_FAILURE); |
| } |
| in_execution_batch = false; |
| fuzztest::internal::state->input_start_time = 0; |
| } |
| |
| extern "C" void CentipedePrepareProcessing() { |
| fuzztest::internal::PrepareCoverage(/*full_clear=*/!in_execution_batch); |
| fuzztest::internal::state->ResetTimers(); |
| } |
| |
| extern "C" void CentipedeFinalizeProcessing() { |
| fuzztest::internal::CheckWatchdogLimits(); |
| if (fuzztest::internal::state->input_start_time.exchange(0) != 0) { |
| fuzztest::internal::PostProcessSancov(); |
| } |
| } |
| |
| extern "C" int CentipedeSetCurrentThreadTraced(int traced) { |
| const int old_traced = fuzztest::internal::tls.traced; |
| fuzztest::internal::tls.traced = traced; |
| return old_traced; |
| } |
| |
| extern "C" size_t CentipedeGetExecutionResult(uint8_t* data, size_t capacity) { |
| fuzztest::internal::BlobSequence outputs_blobseq(data, capacity); |
| if (!fuzztest::internal::StartSendingOutputsToEngine(outputs_blobseq)) |
| return 0; |
| if (!fuzztest::internal::FinishSendingOutputsToEngine(outputs_blobseq)) |
| return 0; |
| return outputs_blobseq.offset(); |
| } |
| |
| extern "C" size_t CentipedeGetCoverageData(uint8_t* data, size_t capacity) { |
| return fuzztest::internal::CopyFeatures(data, capacity); |
| } |
| |
| extern "C" void CentipedeSetExecutionResult(const uint8_t* data, size_t size) { |
| using fuzztest::internal::state; |
| fuzztest::internal::LockGuard lock(state->execution_result_override_mu); |
| if (!state->execution_result_override) |
| state->execution_result_override = new fuzztest::internal::BatchResult(); |
| state->execution_result_override->ClearAndResize(1); |
| if (data == nullptr) return; |
| // Removing const here should be fine as we don't write to `blobseq`. |
| fuzztest::internal::BlobSequence blobseq(const_cast<uint8_t*>(data), size); |
| state->execution_result_override->Read(blobseq); |
| fuzztest::internal::RunnerCheck( |
| state->execution_result_override->num_outputs_read() == 1, |
| "Failed to set execution result from CentipedeSetExecutionResult"); |
| } |
| |
| extern "C" void CentipedeSetFailureDescription(const char* description) { |
| if (description == nullptr) { |
| static constexpr std::string_view kMsg = |
| "CentipedeSetFailureDescription called with null description\n"; |
| write(STDERR_FILENO, kMsg.data(), kMsg.size()); |
| std::_Exit(EXIT_FAILURE); |
| } |
| std::string_view desc_sv = description; |
| static constexpr std::string_view kSetupFailurePrefix = "SETUP FAILURE:"; |
| using ::fuzztest::internal::SpinlockGuard; |
| using ::fuzztest::internal::state; |
| auto sink_guard = SpinlockGuard::TryAcquire(state->diagnostic_sink_spinlock); |
| if (!sink_guard) { |
| static constexpr std::string_view kMsg = |
| "Diagnostic sink is busy while setting failure:\n"; |
| write(STDERR_FILENO, kMsg.data(), kMsg.size()); |
| write(STDERR_FILENO, description, std::strlen(description)); |
| std::_Exit(EXIT_FAILURE); |
| } |
| const auto* diagnostic_sink = state->diagnostic_sink; |
| if (diagnostic_sink == nullptr) { |
| static constexpr std::string_view kMsg = |
| "Diagnostic sink is missing while setting failure:\n"; |
| write(STDERR_FILENO, kMsg.data(), kMsg.size()); |
| write(STDERR_FILENO, description, std::strlen(description)); |
| std::_Exit(EXIT_FAILURE); |
| } |
| |
| if (desc_sv.substr(0, kSetupFailurePrefix.size()) == kSetupFailurePrefix) { |
| size_t prefix_size = kSetupFailurePrefix.size(); |
| while (prefix_size < desc_sv.size() && desc_sv[prefix_size] == ' ') { |
| ++prefix_size; |
| } |
| const FuzzTestBytesView error = { |
| reinterpret_cast<const uint8_t*>(desc_sv.data() + prefix_size), |
| desc_sv.size() - prefix_size, |
| }; |
| diagnostic_sink->EmitError(diagnostic_sink->ctx, &error); |
| return; |
| } |
| |
| const FuzzTestBytesView description_view = { |
| reinterpret_cast<const uint8_t*>(desc_sv.data()), |
| desc_sv.size(), |
| }; |
| diagnostic_sink->EmitFinding(diagnostic_sink->ctx, &description_view, |
| // For now, use the description as the signature. |
| &description_view); |
| } |