Skip to content

Commit

Permalink
Add a flag that forces failure on issues with the custom mutator.
Browse files Browse the repository at this point in the history
With this flag, we no longer need to perform the "domain setup check" as part
of executing an input. The check happens implicitly during mutation. If the
mutation fails, either due to an ineffective domain or other reasons, Centipede
itself fails.

PiperOrigin-RevId: 726191912
  • Loading branch information
fniksic authored and copybara-github committed Feb 18, 2025
1 parent 1d61720 commit 705bcad
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 16 deletions.
1 change: 1 addition & 0 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,7 @@ cc_test(
"@com_google_fuzztest//centipede/testing:abort_fuzz_target",
"@com_google_fuzztest//centipede/testing:expensive_startup_fuzz_target",
"@com_google_fuzztest//centipede/testing:fuzz_target_with_config",
"@com_google_fuzztest//centipede/testing:fuzz_target_with_custom_mutator",
"@com_google_fuzztest//centipede/testing:seeded_fuzz_target",
"@com_google_fuzztest//centipede/testing:test_fuzz_target",
"@com_google_fuzztest//centipede/testing:test_input_filter",
Expand Down
26 changes: 18 additions & 8 deletions centipede/centipede_default_callbacks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,31 +77,41 @@ void CentipedeDefaultCallbacks::Mutate(
std::vector<ByteArray> &mutants) {
mutants.resize(num_mutants);
if (num_mutants == 0) return;
if (env_.mutator_type == MutatorType::kBuiltIn) {
CentipedeCallbacks::Mutate(inputs, num_mutants, mutants);
return;
}
// Try to use the custom mutator if it hasn't been disabled.
if (custom_mutator_is_usable_.value_or(true)) {
if (MutateViaExternalBinary(env_.binary, inputs, mutants)) {
if (!custom_mutator_is_usable_.has_value()) {
LOG(INFO) << "Custom mutator detected: will use it";
LOG_IF(INFO, env_.mutator_type == MutatorType::kAuto)
<< "Custom mutator detected: will use it";
custom_mutator_is_usable_ = true;
}
if (!mutants.empty()) return;
LOG_FIRST_N(WARNING, 5)
<< "Custom mutator returned no mutants: falling back to internal "
"default mutator";
LOG_IF(FATAL, env_.mutator_type == MutatorType::kCustom)
<< "Custom mutator returned no mutants.";
LOG_FIRST_N(WARNING, 5) << "Custom mutator returned no mutants: falling "
"back to the built-in mutator";
} else if (ShouldStop()) {
LOG(WARNING) << "Custom mutator failed, but ignored since the stop "
"condition it met. Possibly what triggered the stop "
"condition also interrupted the mutator.";
return;
} else {
LOG(WARNING) << "Custom mutator undetected or misbehaving:";
CHECK(!custom_mutator_is_usable_.has_value())
<< "Custom mutator is unreliable, aborting";
LOG(WARNING) << "Falling back to internal default mutator";
LOG_IF(FATAL, env_.mutator_type == MutatorType::kCustom ||
custom_mutator_is_usable_.has_value())
<< "Custom mutator failed, and it was expected to succeed.";
LOG(WARNING) << "Custom mutator undetected or misbehaving. Falling back "
"to the built-in mutator.";
custom_mutator_is_usable_ = false;
}
}
// Fallback of the internal mutator.
CHECK(env_.mutator_type == MutatorType::kAuto)
<< "Internal error: unexpected mutator type "
<< AbslUnparseFlag(env_.mutator_type);
CentipedeCallbacks::Mutate(inputs, num_mutants, mutants);
}

Expand Down
107 changes: 107 additions & 0 deletions centipede/centipede_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1028,4 +1028,111 @@ TEST(Centipede, AbortsOnSetupFailure) {
"Terminating Centipede due to setup failure in the test.");
}

TEST(Centipede, UsesCustomMutatorWithCustomMutatorFlag) {
TempDir temp_dir{test_info_->name()};
Environment env;
env.log_level = 0; // Disable most of the logging in the test.
env.workdir = temp_dir.path();
env.require_pc_table = false; // No PC table here.
env.binary = GetDataDependencyFilepath(
"centipede/testing/fuzz_target_with_custom_mutator");
env.mutator_type = MutatorType::kCustom;
CentipedeDefaultCallbacks callbacks(env);

const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
std::vector<ByteArray> mutants;
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
mutants);

// The custom mutator just returns the original inputs as mutants.
EXPECT_EQ(inputs, mutants);
}

TEST(Centipede, FailsOnMisbehavingMutatorWithCustomMutatorFlag) {
TempDir temp_dir{test_info_->name()};
Environment env;
env.log_level = 0; // Disable most of the logging in the test.
env.workdir = temp_dir.path();
env.require_pc_table = false; // No PC table here.
env.binary =
absl::StrCat(GetDataDependencyFilepath(
"centipede/testing/fuzz_target_with_custom_mutator")
.c_str(),
" --simulate_failure");
env.mutator_type = MutatorType::kCustom;
CentipedeDefaultCallbacks callbacks(env);

const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
std::vector<ByteArray> mutants;
EXPECT_DEATH(callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs),
inputs.size(), mutants),
"Custom mutator failed");
}

TEST(Centipede, DetectsCustomMutatorWithAutoMutatorFlag) {
TempDir temp_dir{test_info_->name()};
Environment env;
env.log_level = 0; // Disable most of the logging in the test.
env.workdir = temp_dir.path();
env.require_pc_table = false; // No PC table here.
env.binary = GetDataDependencyFilepath(
"centipede/testing/fuzz_target_with_custom_mutator");
env.mutator_type = MutatorType::kAuto;
CentipedeDefaultCallbacks callbacks(env);

const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
std::vector<ByteArray> mutants;
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
mutants);

// The custom mutator just returns the original inputs as mutants.
EXPECT_EQ(inputs, mutants);
}

TEST(Centipede, FallsBackToBuiltInMutatorWithAutoMutatorFlag) {
TempDir temp_dir{test_info_->name()};
Environment env;
env.log_level = 0; // Disable most of the logging in the test.
env.workdir = temp_dir.path();
env.require_pc_table = false; // No PC table here.
env.binary =
absl::StrCat(GetDataDependencyFilepath(
"centipede/testing/fuzz_target_with_custom_mutator")
.c_str(),
" --simulate_failure");
env.mutator_type = MutatorType::kAuto;
CentipedeDefaultCallbacks callbacks(env);

const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
std::vector<ByteArray> mutants;
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
mutants);

// The built-in mutator performs non-trivial mutations.
EXPECT_EQ(inputs.size(), mutants.size());
EXPECT_NE(inputs, mutants);
}

TEST(Centipede, UsesBuiltInMutatorWithBuiltInMutatorFlag) {
TempDir temp_dir{test_info_->name()};
Environment env;
env.log_level = 0; // Disable most of the logging in the test.
env.workdir = temp_dir.path();
env.require_pc_table = false; // No PC table here.
env.binary = GetDataDependencyFilepath(
"centipede/testing/fuzz_target_with_custom_mutator");
env.mutator_type = MutatorType::kBuiltIn;
CentipedeDefaultCallbacks callbacks(env);

const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
std::vector<ByteArray> mutants;
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
mutants);

// The built-in mutator performs non-trivial mutations, while the custom
// mutator just returns the original inputs as mutants.
EXPECT_EQ(inputs.size(), mutants.size());
EXPECT_NE(inputs, mutants);
}

} // namespace centipede
38 changes: 38 additions & 0 deletions centipede/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,35 @@ size_t ComputeTimeoutPerBatch(size_t timeout_per_input, size_t batch_size) {

} // namespace

bool AbslParseFlag(std::string_view text, MutatorType *type,
std::string *error) {
if (text == "auto") {
*type = MutatorType::kAuto;
return true;
}
if (text == "built-in") {
*type = MutatorType::kBuiltIn;
return true;
}
if (text == "custom") {
*type = MutatorType::kCustom;
return true;
}
*error = "unknown value for mutator type";
return false;
}

std::string AbslUnparseFlag(MutatorType type) {
switch (type) {
case MutatorType::kAuto:
return "auto";
case MutatorType::kBuiltIn:
return "built-in";
case MutatorType::kCustom:
return "custom";
}
}

const Environment &Environment::Default() {
static absl::NoDestructor<Environment> default_env;
return *default_env;
Expand Down Expand Up @@ -250,6 +279,15 @@ void Environment::UpdateWithTargetConfig(
LOG(INFO) << "Overriding the default max_num_crash_reports to "
<< max_num_crash_reports << " for FuzzTest.";
}

// Use the custom mutator when running with FuzzTest to surface any errors
// with the mutation (e.g., ineffective filter domain).
if (mutator_type == Default().mutator_type) {
mutator_type = MutatorType::kCustom;
LOG(INFO) << "Overriding the default mutator_type to '"
<< AbslUnparseFlag(mutator_type) << "' for FuzzTest.";
}

if (config.jobs != 0) {
CHECK(j == Default().j || j == config.jobs)
<< "Value for --j is inconsistent with the value for jobs in the "
Expand Down
15 changes: 15 additions & 0 deletions centipede/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@

namespace centipede {

enum class MutatorType {
// Detect whether the binary supports custom mutator and use it if it does,
// otherwise fall back to the built-in mutator.
kAuto,
// Use the built-in ByteArray mutator.
kBuiltIn,
// Use the custom mutator provided by the binary.
kCustom
};

bool AbslParseFlag(std::string_view text, MutatorType* type,
std::string* error);
std::string AbslUnparseFlag(MutatorType type);

// Fuzzing environment controlling the behavior of
// CentipedeMain(). Centipede binaries are creating Environment instances using
// the flags defined in environment_flags.cc, while other users can use
Expand All @@ -52,6 +66,7 @@ struct Environment {
size_t batch_size = 1000;
size_t mutate_batch_size = 2;
bool use_legacy_default_mutator = false;
MutatorType mutator_type = MutatorType::kAuto;
size_t load_other_shard_frequency = 10;
bool serialize_shard_loads = false;
size_t seed = 0;
Expand Down
12 changes: 12 additions & 0 deletions centipede/environment_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "./common/logging.h"

using ::centipede::Environment;
using ::centipede::MutatorType;

// TODO(kcc): document usage of standalone binaries and how to use @@ wildcard.
// If the "binary" contains @@, it means the binary can only accept inputs
Expand Down Expand Up @@ -99,6 +100,17 @@ ABSL_FLAG(bool, use_legacy_default_mutator,
Environment::Default().use_legacy_default_mutator,
"When set, use the legacy ByteArrayMutator as the default mutator. "
"Otherwise, the FuzzTest domain based mutator will be used.");
ABSL_FLAG(
MutatorType, mutator_type, Environment::Default().mutator_type,
"The type of mutator to use: 'auto' (the default option), 'built-in', or "
"'custom'. With the 'built-in' option, Centipede will use the built-in "
"byte-array mutator. With the 'custom' option, Centipede will use the "
"custom mutator provided by the binary (e.g., all FuzzTest binaries "
"provide a custom mutator). If the binary doesn't provide one or fails for "
"any reason, Centipede will fail. With the 'auto' option, Centipede will "
"detect whether the binary provides a custom mutator and use it if it "
"does. If the binary doesn't provide a custom mutator or fails for any "
"reason, Centipede will fall back to the built-in mutator.");
ABSL_FLAG(size_t, load_other_shard_frequency,
Environment::Default().load_other_shard_frequency,
"Load a random other shard after processing this many batches. Use 0 "
Expand Down
23 changes: 23 additions & 0 deletions centipede/testing/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@ centipede_fuzz_target(
fuzz_target = "_fuzz_target_with_config",
)

# Fuzz target that uses the C++ RunnerCallbacks API to mutate inputs.
cc_binary(
name = "_fuzz_target_with_custom_mutator",
srcs = ["fuzz_target_with_custom_mutator.cc"],
# Cannot be built directly - build :fuzz_target_with_custom_mutator instead.
tags = [
"local",
"manual",
"notap",
],
deps = [
"@com_google_absl//absl/base:nullability",
"@com_google_fuzztest//centipede:centipede_runner_no_main",
"@com_google_fuzztest//centipede:mutation_input",
"@com_google_fuzztest//common:defs",
],
)

centipede_fuzz_target(
name = "fuzz_target_with_custom_mutator",
fuzz_target = "_fuzz_target_with_custom_mutator",
)

centipede_fuzz_target(
name = "minimize_me_fuzz_target",
)
Expand Down
50 changes: 50 additions & 0 deletions centipede/testing/fuzz_target_with_custom_mutator.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2025 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.

#include <cstdlib>
#include <cstring>
#include <functional>
#include <vector>

#include "absl/base/nullability.h"
#include "./centipede/mutation_input.h"
#include "./centipede/runner_interface.h"
#include "./common/defs.h"

using ::centipede::ByteSpan;

class CustomMutatorRunnerCallbacks : public centipede::RunnerCallbacks {
public:
bool Execute(ByteSpan input) override { return true; }

bool Mutate(const std::vector<centipede::MutationInputRef>& inputs,
size_t num_mutants,
std::function<void(ByteSpan)> new_mutant_callback) override {
size_t i = 0;
for (centipede::MutationInputRef input : inputs) {
if (i++ >= num_mutants) break;
// Just return the original input as a mutant.
new_mutant_callback(input.data);
}
return true;
}
};

int main(int argc, absl::Nonnull<char**> argv) {
if (argc >= 2 && std::strcmp(argv[1], "--simulate_failure") == 0) {
return EXIT_FAILURE;
}
CustomMutatorRunnerCallbacks runner_callbacks;
return centipede::RunnerMain(argc, argv, runner_callbacks);
}
8 changes: 0 additions & 8 deletions fuzztest/internal/centipede_adaptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -324,13 +324,6 @@ class CentipedeAdaptorRunnerCallbacks : public centipede::RunnerCallbacks {
prng_(GetRandomSeed()) {}

bool Execute(centipede::ByteSpan input) override {
if (!domain_setup_is_checked_) {
// Create a new domain input to trigger any domain setup
// failures here. (e.g. Ineffective Filter)
fuzzer_impl_.params_domain_.Init(prng_);
domain_setup_is_checked_ = true;
}

auto parsed_input =
fuzzer_impl_.TryParse({(char*)input.data(), input.size()});
if (parsed_input.ok()) {
Expand Down Expand Up @@ -457,7 +450,6 @@ class CentipedeAdaptorRunnerCallbacks : public centipede::RunnerCallbacks {
Runtime& runtime_;
FuzzTestFuzzerImpl& fuzzer_impl_;
const Configuration& configuration_;
bool domain_setup_is_checked_ = false;
std::unique_ptr<TablesOfRecentCompares> cmp_tables_;
absl::BitGen prng_;
};
Expand Down

0 comments on commit 705bcad

Please sign in to comment.