Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,7 @@ RUNNER_DEPS = [
":knobs",
":mutation_data",
":rolling_hash",
":engine_abi",
":runner_cmp_trace",
":runner_fork_server",
":runner_request",
Expand Down Expand Up @@ -1231,6 +1232,7 @@ cc_library(
deps = [
":callstack",
":dispatcher_flag_helper",
":engine_abi",
":execution_metadata",
":feature",
":foreach_nonzero",
Expand Down
29 changes: 20 additions & 9 deletions centipede/engine_abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
57 changes: 28 additions & 29 deletions centipede/engine_worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down Expand Up @@ -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 <typename T>
constexpr T Bits(T v, size_t begin, size_t size) {
return (v >> begin) & ((T{1} << size) - 1);
Expand All @@ -560,7 +553,8 @@ struct CoverageDomainConfiguration {
uint8_t feature_id_bit_size;
uint8_t counter_bit_size;
};
std::array<CoverageDomainConfiguration, 1 << kDomainIdBitSize> coverage_domains;
std::array<CoverageDomainConfiguration, 1 << kFuzzTestCoverageDomainIdBitSize>
coverage_domains;

void WorkerDoExecute(const FuzzTestAdapter& adapter) {
auto* inputs_blobseq = GetInputsBlobSequence();
Expand All @@ -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;
Expand All @@ -602,27 +599,26 @@ void WorkerDoExecute(const FuzzTestAdapter& adapter) {
// In-loop variables declared outside to save allocations.
std::vector<uint64_t> features;
std::vector<uint8_t> serialized_metadata;
std::vector<FuzzTestInputHandle> 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<FuzzTestInputSinkCtx*>(&input),
/*Emit=*/[](FuzzTestInputSinkCtx* ctx, FuzzTestInputHandle input) {
*reinterpret_cast<decltype(&input)>(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 = {
Expand All @@ -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);
}
Expand All @@ -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
Expand Down Expand Up @@ -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");
Expand Down
22 changes: 22 additions & 0 deletions centipede/sancov_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <stddef.h>
#include <stdint.h>

#include "./centipede/engine_abi.h"

#ifdef __cplusplus
extern "C" {
#endif
Expand All @@ -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
Expand Down
103 changes: 95 additions & 8 deletions centipede/sancov_state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -514,6 +512,43 @@ void PostProcessSancov(bool reject_input) {
}
}

template <typename T>
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()};
Expand Down Expand Up @@ -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<uint8_t>(domain_id),
FuzzTestBytesView{
reinterpret_cast<const uint8_t*>(domain_name.data()),
domain_name.size(),
},
/*feature_id_bit_size=*/
static_cast<uint8_t>(is_scoring_domain ? 27 - kCMPScoreBits : 27),
/*counter_bit_size=*/
static_cast<uint8_t>(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();
}
4 changes: 4 additions & 0 deletions centipede/sancov_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading