Move TempDir to io lib so it can be reused.
PiperOrigin-RevId: 591024118
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc
index c4cb79d..7cadbab 100644
--- a/e2e_tests/functional_test.cc
+++ b/e2e_tests/functional_test.cc
@@ -86,22 +86,6 @@
return binary_path;
}
-class TempDir {
- public:
- TempDir() {
- dirname_ = "/tmp/replay_test_XXXXXX";
- dirname_ = mkdtemp(dirname_.data());
- EXPECT_TRUE(std::filesystem::is_directory(dirname_));
- }
-
- const std::string& dirname() const { return dirname_; }
-
- ~TempDir() { std::filesystem::remove_all(dirname_); }
-
- private:
- std::string dirname_;
-};
-
class UnitTestModeTest : public ::testing::Test {
protected:
void SetUp() override {
@@ -683,11 +667,11 @@
auto [status, std_out, std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_REPRODUCERS_OUT_DIR", out_dir.dirname()}});
+ {{"FUZZTEST_REPRODUCERS_OUT_DIR", out_dir.path()}});
EXPECT_THAT(std_err, HasSubstr("argument 0: \"Fuzz"));
EXPECT_THAT(status, Eq(Signal(SIGABRT)));
- auto replay_files = ReadFileOrDirectory(out_dir.dirname());
+ auto replay_files = ReadFileOrDirectory(out_dir.path());
ASSERT_EQ(replay_files.size(), 1) << std_err;
auto parsed = IRObject::FromString(replay_files[0].data);
ASSERT_TRUE(parsed) << std_err;
@@ -704,9 +688,9 @@
// find the crash without saving some corpus.
auto [status, std_out, std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_TESTSUITE_OUT_DIR", out_dir.dirname()}});
+ {{"FUZZTEST_TESTSUITE_OUT_DIR", out_dir.path()}});
- auto corpus_files = ReadFileOrDirectory(out_dir.dirname());
+ auto corpus_files = ReadFileOrDirectory(out_dir.path());
EXPECT_THAT(corpus_files, Not(IsEmpty())) << std_err;
}
@@ -719,14 +703,14 @@
// find the crash without saving some corpus.
auto [producer_status, producer_std_out, producer_std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.dirname()}});
+ {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}});
- auto corpus_files = ReadFileOrDirectory(corpus_dir.dirname());
+ auto corpus_files = ReadFileOrDirectory(corpus_dir.path());
ASSERT_THAT(corpus_files, Not(IsEmpty())) << producer_std_err;
auto [consumer_status, consumer_std_out, consumer_std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_TESTSUITE_IN_DIR", corpus_dir.dirname()}});
+ {{"FUZZTEST_TESTSUITE_IN_DIR", corpus_dir.path()}});
EXPECT_THAT(consumer_std_err,
HasSubstr(absl::StrFormat("Parsed %d inputs and ignored 0 inputs",
corpus_files.size())));
@@ -742,9 +726,9 @@
// find the crash without saving some corpus.
auto [producer_status, producer_std_out, producer_std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.dirname()}});
+ {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}});
- auto corpus_files = ReadFileOrDirectory(corpus_dir.dirname());
+ auto corpus_files = ReadFileOrDirectory(corpus_dir.path());
ASSERT_THAT(corpus_files, Not(IsEmpty())) << producer_std_err;
std::vector<std::string> corpus_data;
for (const FilePathAndData& corpus_file : corpus_files) {
@@ -753,11 +737,11 @@
auto [minimizer_status, minimizer_std_out, minimizer_std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_MINIMIZE_TESTSUITE_DIR", corpus_dir.dirname()},
- {"FUZZTEST_TESTSUITE_OUT_DIR", minimized_corpus_dir.dirname()}});
+ {{"FUZZTEST_MINIMIZE_TESTSUITE_DIR", corpus_dir.path()},
+ {"FUZZTEST_TESTSUITE_OUT_DIR", minimized_corpus_dir.path()}});
auto minimized_corpus_files =
- ReadFileOrDirectory(minimized_corpus_dir.dirname());
+ ReadFileOrDirectory(minimized_corpus_dir.path());
EXPECT_THAT(minimized_corpus_files,
AllOf(Not(IsEmpty()), SizeIs(Le(corpus_files.size()))))
<< minimizer_std_err;
@@ -786,9 +770,9 @@
// find the crash without saving some corpus.
auto [producer_status, producer_std_out, producer_std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.dirname()}});
+ {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}});
- auto corpus_files = ReadFileOrDirectory(corpus_dir.dirname());
+ auto corpus_files = ReadFileOrDirectory(corpus_dir.path());
ASSERT_THAT(corpus_files, Not(IsEmpty())) << producer_std_err;
for (const auto& corpus_file : corpus_files) {
ASSERT_TRUE(WriteFile(corpus_file.path + "_dup", corpus_file.data));
@@ -796,11 +780,11 @@
auto [minimizer_status, minimizer_std_out, minimizer_std_err] =
RunWith({{"fuzz", "MySuite.String"}},
- {{"FUZZTEST_MINIMIZE_TESTSUITE_DIR", corpus_dir.dirname()},
- {"FUZZTEST_TESTSUITE_OUT_DIR", minimized_corpus_dir.dirname()}});
+ {{"FUZZTEST_MINIMIZE_TESTSUITE_DIR", corpus_dir.path()},
+ {"FUZZTEST_TESTSUITE_OUT_DIR", minimized_corpus_dir.path()}});
auto minimized_corpus_files =
- ReadFileOrDirectory(minimized_corpus_dir.dirname());
+ ReadFileOrDirectory(minimized_corpus_dir.path());
EXPECT_THAT(minimized_corpus_files,
AllOf(Not(IsEmpty()), SizeIs(Le(corpus_files.size()))))
<< minimizer_std_err;
@@ -828,7 +812,7 @@
public:
template <typename T>
ReplayFile(std::in_place_t, const T& corpus) {
- filename_ = absl::StrCat(dir_.dirname(), "/replay_file");
+ filename_ = absl::StrCat(dir_.path(), "/replay_file");
WriteFile(filename_, internal::IRObject::FromCorpus(corpus).ToString());
}
@@ -876,11 +860,11 @@
auto [status, std_out, std_err] =
RunWith({{"fuzz", "MySuite.WithDomainClass"}},
- {{"FUZZTEST_REPRODUCERS_OUT_DIR", out_dir.dirname()}});
+ {{"FUZZTEST_REPRODUCERS_OUT_DIR", out_dir.path()}});
EXPECT_THAT(std_err, HasSubstr("argument 0: 10")) << std_err;
EXPECT_THAT(status, Ne(ExitCode(0))) << std_err;
- auto replay_files = ReadFileOrDirectory(out_dir.dirname());
+ auto replay_files = ReadFileOrDirectory(out_dir.path());
ASSERT_EQ(replay_files.size(), 1) << std_err;
auto parsed = IRObject::FromString(replay_files[0].data);
ASSERT_TRUE(parsed) << std_err;
@@ -913,14 +897,14 @@
TempDir out_dir;
ReplayFile replay(std::in_place, std::tuple<std::string>{current_input});
auto env = replay.GetMinimizeEnv();
- env["FUZZTEST_REPRODUCERS_OUT_DIR"] = out_dir.dirname();
+ env["FUZZTEST_REPRODUCERS_OUT_DIR"] = out_dir.path();
auto [status, std_out, std_err] =
RunWith({{"fuzz", "MySuite.Minimizer"}}, env);
ASSERT_THAT(std_err, HasSubstr("argument 0: \""));
ASSERT_THAT(status, Eq(Signal(SIGABRT)));
- auto replay_files = ReadFileOrDirectory(out_dir.dirname());
+ auto replay_files = ReadFileOrDirectory(out_dir.path());
ASSERT_EQ(replay_files.size(), 1) << std_err;
auto parsed = IRObject::FromString(replay_files[0].data);
ASSERT_TRUE(parsed) << std_err;
@@ -1079,7 +1063,7 @@
TempDir workdir;
return RunCommand(
{CentipedePath(), "--print_runner_log", "--exit_on_crash",
- absl::StrCat("--workdir=", workdir.dirname()),
+ absl::StrCat("--workdir=", workdir.path()),
absl::StrCat("--binary=", BinaryPath(kDefaultTargetBinary), " ",
CreateFuzzTestFlag("fuzz", test_name)),
absl::StrCat("--num_runs=", iterations)},
@@ -1199,7 +1183,7 @@
environment["ASAN_OPTIONS"] = "handle_aborts=0";
return RunCommand({CentipedePath(), "--exit_on_crash",
absl::StrCat("--stop_at=", absl::Now() + timeout),
- absl::StrCat("--workdir=", workdir.dirname()),
+ absl::StrCat("--workdir=", workdir.path()),
absl::StrCat("--binary=", BinaryPath(target_binary), " ",
CreateFuzzTestFlag("fuzz", test_name))},
environment, timeout + absl::Seconds(10));
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index f615e1c..d5e6527 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -155,17 +155,33 @@
srcs = ["internal/centipede_adaptor.cc"],
hdrs = ["internal/centipede_adaptor.h"],
defines = ["FUZZTEST_USE_CENTIPEDE"],
+ linkopts = [
+ # Needed for linking the Centipede engine with the runner, due to
+ # the common source code built separately for the engine and runner.
+ "-Wl,--warn-backrefs-exclude=*/centipede/*",
+ ],
deps = [
":configuration",
":coverage",
":domain_core",
":fixture_driver",
+ ":io",
":logging",
":runtime",
"@com_google_absl//absl/algorithm:container",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/strings:string_view",
+ "@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
+ "@com_google_fuzztest//centipede:centipede_callbacks",
+ "@com_google_fuzztest//centipede:centipede_interface",
"@com_google_fuzztest//centipede:centipede_runner_no_main",
+ "@com_google_fuzztest//centipede:defs",
+ "@com_google_fuzztest//centipede:early_exit",
+ "@com_google_fuzztest//centipede:environment",
+ "@com_google_fuzztest//centipede:runner_result",
+ "@com_google_fuzztest//centipede:shared_memory_blob_sequence",
],
)
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc
index 46eb4ab..c506909 100644
--- a/fuzztest/internal/centipede_adaptor.cc
+++ b/fuzztest/internal/centipede_adaptor.cc
@@ -14,8 +14,12 @@
#include "./fuzztest/internal/centipede_adaptor.h"
+#include <sys/mman.h>
+
#include <cstddef>
#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
#include <cstring>
#include <functional>
#include <memory>
@@ -26,12 +30,26 @@
#include <vector>
#include "absl/algorithm/container.h"
+#include "absl/strings/match.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
#include "absl/types/span.h"
+#include "./centipede/centipede_callbacks.h"
+#include "./centipede/centipede_interface.h"
+#include "./centipede/defs.h"
+#include "./centipede/early_exit.h"
+#include "./centipede/environment.h"
#include "./centipede/runner_interface.h"
+#include "./centipede/runner_result.h"
+#include "./centipede/shared_memory_blob_sequence.h"
#include "./fuzztest/internal/configuration.h"
#include "./fuzztest/internal/coverage.h"
#include "./fuzztest/internal/domains/domain_base.h"
+#include "./fuzztest/internal/io.h"
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/runtime.h"
@@ -45,16 +63,59 @@
return std::seed_seq({seed, seed >> 32});
}
+centipede::Environment CreateDefaultCentipedeEnvironment() {
+ centipede::Environment env;
+ // Skip input timeout as we don't yet support it in FuzzTest.
+ env.timeout_per_input = 0;
+ // Do not limit the address space as the fuzzing engine needs a
+ // lot of address space. rss_limit_mb will be used for OOM
+ // detection.
+ env.address_space_limit_mb = 0;
+ return env;
+}
+
+centipede::Environment CreateCentipedeEnvironmentFromFuzzTestFlags(
+ const Runtime& runtime, absl::string_view workdir,
+ absl::string_view test_name) {
+ centipede::Environment env = CreateDefaultCentipedeEnvironment();
+ env.workdir = workdir;
+ env.exit_on_crash = true;
+ // Populating the PC table in single-process mode is not implemented.
+ env.require_pc_table = false;
+ if (runtime.fuzz_time_limit() != absl::InfiniteDuration()) {
+ absl::FPrintF(GetStderr(), "[.] Fuzzing timeout set to: %s\n",
+ absl::FormatDuration(runtime.fuzz_time_limit()));
+ env.stop_at = absl::Now() + runtime.fuzz_time_limit();
+ }
+ env.first_corpus_dir_output_only = true;
+ if (const char* corpus_out_dir_chars = getenv("FUZZTEST_TESTSUITE_OUT_DIR")) {
+ env.corpus_dir.push_back(corpus_out_dir_chars);
+ } else {
+ env.corpus_dir.push_back("");
+ }
+ if (const char* corpus_in_dir_chars = getenv("FUZZTEST_TESTSUITE_IN_DIR"))
+ env.corpus_dir.push_back(corpus_in_dir_chars);
+ if (const char* max_fuzzing_runs = getenv("FUZZTEST_MAX_FUZZING_RUNS")) {
+ if (!absl::SimpleAtoi(max_fuzzing_runs, &env.num_runs)) {
+ absl::FPrintF(GetStderr(),
+ "[!] Cannot parse env FUZZTEST_MAX_FUZZING_RUNS=%s - will "
+ "not limit fuzzing runs.\n",
+ max_fuzzing_runs);
+ }
+ }
+ return env;
+}
+
} // namespace
class CentipedeAdaptorRunnerCallbacks : public centipede::RunnerCallbacks {
public:
CentipedeAdaptorRunnerCallbacks(Runtime& runtime,
FuzzTestFuzzerImpl& fuzzer_impl,
- const Configuration& configuration)
+ const Configuration* configuration)
: runtime_(runtime),
fuzzer_impl_(fuzzer_impl),
- configuration_(configuration),
+ configuration_(*configuration),
prng_(GetRandomSeed()) {
if (GetExecutionCoverage() == nullptr) {
execution_coverage_ = std::make_unique<ExecutionCoverage>(
@@ -62,14 +123,6 @@
execution_coverage_->SetIsTracing(true);
SetExecutionCoverage(execution_coverage_.get());
}
- runtime_.EnableReporter(&fuzzer_impl_.stats_, [] { return absl::Now(); });
- if (IsSilenceTargetEnabled()) SilenceTargetStdoutAndStderr();
- FUZZTEST_INTERNAL_CHECK(fuzzer_impl_.fixture_driver_ != nullptr,
- "Invalid fixture driver!");
- fuzzer_impl_.fixture_driver_->SetUpFuzzTest();
- // Always create a new domain input to trigger any domain setup
- // failures here. (e.g. Ineffective Filter)
- fuzzer_impl_.params_domain_->UntypedInit(prng_);
}
bool Execute(centipede::ByteSpan input) override {
@@ -139,9 +192,6 @@
}
~CentipedeAdaptorRunnerCallbacks() override {
- FUZZTEST_INTERNAL_CHECK(fuzzer_impl_.fixture_driver_ != nullptr,
- "Invalid fixture driver!");
- fuzzer_impl_.fixture_driver_->TearDownFuzzTest();
if (GetExecutionCoverage() == execution_coverage_.get())
SetExecutionCoverage(nullptr);
}
@@ -170,11 +220,130 @@
Runtime& runtime_;
FuzzTestFuzzerImpl& fuzzer_impl_;
- Configuration configuration_;
+ const Configuration& configuration_;
std::unique_ptr<ExecutionCoverage> execution_coverage_;
absl::BitGen prng_;
};
+namespace {
+
+class CentipedeAdaptorEngineCallbacks : public centipede::CentipedeCallbacks {
+ public:
+ CentipedeAdaptorEngineCallbacks(const centipede::Environment& env,
+ Runtime& runtime,
+ FuzzTestFuzzerImpl& fuzzer_impl,
+ const Configuration* configuration)
+ : centipede::CentipedeCallbacks(env),
+ runtime_(runtime),
+ runner_callbacks_(runtime, fuzzer_impl, configuration),
+ batch_result_buffer_size_(env.shmem_size_mb * 1024 * 1024),
+ batch_result_buffer_(nullptr) {}
+
+ ~CentipedeAdaptorEngineCallbacks() {
+ if (batch_result_buffer_ != nullptr)
+ munmap(batch_result_buffer_, batch_result_buffer_size_);
+ }
+
+ bool Execute(std::string_view binary,
+ const std::vector<centipede::ByteArray>& inputs,
+ centipede::BatchResult& batch_result) override {
+ // Execute the test in-process.
+ batch_result.ClearAndResize(inputs.size());
+ size_t buffer_offset = 0;
+ if (batch_result_buffer_ == nullptr) {
+ // Use mmap which allocates memory on demand to reduce sanitizer overhead.
+ batch_result_buffer_ = static_cast<uint8_t*>(
+ mmap(nullptr, batch_result_buffer_size_, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+ FUZZTEST_INTERNAL_CHECK(
+ batch_result_buffer_ != MAP_FAILED,
+ "Cannot mmap anonymous memory for batch result buffer");
+ }
+ CentipedeBeginExecutionBatch();
+ for (const auto& input : inputs) {
+ if (runtime_.termination_requested()) break;
+ if (buffer_offset >= batch_result_buffer_size_) break;
+ CentipedePrepareProcessing();
+ runner_callbacks_.Execute(input);
+ CentipedeFinalizeProcessing();
+ buffer_offset += CentipedeGetExecutionResult(
+ batch_result_buffer_ + buffer_offset,
+ batch_result_buffer_size_ - buffer_offset);
+ }
+ CentipedeEndExecutionBatch();
+ if (buffer_offset > 0) {
+ centipede::BlobSequence batch_result_blobseq(batch_result_buffer_,
+ buffer_offset);
+ batch_result.Read(batch_result_blobseq);
+ }
+ if (runtime_.termination_requested() && !centipede::EarlyExitRequested()) {
+ absl::FPrintF(GetStderr(), "[.] Early termination requested.\n");
+ centipede::RequestEarlyExit(0);
+ }
+ return true;
+ }
+
+ size_t GetSeeds(size_t num_seeds,
+ std::vector<centipede::ByteArray>& seeds) override {
+ seeds.clear();
+ size_t num_avail_seeds = 0;
+ runner_callbacks_.GetSeeds([&](centipede::ByteSpan seed) {
+ ++num_avail_seeds;
+ if (seeds.size() < num_seeds) {
+ seeds.emplace_back(seed.begin(), seed.end());
+ }
+ });
+ return num_avail_seeds;
+ }
+
+ void Mutate(const std::vector<centipede::MutationInputRef>& inputs,
+ size_t num_mutants,
+ std::vector<centipede::ByteArray>& mutants) override {
+ mutants.clear();
+ runner_callbacks_.Mutate(
+ inputs, num_mutants, [&](centipede::ByteSpan mutant) {
+ mutants.emplace_back(mutant.begin(), mutant.end());
+ });
+ if (runtime_.termination_requested() && !centipede::EarlyExitRequested()) {
+ absl::FPrintF(GetStderr(), "[.] Early termination requested.\n");
+ centipede::RequestEarlyExit(0);
+ }
+ }
+
+ private:
+ Runtime& runtime_;
+ CentipedeAdaptorRunnerCallbacks runner_callbacks_;
+ size_t batch_result_buffer_size_;
+ uint8_t* batch_result_buffer_;
+ std::unique_ptr<ExecutionCoverage> execution_coverage_;
+};
+
+class CentipedeAdaptorEngineCallbacksFactory
+ : public centipede::CentipedeCallbacksFactory {
+ public:
+ CentipedeAdaptorEngineCallbacksFactory(Runtime& runtime,
+ FuzzTestFuzzerImpl& fuzzer_impl,
+ const Configuration* configuration)
+ : runtime_(runtime),
+ fuzzer_impl_(fuzzer_impl),
+ configuration_(configuration) {}
+
+ centipede::CentipedeCallbacks* create(
+ const centipede::Environment& env) override {
+ return new CentipedeAdaptorEngineCallbacks(env, runtime_, fuzzer_impl_,
+ configuration_);
+ }
+
+ void destroy(centipede::CentipedeCallbacks* callbacks) { delete callbacks; }
+
+ private:
+ Runtime& runtime_;
+ FuzzTestFuzzerImpl& fuzzer_impl_;
+ const Configuration* configuration_;
+};
+
+} // namespace
+
CentipedeFuzzerAdaptor::CentipedeFuzzerAdaptor(
const FuzzTest& test, std::unique_ptr<UntypedFixtureDriver> fixture_driver)
: test_(test), fuzzer_impl_(test_, std::move(fixture_driver)) {}
@@ -187,13 +356,100 @@
int CentipedeFuzzerAdaptor::RunInFuzzingMode(
int* argc, char*** argv, const Configuration& configuration) {
+ FUZZTEST_INTERNAL_CHECK(fuzzer_impl_.fixture_driver_ != nullptr,
+ "Invalid fixture driver!");
runtime_.SetRunMode(RunMode::kFuzz);
runtime_.SetCurrentTest(&test_);
- CentipedeAdaptorRunnerCallbacks runner_callback(runtime_, fuzzer_impl_,
- configuration);
- return centipede::RunnerMain(argc != nullptr ? *argc : 0,
- argv != nullptr ? *argv : nullptr,
- runner_callback);
+ if (IsSilenceTargetEnabled()) SilenceTargetStdoutAndStderr();
+ runtime_.EnableReporter(&fuzzer_impl_.stats_, [] { return absl::Now(); });
+ fuzzer_impl_.fixture_driver_->SetUpFuzzTest();
+ // Always create a new domain input to trigger any domain setup
+ // failures here. (e.g. Ineffective Filter)
+ FuzzTestFuzzerImpl::PRNG prng;
+ fuzzer_impl_.params_domain_->UntypedInit(prng);
+ // When the CENTIPEDE_RUNNER_FLAGS env var exists, the current process is
+ // considered a child process spawned by the Centipede binary as the runner,
+ // and we should not run CentipedeMain in this process.
+ const bool runner_mode = getenv("CENTIPEDE_RUNNER_FLAGS");
+ const int result = ([&]() {
+ if (runner_mode) {
+ CentipedeAdaptorRunnerCallbacks runner_callbacks(runtime_, fuzzer_impl_,
+ &configuration);
+ return centipede::RunnerMain(argc != nullptr ? *argc : 0,
+ argv != nullptr ? *argv : nullptr,
+ runner_callbacks);
+ }
+ // Centipede engine does not support replay and reproducer minimization
+ // (within the single process). So use the existing fuzztest implementation.
+ // This is fine because it does not require coverage instrumentation.
+ if (fuzzer_impl_.ReplayInputsIfAvailable(configuration)) return 0;
+ // Run as the fuzzing engine.
+ if (getenv("FUZZTEST_MINIMIZE_TESTSUITE_DIR")) {
+ absl::FPrintF(GetStderr(),
+ "[!] Corpus minimization is not supported in the "
+ "single-process mode. Consider using the Centipede binary "
+ "in corpus distillation mode - see centipede/README.md.");
+ return 1;
+ }
+ TempDir workdir("/tmp/fuzztest-workdir-");
+ const auto env = CreateCentipedeEnvironmentFromFuzzTestFlags(
+ runtime_, workdir.path(), test_.full_name());
+ CentipedeAdaptorEngineCallbacksFactory factory(runtime_, fuzzer_impl_,
+ &configuration);
+ return centipede::CentipedeMain(env, factory);
+ })();
+ fuzzer_impl_.fixture_driver_->TearDownFuzzTest();
+ if (result) std::exit(result);
+ if (!runner_mode) {
+ absl::FPrintF(GetStderr(), "\n[.] Fuzzing was terminated.\n");
+ runtime_.PrintFinalStatsOnDefaultSink();
+ absl::FPrintF(GetStderr(), "\n");
+ }
+ return 0;
}
} // namespace fuzztest::internal
+
+// The code below is used at very early stage of the process. Cannot use
+// GetStderr().
+namespace {
+
+class CentipedeCallbacksForRunnerFlagsExtraction
+ : public centipede::CentipedeCallbacks {
+ public:
+ using centipede::CentipedeCallbacks::CentipedeCallbacks;
+
+ bool Execute(std::string_view binary,
+ const std::vector<centipede::ByteArray>& inputs,
+ centipede::BatchResult& batch_result) override {
+ return false;
+ }
+
+ std::string GetRunnerFlagsContent() {
+ constexpr absl::string_view kRunnerFlagPrefix = "CENTIPEDE_RUNNER_FLAGS=";
+ const std::string runner_flags = ConstructRunnerFlags();
+ if (!absl::StartsWith(runner_flags, kRunnerFlagPrefix)) {
+ absl::FPrintF(
+ stderr,
+ "[!] Unexpected prefix in Centipede runner flags - returning "
+ "without stripping the prefix.\n");
+ return runner_flags;
+ }
+ return runner_flags.substr(kRunnerFlagPrefix.size());
+ }
+};
+
+} // namespace
+
+extern "C" const char* CentipedeGetRunnerFlags() {
+ if (const char* runner_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) {
+ // Runner mode. Use the existing flags.
+ return strdup(runner_flags_env);
+ }
+ // Set the runner flags according to the FuzzTest default environment.
+ const auto env = fuzztest::internal::CreateDefaultCentipedeEnvironment();
+ CentipedeCallbacksForRunnerFlagsExtraction callbacks(env);
+ const std::string runner_flags = callbacks.GetRunnerFlagsContent();
+ absl::FPrintF(stderr, "[.] Centipede runner flags: %s\n", runner_flags);
+ return strdup(runner_flags.c_str());
+}
diff --git a/fuzztest/internal/io.cc b/fuzztest/internal/io.cc
index f3e4af7..45c71a1 100644
--- a/fuzztest/internal/io.cc
+++ b/fuzztest/internal/io.cc
@@ -14,8 +14,14 @@
#include "./fuzztest/internal/io.h"
+#if defined(__APPLE__)
+// For mkdtemp
+#include <unistd.h>
+#endif
+
#include <cerrno>
#include <cstdio>
+#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
@@ -23,6 +29,7 @@
#include <sstream>
#include <string>
#include <string_view>
+#include <system_error>
#include <utility>
#include <vector>
@@ -40,6 +47,9 @@
// Just stub out these functions.
#define STUB_FILESYSTEM
#endif
+#elif defined(_WIN32)
+// No mkdtemp.
+#define STUB_FILESYSTEM
#endif
namespace fuzztest::internal {
@@ -67,6 +77,13 @@
FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS");
}
+TempDir::TempDir(absl::string_view path_prefix) {
+ FUZZTEST_INTERNAL_CHECK(false, "Not implemented in iOS/MacOS");
+}
+TempDir::~TempDir() {
+ FUZZTEST_INTERNAL_CHECK(false, "Not implemented in iOS/MacOS");
+}
+
#else // defined(__APPLE__)
bool WriteFile(absl::string_view filename, absl::string_view contents) {
@@ -142,6 +159,24 @@
return out;
}
+TempDir::TempDir(absl::string_view path_prefix) {
+ std::string filename = absl::StrFormat("%sXXXXXX", path_prefix);
+ const char* path = mkdtemp(filename.data());
+ const auto saved_errno = errno;
+ FUZZTEST_INTERNAL_CHECK(path, "Cannot create temporary dir with path prefix ",
+ path_prefix, ": ", saved_errno);
+ path_ = path;
+}
+
+TempDir::~TempDir() {
+ std::error_code ec;
+ std::filesystem::remove_all(path_, ec);
+ if (ec) {
+ absl::FPrintF(GetStderr(), "[!] Unable to clean up temporary dir %s: %s",
+ path_, ec.message());
+ }
+}
+
#endif // defined(STUB_FILESYSTEM)
absl::string_view Basename(absl::string_view filename) {
diff --git a/fuzztest/internal/io.h b/fuzztest/internal/io.h
index 1066875..b5a174f 100644
--- a/fuzztest/internal/io.h
+++ b/fuzztest/internal/io.h
@@ -50,6 +50,19 @@
// Returns the basename of `filename`.
absl::string_view Basename(absl::string_view filename);
+// A temporary directory with `path_prefix` that will be cleaned up on object
+// destruction.
+class TempDir {
+ public:
+ explicit TempDir(absl::string_view path_prefix = "/tmp/");
+ ~TempDir();
+
+ const std::string& path() const { return path_; }
+
+ private:
+ std::string path_;
+};
+
} // namespace fuzztest::internal
#endif // FUZZTEST_FUZZTEST_INTERNAL_IO_H_
diff --git a/fuzztest/internal/io_test.cc b/fuzztest/internal/io_test.cc
index 98841f1..99cd87e 100644
--- a/fuzztest/internal/io_test.cc
+++ b/fuzztest/internal/io_test.cc
@@ -38,14 +38,15 @@
using ::testing::IsEmpty;
using ::testing::Optional;
using ::testing::SizeIs;
+using ::testing::StartsWith;
using ::testing::UnorderedElementsAre;
-std::string TmpFile(const std::string& name) {
+std::string TestTmpFile(const std::string& name) {
std::string filename = absl::StrCat(testing::TempDir(), "/", name, "XXXXXX");
return mktemp(filename.data());
}
-std::string TmpDir(const std::string& name) {
+std::string TestTmpDir(const std::string& name) {
std::string filename = absl::StrCat(testing::TempDir(), "/", name, "XXXXXX");
return mkdtemp(filename.data());
}
@@ -69,14 +70,14 @@
}
TEST(IOTest, WriteFileWorksWhenDirectoryExists) {
- const std::string tmp_name = TmpFile("write_test");
+ const std::string tmp_name = TestTmpFile("write_test");
EXPECT_TRUE(WriteFile(tmp_name, "Payload1"));
EXPECT_EQ(TestRead(tmp_name), "Payload1");
std::filesystem::remove(tmp_name);
}
TEST(IOTest, WriteFileWorksWhenDirectoryDoesNotExist) {
- const std::string tmp_dir = TmpDir("write_test_dir");
+ const std::string tmp_dir = TestTmpDir("write_test_dir");
const std::string tmp_name = absl::StrCat(tmp_dir, "/doesnt_exist/file");
EXPECT_TRUE(WriteFile(tmp_name, "Payload1"));
EXPECT_EQ(TestRead(tmp_name), "Payload1");
@@ -84,14 +85,14 @@
}
TEST(IOTest, WriteDataToDirReturnsWrittenFilePath) {
- const std::string tmp_dir = TmpDir("write_test_dir");
+ const std::string tmp_dir = TestTmpDir("write_test_dir");
const std::string path = WriteDataToDir("data", tmp_dir);
EXPECT_THAT(ReadFile(path), Optional(Eq("data")));
std::filesystem::remove_all(tmp_dir);
}
TEST(IOTest, WriteDataToDirWritesToSameFileOnSameData) {
- const std::string tmp_dir = TmpDir("write_test_dir");
+ const std::string tmp_dir = TestTmpDir("write_test_dir");
const std::string path = WriteDataToDir("data", tmp_dir);
EXPECT_THAT(WriteDataToDir("data", tmp_dir), Eq(path));
EXPECT_THAT(ReadFile(path), Optional(Eq("data")));
@@ -105,7 +106,7 @@
}
TEST(IOTest, ReadFileWorksWhenFileExists) {
- const std::string tmp_name = TmpFile("read_test");
+ const std::string tmp_name = TestTmpFile("read_test");
TestWrite(tmp_name, "Payload2");
EXPECT_THAT(ReadFile(tmp_name), Optional(Eq("Payload2")));
EXPECT_THAT(ReadFileOrDirectory(tmp_name),
@@ -114,7 +115,7 @@
}
TEST(IOTest, ReadFileOrDirectoryWorks) {
- const std::string tmp_dir = TmpDir("write_test_dir");
+ const std::string tmp_dir = TestTmpDir("write_test_dir");
EXPECT_THAT(ReadFileOrDirectory(tmp_dir), UnorderedElementsAre());
const std::string tmp_file_1 = absl::StrCat(tmp_dir, "/file1");
TestWrite(tmp_file_1, "Payload3.1");
@@ -129,7 +130,7 @@
}
TEST(IOTest, ReadFileOrDirectoryWorksRecursively) {
- const std::string tmp_dir = TmpDir("test_dir");
+ const std::string tmp_dir = TestTmpDir("test_dir");
const std::string tmp_sub_dir = absl::StrCat(tmp_dir, "/subdir");
mkdir(tmp_sub_dir.c_str(), 0700);
const std::string tmp_file_1 = absl::StrCat(tmp_dir, "/file1");
@@ -143,7 +144,7 @@
}
TEST(IOTest, ReadFilesFromDirectoryWorks) {
- const std::string tmp_dir = TmpDir("write_test_dir");
+ const std::string tmp_dir = TestTmpDir("write_test_dir");
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir), UnorderedElementsAre());
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir), SizeIs(0));
const std::string tmp_file_1 = absl::StrCat(tmp_dir, "/file1");
@@ -161,7 +162,7 @@
}
TEST(IOTest, ReadFilesFromDirectoryReturnsEmptyVectorWhenNoFilesInDir) {
- const std::string tmp_dir = TmpDir("empty_dir");
+ const std::string tmp_dir = TestTmpDir("empty_dir");
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir), UnorderedElementsAre());
EXPECT_THAT(ReadFileOrDirectory(tmp_dir), SizeIs(0));
std::filesystem::remove_all(tmp_dir);
@@ -173,7 +174,7 @@
}
TEST(IOTest, ListDirectoryReturnsPathsInDirectory) {
- const std::string tmp_dir = TmpDir("test_dir");
+ const std::string tmp_dir = TestTmpDir("test_dir");
const std::string tmp_file_1 = absl::StrCat(tmp_dir, "/file1");
TestWrite(tmp_file_1, /*contents=*/"File1");
const std::string tmp_file_2 = absl::StrCat(tmp_dir, "/file2");
@@ -184,7 +185,7 @@
}
TEST(IOTest, ListDirectoryReturnsEmptyVectorWhenDirectoryIsEmpty) {
- const std::string tmp_dir = TmpDir("empty_dir");
+ const std::string tmp_dir = TestTmpDir("empty_dir");
EXPECT_THAT(ListDirectory(tmp_dir), IsEmpty());
std::filesystem::remove_all(tmp_dir);
}
@@ -193,5 +194,21 @@
EXPECT_THAT(ListDirectory("/doesnt_exist/"), IsEmpty());
}
+TEST(IOTest, TempDirGeneratesDirFollowingPathPrefix) {
+ TempDir temp_dir("/tmp/some_path_prefix");
+ EXPECT_THAT(temp_dir.path(), StartsWith("/tmp/some_path_prefix"));
+ EXPECT_TRUE(std::filesystem::is_directory(temp_dir.path()));
+}
+
+TEST(IOTest, TempDirRemovesDirOnDestruction) {
+ std::string temp_dir_path;
+ {
+ TempDir temp_dir("/tmp/some_path_prefix");
+ temp_dir_path = temp_dir.path();
+ EXPECT_TRUE(std::filesystem::is_directory(temp_dir_path));
+ }
+ EXPECT_FALSE(std::filesystem::exists(temp_dir_path));
+}
+
} // namespace
} // namespace fuzztest::internal
diff --git a/fuzztest/internal/runtime.h b/fuzztest/internal/runtime.h
index 97214e1..541ffb0 100644
--- a/fuzztest/internal/runtime.h
+++ b/fuzztest/internal/runtime.h
@@ -323,6 +323,7 @@
// Defined in centipede_adaptor.cc
friend class CentipedeFuzzerAdaptor;
friend class CentipedeAdaptorRunnerCallbacks;
+ friend class CentipedeAdaptorEngineCallbacks;
};
} // namespace internal