diff --git a/centipede/BUILD b/centipede/BUILD index f16df040..37d2a1d3 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -1074,6 +1074,7 @@ RUNNER_DEPS = [ ":knobs", ":mutation_data", ":rolling_hash", + ":engine_abi", ":runner_cmp_trace", ":runner_fork_server", ":runner_request", @@ -1231,6 +1232,7 @@ cc_library( deps = [ ":callstack", ":dispatcher_flag_helper", + ":engine_abi", ":execution_metadata", ":feature", ":foreach_nonzero", diff --git a/centipede/engine_abi.h b/centipede/engine_abi.h index 793e7079..63616655 100644 --- a/centipede/engine_abi.h +++ b/centipede/engine_abi.h @@ -87,21 +87,32 @@ typedef struct { size_t size; } FuzzTestUint64sView; +// Constants for the layout of the coverage feature as a 64-bit unsigned +// integer: +// +// - Bits 63..59: 5-bit domain ID of the feature. Each domain is a +// logically independent feature namespace registered in +// `FuzzTestAdapter::SetUpCoverageDomains`. +// - Bits 58..32: 27-bit feature ID within the domain. +// - Bits 31..0: 32-bit counter value of the feature. +// +typedef enum { + kFuzzTestCoverageCounterStartBit = 0, + kFuzzTestCoverageCounterBitSize = 32, + kFuzzTestCoverageFeatureIdStartBit = 32, + kFuzzTestCoverageFeatureIdBitSize = 27, + kFuzzTestCoverageDomainIdStartBit = 59, + kFuzzTestCoverageDomainIdBitSize = 5, +} FuzzTestCoverageFeatureLayout; + // Sink for execution feedback. typedef struct FuzzTestFeedbackSinkCtx FuzzTestFeedbackSinkCtx; typedef struct { FuzzTestFeedbackSinkCtx* ctx; // Emits an array of coverage features captured from the execution - // inside `Execute` call. - // - // Each feature is a 64-bit unsigned integer with the following layout: - // - // - Bits 63..59: 5-bit domain ID of the feature. Each domain is a - // logically independent feature namespace registered in - // `FuzzTestAdapter::SetUpCoverageDomains`. - // - Bits 58..32: 27-bit feature ID within the domain. - // - Bits 31..0: 32-bit counter value of the feature. + // inside `Execute` call. See `FuzzTestCoverageFeatureLayout` for the feature + // layout. // // Multiple emissions are concatenated. void (*EmitCoverageFeatures)(FuzzTestFeedbackSinkCtx* ctx, diff --git a/centipede/engine_worker.cc b/centipede/engine_worker.cc index c9b5eedb..19886f86 100644 --- a/centipede/engine_worker.cc +++ b/centipede/engine_worker.cc @@ -504,7 +504,7 @@ void WorkerDoMutate(const FuzzTestAdapter& adapter) { WORKER_CHECK_FOR_ERROR(); WorkerCheck(emitted_inputs.size() == 1, "DeserializeInputContent must emit exactly one input"); - if (adapter.UpdateInputMetadata) { + if (adapter.UpdateInputMetadata != nullptr) { adapter.UpdateInputMetadata(adapter.ctx, &input_metadata, emitted_inputs[0]); } @@ -542,13 +542,6 @@ void WorkerDoMutate(const FuzzTestAdapter& adapter) { } } -constexpr size_t kCounterStartBit = 0; -constexpr size_t kCounterBitSize = 32; -constexpr size_t kFeatureIdStartBit = 32; -constexpr size_t kFeatureIdBitSize = 27; -constexpr size_t kDomainIdStartBit = 59; -constexpr size_t kDomainIdBitSize = 5; - template constexpr T Bits(T v, size_t begin, size_t size) { return (v >> begin) & ((T{1} << size) - 1); @@ -560,7 +553,8 @@ struct CoverageDomainConfiguration { uint8_t feature_id_bit_size; uint8_t counter_bit_size; }; -std::array coverage_domains; +std::array + coverage_domains; void WorkerDoExecute(const FuzzTestAdapter& adapter) { auto* inputs_blobseq = GetInputsBlobSequence(); @@ -579,12 +573,15 @@ void WorkerDoExecute(const FuzzTestAdapter& adapter) { /*ctx=*/nullptr, /*Register=*/[](FuzzTestCoverageDomainRegistryCtx* ctx, const FuzzTestCoverageDomain* domain) { - WorkerCheck(domain->domain_id < (1 << kDomainIdBitSize), - "domain ID is too large"); - WorkerCheck(domain->feature_id_bit_size <= kFeatureIdBitSize, - "domain feature id bit size is too large"); - WorkerCheck(domain->counter_bit_size <= kCounterBitSize, - "domain counter bit size is too large"); + WorkerCheck( + domain->domain_id < (1 << kFuzzTestCoverageDomainIdBitSize), + "domain ID is too large"); + WorkerCheck( + domain->feature_id_bit_size <= kFuzzTestCoverageFeatureIdBitSize, + "domain feature id bit size is too large"); + WorkerCheck( + domain->counter_bit_size <= kFuzzTestCoverageCounterBitSize, + "domain counter bit size is too large"); WorkerCheck(!coverage_domains[domain->domain_id].registered, "domain ID is already registered"); coverage_domains[domain->domain_id].registered = true; @@ -602,27 +599,26 @@ void WorkerDoExecute(const FuzzTestAdapter& adapter) { // In-loop variables declared outside to save allocations. std::vector features; std::vector serialized_metadata; + std::vector emitted_inputs; + const auto input_sink = GetInputSinkTo(emitted_inputs); for (size_t i = 0; i < num_inputs; i++) { auto blob = inputs_blobseq->Read(); if (!blob.IsValid()) return; // no more blobs to read. WorkerCheck(IsDataInput(blob), "Must read data input"); - FuzzTestInputHandle input; - FuzzTestInputSink sink = { - /*ctx=*/reinterpret_cast(&input), - /*Emit=*/[](FuzzTestInputSinkCtx* ctx, FuzzTestInputHandle input) { - *reinterpret_cast(ctx) = input; - }}; - if (!BatchResult::WriteInputBegin(*outputs_blobseq)) { WorkerLog("failed to write input begin"); break; } + emitted_inputs.clear(); const auto input_content = FuzzTestBytesView{blob.data, blob.size}; - adapter.DeserializeInputContent(adapter.ctx, &input_content, &sink); + adapter.DeserializeInputContent(adapter.ctx, &input_content, &input_sink); WORKER_CHECK_FOR_ERROR(); + WorkerCheck(emitted_inputs.size() == 1, + "Deserialize must emit exactly one input"); + auto input = emitted_inputs[0]; features.clear(); FuzzTestFeedbackSink feedback_sink = { @@ -640,7 +636,7 @@ void WorkerDoExecute(const FuzzTestAdapter& adapter) { WORKER_CHECK_FOR_ERROR(); serialized_metadata.clear(); - if (adapter.SerializeInputMetadata) { + if (adapter.SerializeInputMetadata != nullptr) { const auto metadata_sink = GetBytesSinkTo(serialized_metadata); adapter.SerializeInputMetadata(adapter.ctx, input, &metadata_sink); } @@ -652,14 +648,15 @@ void WorkerDoExecute(const FuzzTestAdapter& adapter) { // Convert to the Centipede feature layout with possible loss. for (auto& feature : features) { const uint64_t domain_id = - Bits(feature, kDomainIdStartBit, kDomainIdBitSize); + Bits(feature, kFuzzTestCoverageDomainIdStartBit, + kFuzzTestCoverageDomainIdBitSize); WorkerCheck(coverage_domains[domain_id].registered, "Emitted features in unregistered domain"); const auto& domain = coverage_domains[domain_id]; - uint64_t feature_id = - Bits(feature, kFeatureIdStartBit, domain.feature_id_bit_size); - uint64_t counter = - Bits(feature, kCounterStartBit, domain.counter_bit_size); + uint64_t feature_id = Bits(feature, kFuzzTestCoverageFeatureIdStartBit, + domain.feature_id_bit_size); + uint64_t counter = Bits(feature, kFuzzTestCoverageCounterStartBit, + domain.counter_bit_size); if (coverage_domains[domain_id].counter_bit_size > 0) { // Assume that `domain_id` is one of the scoring domains in Centipede, // which uses the lower 6 bits for the counter value. The conversion is @@ -754,6 +751,8 @@ FuzzTestWorkerStatus WorkerMaybeRun(const FuzzTestAdapterManager& manager) { manager.ConstructAdapter(manager.ctx, /*diagnostic_sink=*/&diagnostic_sink, &adapter); WORKER_CHECK_FOR_ERROR(); + WorkerCheck(adapter.SetUpCoverageDomains != nullptr, + "SetUpCoverageDomains must be defined"); WorkerCheck(adapter.GetRandomSeedInput != nullptr, "GetRandomSeedInput must be defined"); WorkerCheck(adapter.Execute != nullptr, "Execute must be defined"); diff --git a/centipede/sancov_runtime.h b/centipede/sancov_runtime.h index ce3dbab9..7368e8bd 100644 --- a/centipede/sancov_runtime.h +++ b/centipede/sancov_runtime.h @@ -23,6 +23,8 @@ #include #include +#include "./centipede/engine_abi.h" + #ifdef __cplusplus extern "C" { #endif @@ -48,8 +50,28 @@ void SanCovRuntimeClearCoverage(bool full_clear); // the process. // // If `reject_input==true`, then it will simply empty the feature array. +// +// DEPRECATED: the features here is in the raw Centipede layout instead of +// the standard engine layout. Use `SanCovRuntimePostProcessCoverage` + +// `SanCovRuntimeEmitFeatures` to work with the engine. struct SanCovRuntimeRawFeatureParts SanCovRuntimeGetCoverage(bool reject_input); +// Registers the SanCov coverage domains. Returns next available domain ID. +size_t SanCovRuntimeSetUpCoverageDomains( + const FuzzTestCoverageDomainRegistry* registry); + +// Post-processes all coverage data, and saves it internally for later emission +// and retrival. +// +// If `reject_input==true`, no coverage would be saved. +void SanCovRuntimePostProcessCoverage(bool reject_input); + +// Emits post-processed SanCov coverage features into the engine `sink`. +// +// Should be called at most once after `SanCovPostProcessCoverage` - later calls +// will emit empty coverage. +void SanCovRuntimeEmitFeatures(const FuzzTestFeedbackSink* sink); + #ifdef __cplusplus } // extern "C" #endif diff --git a/centipede/sancov_state.cc b/centipede/sancov_state.cc index 784793ce..701950f9 100644 --- a/centipede/sancov_state.cc +++ b/centipede/sancov_state.cc @@ -317,6 +317,12 @@ void CleanUpSancovTls() { if (sancov_state->flags.callstack_level != 0) { tls.call_stack.Reset(sancov_state->flags.callstack_level); } + if (sancov_state->flags.use_auto_dictionary) { + tls.cmp_trace2.Clear(); + tls.cmp_trace4.Clear(); + tls.cmp_trace8.Clear(); + tls.cmp_traceN.Clear(); + } RunnerCheck(tls.sancov_lowest_sp != nullptr, "sancov_lowest_sp is null for a live thread"); *tls.sancov_lowest_sp = tls.lowest_sp = tls.top_frame_sp; @@ -325,14 +331,6 @@ void CleanUpSancovTls() { void PrepareSancov(bool full_clear) { if (full_clear) { - sancov_state->ForEachTls([](ThreadLocalSancovState& tls) { - if (sancov_state->flags.use_auto_dictionary) { - tls.cmp_trace2.Clear(); - tls.cmp_trace4.Clear(); - tls.cmp_trace8.Clear(); - tls.cmp_traceN.Clear(); - } - }); sancov_state->pc_counter_set.ForEachNonZeroByte( [](size_t idx, uint8_t value) {}, 0, sancov_state->actual_pc_counter_set_size_aligned); @@ -514,6 +512,43 @@ void PostProcessSancov(bool reject_input) { } } +template +constexpr T MoveBits(T bits, size_t start, size_t size) { + return (bits & ((T{1} << size) - 1)) << start; +} + +void SanCovRuntimeConvertToEngineFeatures(feature_t* start, size_t size) { + using ::fuzztest::internal::feature_domains::CMPScoreFeatureIndex; + using ::fuzztest::internal::feature_domains::Domain; + using ::fuzztest::internal::feature_domains::IsComparisonScoreFeature; + using ::fuzztest::internal::feature_domains::kCMPScoreBitmask; + using ::fuzztest::internal::feature_domains::kCMPScoreBits; + + for (size_t i = 0; i < size; ++i) { + auto& feature = start[i]; + const auto domain_id = Domain::FeatureToDomainId(feature); + auto feature_id = Domain::FeatureToIndexInDomain(feature); + if (IsComparisonScoreFeature(feature)) { + feature_id = CMPScoreFeatureIndex(feature); + auto counter = feature_id & kCMPScoreBitmask; + if constexpr (kCMPScoreBits > kFuzzTestCoverageCounterBitSize) { + counter >>= kCMPScoreBits - kFuzzTestCoverageCounterBitSize; + } + feature = MoveBits(domain_id, kFuzzTestCoverageDomainIdStartBit, + kFuzzTestCoverageDomainIdBitSize) | + MoveBits(feature_id, kFuzzTestCoverageFeatureIdStartBit, + kFuzzTestCoverageFeatureIdBitSize) | + MoveBits(counter, kFuzzTestCoverageCounterStartBit, + kFuzzTestCoverageCounterBitSize); + continue; + } + feature = MoveBits(domain_id, kFuzzTestCoverageDomainIdStartBit, + kFuzzTestCoverageDomainIdBitSize) | + MoveBits(feature_id, kFuzzTestCoverageFeatureIdStartBit, + kFuzzTestCoverageFeatureIdBitSize); + } +} + SanCovRuntimeRawFeatureParts SanCovRuntimeGetFeatures() { return {fuzztest::internal::sancov_state->g_features.data(), fuzztest::internal::sancov_state->g_features.size()}; @@ -543,3 +578,55 @@ struct SanCovRuntimeRawFeatureParts SanCovRuntimeGetCoverage( return fuzztest::internal::SanCovRuntimeGetFeatures(); } + +size_t SanCovRuntimeSetUpCoverageDomains( + const FuzzTestCoverageDomainRegistry* registry) { + using ::fuzztest::internal::feature_domains::Domain; + using ::fuzztest::internal::feature_domains::kCMPScoreBits; + using ::fuzztest::internal::feature_domains::kCMPScoreDomains; + using ::fuzztest::internal::feature_domains::kNumDomains; + + static_assert(Domain::kDomainSize == 1 << 27); + static_assert(kNumDomains <= 100); + static_assert(kCMPScoreBits <= 27); + + for (size_t domain_id = 0; domain_id < kNumDomains; ++domain_id) { + const bool is_scoring_domain = + domain_id >= kCMPScoreDomains.front().domain_id() && + domain_id <= kCMPScoreDomains.back().domain_id(); + std::string domain_name = "dom"; + domain_name += '0' + domain_id / 10; + domain_name += '0' + domain_id % 10; + const FuzzTestCoverageDomain domain = { + static_cast(domain_id), + FuzzTestBytesView{ + reinterpret_cast(domain_name.data()), + domain_name.size(), + }, + /*feature_id_bit_size=*/ + static_cast(is_scoring_domain ? 27 - kCMPScoreBits : 27), + /*counter_bit_size=*/ + static_cast(is_scoring_domain ? kCMPScoreBits : 0), + }; + registry->Register(registry->ctx, &domain); + } + + return kNumDomains; +} + +void SanCovRuntimePostProcessCoverage(bool reject_input) { + fuzztest::internal::PostProcessSancov(reject_input); +} + +void SanCovRuntimeEmitFeatures(const FuzzTestFeedbackSink* sink) { + using ::fuzztest::internal::sancov_state; + + fuzztest::internal::SanCovRuntimeConvertToEngineFeatures( + sancov_state->g_features.data(), sancov_state->g_features.size()); + const FuzzTestUint64sView features = { + sancov_state->g_features.data(), + sancov_state->g_features.size(), + }; + sink->EmitCoverageFeatures(sink->ctx, &features); + sancov_state->g_features.clear(); +} diff --git a/centipede/sancov_state.h b/centipede/sancov_state.h index 5ff8d27e..bde30e12 100644 --- a/centipede/sancov_state.h +++ b/centipede/sancov_state.h @@ -313,6 +313,10 @@ SanCovRuntimeRawFeatureParts SanCovRuntimeGetFeatures(); // Gets the execution metadata gathered in `PostProcessSancov`. const ExecutionMetadata& SanCovRuntimeGetExecutionMetadata(); +// Convert Centipede features in `{start, start + size}` to the engine feature +// layout as in `engine_abi.h`. +void SanCovRuntimeConvertToEngineFeatures(feature_t* start, size_t size); + // Check for stack limit for `stack_usage`, with `is_current_stack` set if it // is for the current calling stack. __attribute__((weak)) void CheckStackLimit(size_t stack_usage,