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