Skip to content

Commit 705bcad

Browse files
fniksiccopybara-github
authored andcommitted
Add a flag that forces failure on issues with the custom mutator.
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
1 parent 1d61720 commit 705bcad

File tree

9 files changed

+264
-16
lines changed

9 files changed

+264
-16
lines changed

centipede/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,7 @@ cc_test(
18281828
"@com_google_fuzztest//centipede/testing:abort_fuzz_target",
18291829
"@com_google_fuzztest//centipede/testing:expensive_startup_fuzz_target",
18301830
"@com_google_fuzztest//centipede/testing:fuzz_target_with_config",
1831+
"@com_google_fuzztest//centipede/testing:fuzz_target_with_custom_mutator",
18311832
"@com_google_fuzztest//centipede/testing:seeded_fuzz_target",
18321833
"@com_google_fuzztest//centipede/testing:test_fuzz_target",
18331834
"@com_google_fuzztest//centipede/testing:test_input_filter",

centipede/centipede_default_callbacks.cc

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,31 +77,41 @@ void CentipedeDefaultCallbacks::Mutate(
7777
std::vector<ByteArray> &mutants) {
7878
mutants.resize(num_mutants);
7979
if (num_mutants == 0) return;
80+
if (env_.mutator_type == MutatorType::kBuiltIn) {
81+
CentipedeCallbacks::Mutate(inputs, num_mutants, mutants);
82+
return;
83+
}
8084
// Try to use the custom mutator if it hasn't been disabled.
8185
if (custom_mutator_is_usable_.value_or(true)) {
8286
if (MutateViaExternalBinary(env_.binary, inputs, mutants)) {
8387
if (!custom_mutator_is_usable_.has_value()) {
84-
LOG(INFO) << "Custom mutator detected: will use it";
88+
LOG_IF(INFO, env_.mutator_type == MutatorType::kAuto)
89+
<< "Custom mutator detected: will use it";
8590
custom_mutator_is_usable_ = true;
8691
}
8792
if (!mutants.empty()) return;
88-
LOG_FIRST_N(WARNING, 5)
89-
<< "Custom mutator returned no mutants: falling back to internal "
90-
"default mutator";
93+
LOG_IF(FATAL, env_.mutator_type == MutatorType::kCustom)
94+
<< "Custom mutator returned no mutants.";
95+
LOG_FIRST_N(WARNING, 5) << "Custom mutator returned no mutants: falling "
96+
"back to the built-in mutator";
9197
} else if (ShouldStop()) {
9298
LOG(WARNING) << "Custom mutator failed, but ignored since the stop "
9399
"condition it met. Possibly what triggered the stop "
94100
"condition also interrupted the mutator.";
95101
return;
96102
} else {
97-
LOG(WARNING) << "Custom mutator undetected or misbehaving:";
98-
CHECK(!custom_mutator_is_usable_.has_value())
99-
<< "Custom mutator is unreliable, aborting";
100-
LOG(WARNING) << "Falling back to internal default mutator";
103+
LOG_IF(FATAL, env_.mutator_type == MutatorType::kCustom ||
104+
custom_mutator_is_usable_.has_value())
105+
<< "Custom mutator failed, and it was expected to succeed.";
106+
LOG(WARNING) << "Custom mutator undetected or misbehaving. Falling back "
107+
"to the built-in mutator.";
101108
custom_mutator_is_usable_ = false;
102109
}
103110
}
104111
// Fallback of the internal mutator.
112+
CHECK(env_.mutator_type == MutatorType::kAuto)
113+
<< "Internal error: unexpected mutator type "
114+
<< AbslUnparseFlag(env_.mutator_type);
105115
CentipedeCallbacks::Mutate(inputs, num_mutants, mutants);
106116
}
107117

centipede/centipede_test.cc

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,4 +1028,111 @@ TEST(Centipede, AbortsOnSetupFailure) {
10281028
"Terminating Centipede due to setup failure in the test.");
10291029
}
10301030

1031+
TEST(Centipede, UsesCustomMutatorWithCustomMutatorFlag) {
1032+
TempDir temp_dir{test_info_->name()};
1033+
Environment env;
1034+
env.log_level = 0; // Disable most of the logging in the test.
1035+
env.workdir = temp_dir.path();
1036+
env.require_pc_table = false; // No PC table here.
1037+
env.binary = GetDataDependencyFilepath(
1038+
"centipede/testing/fuzz_target_with_custom_mutator");
1039+
env.mutator_type = MutatorType::kCustom;
1040+
CentipedeDefaultCallbacks callbacks(env);
1041+
1042+
const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
1043+
std::vector<ByteArray> mutants;
1044+
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
1045+
mutants);
1046+
1047+
// The custom mutator just returns the original inputs as mutants.
1048+
EXPECT_EQ(inputs, mutants);
1049+
}
1050+
1051+
TEST(Centipede, FailsOnMisbehavingMutatorWithCustomMutatorFlag) {
1052+
TempDir temp_dir{test_info_->name()};
1053+
Environment env;
1054+
env.log_level = 0; // Disable most of the logging in the test.
1055+
env.workdir = temp_dir.path();
1056+
env.require_pc_table = false; // No PC table here.
1057+
env.binary =
1058+
absl::StrCat(GetDataDependencyFilepath(
1059+
"centipede/testing/fuzz_target_with_custom_mutator")
1060+
.c_str(),
1061+
" --simulate_failure");
1062+
env.mutator_type = MutatorType::kCustom;
1063+
CentipedeDefaultCallbacks callbacks(env);
1064+
1065+
const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
1066+
std::vector<ByteArray> mutants;
1067+
EXPECT_DEATH(callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs),
1068+
inputs.size(), mutants),
1069+
"Custom mutator failed");
1070+
}
1071+
1072+
TEST(Centipede, DetectsCustomMutatorWithAutoMutatorFlag) {
1073+
TempDir temp_dir{test_info_->name()};
1074+
Environment env;
1075+
env.log_level = 0; // Disable most of the logging in the test.
1076+
env.workdir = temp_dir.path();
1077+
env.require_pc_table = false; // No PC table here.
1078+
env.binary = GetDataDependencyFilepath(
1079+
"centipede/testing/fuzz_target_with_custom_mutator");
1080+
env.mutator_type = MutatorType::kAuto;
1081+
CentipedeDefaultCallbacks callbacks(env);
1082+
1083+
const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
1084+
std::vector<ByteArray> mutants;
1085+
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
1086+
mutants);
1087+
1088+
// The custom mutator just returns the original inputs as mutants.
1089+
EXPECT_EQ(inputs, mutants);
1090+
}
1091+
1092+
TEST(Centipede, FallsBackToBuiltInMutatorWithAutoMutatorFlag) {
1093+
TempDir temp_dir{test_info_->name()};
1094+
Environment env;
1095+
env.log_level = 0; // Disable most of the logging in the test.
1096+
env.workdir = temp_dir.path();
1097+
env.require_pc_table = false; // No PC table here.
1098+
env.binary =
1099+
absl::StrCat(GetDataDependencyFilepath(
1100+
"centipede/testing/fuzz_target_with_custom_mutator")
1101+
.c_str(),
1102+
" --simulate_failure");
1103+
env.mutator_type = MutatorType::kAuto;
1104+
CentipedeDefaultCallbacks callbacks(env);
1105+
1106+
const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
1107+
std::vector<ByteArray> mutants;
1108+
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
1109+
mutants);
1110+
1111+
// The built-in mutator performs non-trivial mutations.
1112+
EXPECT_EQ(inputs.size(), mutants.size());
1113+
EXPECT_NE(inputs, mutants);
1114+
}
1115+
1116+
TEST(Centipede, UsesBuiltInMutatorWithBuiltInMutatorFlag) {
1117+
TempDir temp_dir{test_info_->name()};
1118+
Environment env;
1119+
env.log_level = 0; // Disable most of the logging in the test.
1120+
env.workdir = temp_dir.path();
1121+
env.require_pc_table = false; // No PC table here.
1122+
env.binary = GetDataDependencyFilepath(
1123+
"centipede/testing/fuzz_target_with_custom_mutator");
1124+
env.mutator_type = MutatorType::kBuiltIn;
1125+
CentipedeDefaultCallbacks callbacks(env);
1126+
1127+
const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
1128+
std::vector<ByteArray> mutants;
1129+
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size(),
1130+
mutants);
1131+
1132+
// The built-in mutator performs non-trivial mutations, while the custom
1133+
// mutator just returns the original inputs as mutants.
1134+
EXPECT_EQ(inputs.size(), mutants.size());
1135+
EXPECT_NE(inputs, mutants);
1136+
}
1137+
10311138
} // namespace centipede

centipede/environment.cc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,35 @@ size_t ComputeTimeoutPerBatch(size_t timeout_per_input, size_t batch_size) {
6565

6666
} // namespace
6767

68+
bool AbslParseFlag(std::string_view text, MutatorType *type,
69+
std::string *error) {
70+
if (text == "auto") {
71+
*type = MutatorType::kAuto;
72+
return true;
73+
}
74+
if (text == "built-in") {
75+
*type = MutatorType::kBuiltIn;
76+
return true;
77+
}
78+
if (text == "custom") {
79+
*type = MutatorType::kCustom;
80+
return true;
81+
}
82+
*error = "unknown value for mutator type";
83+
return false;
84+
}
85+
86+
std::string AbslUnparseFlag(MutatorType type) {
87+
switch (type) {
88+
case MutatorType::kAuto:
89+
return "auto";
90+
case MutatorType::kBuiltIn:
91+
return "built-in";
92+
case MutatorType::kCustom:
93+
return "custom";
94+
}
95+
}
96+
6897
const Environment &Environment::Default() {
6998
static absl::NoDestructor<Environment> default_env;
7099
return *default_env;
@@ -250,6 +279,15 @@ void Environment::UpdateWithTargetConfig(
250279
LOG(INFO) << "Overriding the default max_num_crash_reports to "
251280
<< max_num_crash_reports << " for FuzzTest.";
252281
}
282+
283+
// Use the custom mutator when running with FuzzTest to surface any errors
284+
// with the mutation (e.g., ineffective filter domain).
285+
if (mutator_type == Default().mutator_type) {
286+
mutator_type = MutatorType::kCustom;
287+
LOG(INFO) << "Overriding the default mutator_type to '"
288+
<< AbslUnparseFlag(mutator_type) << "' for FuzzTest.";
289+
}
290+
253291
if (config.jobs != 0) {
254292
CHECK(j == Default().j || j == config.jobs)
255293
<< "Value for --j is inconsistent with the value for jobs in the "

centipede/environment.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@
3030

3131
namespace centipede {
3232

33+
enum class MutatorType {
34+
// Detect whether the binary supports custom mutator and use it if it does,
35+
// otherwise fall back to the built-in mutator.
36+
kAuto,
37+
// Use the built-in ByteArray mutator.
38+
kBuiltIn,
39+
// Use the custom mutator provided by the binary.
40+
kCustom
41+
};
42+
43+
bool AbslParseFlag(std::string_view text, MutatorType* type,
44+
std::string* error);
45+
std::string AbslUnparseFlag(MutatorType type);
46+
3347
// Fuzzing environment controlling the behavior of
3448
// CentipedeMain(). Centipede binaries are creating Environment instances using
3549
// the flags defined in environment_flags.cc, while other users can use
@@ -52,6 +66,7 @@ struct Environment {
5266
size_t batch_size = 1000;
5367
size_t mutate_batch_size = 2;
5468
bool use_legacy_default_mutator = false;
69+
MutatorType mutator_type = MutatorType::kAuto;
5570
size_t load_other_shard_frequency = 10;
5671
bool serialize_shard_loads = false;
5772
size_t seed = 0;

centipede/environment_flags.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "./common/logging.h"
3232

3333
using ::centipede::Environment;
34+
using ::centipede::MutatorType;
3435

3536
// TODO(kcc): document usage of standalone binaries and how to use @@ wildcard.
3637
// If the "binary" contains @@, it means the binary can only accept inputs
@@ -99,6 +100,17 @@ ABSL_FLAG(bool, use_legacy_default_mutator,
99100
Environment::Default().use_legacy_default_mutator,
100101
"When set, use the legacy ByteArrayMutator as the default mutator. "
101102
"Otherwise, the FuzzTest domain based mutator will be used.");
103+
ABSL_FLAG(
104+
MutatorType, mutator_type, Environment::Default().mutator_type,
105+
"The type of mutator to use: 'auto' (the default option), 'built-in', or "
106+
"'custom'. With the 'built-in' option, Centipede will use the built-in "
107+
"byte-array mutator. With the 'custom' option, Centipede will use the "
108+
"custom mutator provided by the binary (e.g., all FuzzTest binaries "
109+
"provide a custom mutator). If the binary doesn't provide one or fails for "
110+
"any reason, Centipede will fail. With the 'auto' option, Centipede will "
111+
"detect whether the binary provides a custom mutator and use it if it "
112+
"does. If the binary doesn't provide a custom mutator or fails for any "
113+
"reason, Centipede will fall back to the built-in mutator.");
102114
ABSL_FLAG(size_t, load_other_shard_frequency,
103115
Environment::Default().load_other_shard_frequency,
104116
"Load a random other shard after processing this many batches. Use 0 "

centipede/testing/BUILD

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,29 @@ centipede_fuzz_target(
6464
fuzz_target = "_fuzz_target_with_config",
6565
)
6666

67+
# Fuzz target that uses the C++ RunnerCallbacks API to mutate inputs.
68+
cc_binary(
69+
name = "_fuzz_target_with_custom_mutator",
70+
srcs = ["fuzz_target_with_custom_mutator.cc"],
71+
# Cannot be built directly - build :fuzz_target_with_custom_mutator instead.
72+
tags = [
73+
"local",
74+
"manual",
75+
"notap",
76+
],
77+
deps = [
78+
"@com_google_absl//absl/base:nullability",
79+
"@com_google_fuzztest//centipede:centipede_runner_no_main",
80+
"@com_google_fuzztest//centipede:mutation_input",
81+
"@com_google_fuzztest//common:defs",
82+
],
83+
)
84+
85+
centipede_fuzz_target(
86+
name = "fuzz_target_with_custom_mutator",
87+
fuzz_target = "_fuzz_target_with_custom_mutator",
88+
)
89+
6790
centipede_fuzz_target(
6891
name = "minimize_me_fuzz_target",
6992
)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2025 The Centipede Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <cstdlib>
16+
#include <cstring>
17+
#include <functional>
18+
#include <vector>
19+
20+
#include "absl/base/nullability.h"
21+
#include "./centipede/mutation_input.h"
22+
#include "./centipede/runner_interface.h"
23+
#include "./common/defs.h"
24+
25+
using ::centipede::ByteSpan;
26+
27+
class CustomMutatorRunnerCallbacks : public centipede::RunnerCallbacks {
28+
public:
29+
bool Execute(ByteSpan input) override { return true; }
30+
31+
bool Mutate(const std::vector<centipede::MutationInputRef>& inputs,
32+
size_t num_mutants,
33+
std::function<void(ByteSpan)> new_mutant_callback) override {
34+
size_t i = 0;
35+
for (centipede::MutationInputRef input : inputs) {
36+
if (i++ >= num_mutants) break;
37+
// Just return the original input as a mutant.
38+
new_mutant_callback(input.data);
39+
}
40+
return true;
41+
}
42+
};
43+
44+
int main(int argc, absl::Nonnull<char**> argv) {
45+
if (argc >= 2 && std::strcmp(argv[1], "--simulate_failure") == 0) {
46+
return EXIT_FAILURE;
47+
}
48+
CustomMutatorRunnerCallbacks runner_callbacks;
49+
return centipede::RunnerMain(argc, argv, runner_callbacks);
50+
}

fuzztest/internal/centipede_adaptor.cc

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,6 @@ class CentipedeAdaptorRunnerCallbacks : public centipede::RunnerCallbacks {
324324
prng_(GetRandomSeed()) {}
325325

326326
bool Execute(centipede::ByteSpan input) override {
327-
if (!domain_setup_is_checked_) {
328-
// Create a new domain input to trigger any domain setup
329-
// failures here. (e.g. Ineffective Filter)
330-
fuzzer_impl_.params_domain_.Init(prng_);
331-
domain_setup_is_checked_ = true;
332-
}
333-
334327
auto parsed_input =
335328
fuzzer_impl_.TryParse({(char*)input.data(), input.size()});
336329
if (parsed_input.ok()) {
@@ -457,7 +450,6 @@ class CentipedeAdaptorRunnerCallbacks : public centipede::RunnerCallbacks {
457450
Runtime& runtime_;
458451
FuzzTestFuzzerImpl& fuzzer_impl_;
459452
const Configuration& configuration_;
460-
bool domain_setup_is_checked_ = false;
461453
std::unique_ptr<TablesOfRecentCompares> cmp_tables_;
462454
absl::BitGen prng_;
463455
};

0 commit comments

Comments
 (0)