No public description PiperOrigin-RevId: 568640977
diff --git a/fuzztest/BUILD b/fuzztest/BUILD index f53398a..55d1730 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD
@@ -24,6 +24,7 @@ cc_library( name = "fuzztest", + testonly = True, srcs = ["fuzztest.cc"], hdrs = ["fuzztest.h"], deps = [ @@ -52,6 +53,8 @@ hdrs = ["init_fuzztest.h"], deps = [ ":googletest_adaptor", + ":io", + ":logging", ":registry", ":runtime", "@com_google_absl//absl/flags:flag", @@ -97,6 +100,7 @@ cc_library( name = "centipede_adaptor", + testonly = True, srcs = ["internal/centipede_adaptor.cc"], hdrs = ["internal/centipede_adaptor.h"], deps = [ @@ -111,6 +115,7 @@ cc_library( name = "compatibility_mode", + testonly = True, srcs = ["internal/compatibility_mode.cc"], hdrs = ["internal/compatibility_mode.h"], deps = [ @@ -264,6 +269,8 @@ deps = [ ":registry", ":runtime", + "@com_google_absl//absl/functional:function_ref", + "@com_google_absl//absl/strings", "@com_google_googletest//:gtest", ], ) @@ -338,12 +345,14 @@ ":type_support", "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/types:span", ], ) cc_library( name = "registry", + testonly = True, srcs = ["internal/registry.cc"], hdrs = ["internal/registry.h"], deps = [
diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc index 9d05df0..1797780 100644 --- a/fuzztest/init_fuzztest.cc +++ b/fuzztest/init_fuzztest.cc
@@ -10,10 +10,13 @@ #include "gtest/gtest.h" #include "absl/flags/flag.h" #include "absl/strings/match.h" +#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "./fuzztest/internal/googletest_adaptor.h" +#include "./fuzztest/internal/io.h" +#include "./fuzztest/internal/logging.h" #include "./fuzztest/internal/registry.h" #include "./fuzztest/internal/runtime.h" @@ -124,6 +127,16 @@ std::exit(0); } + FUZZTEST_INTERNAL_CHECK( + internal::Runtime::instance().files_to_replay().empty(), + "You cannot manually set corpus files"); + std::vector<std::string> corpus_files = + internal::GetFileOrFilesInDir(absl::StrCat( + "security/laser/sundew/blaze/corpora/", *argv[0], "/minimized")); + if (!corpus_files.empty()) { + internal::Runtime::instance().SetFilesToReplay(corpus_files); + } + const auto test_to_fuzz = absl::GetFlag(FUZZTEST_FLAG(fuzz)); const bool is_test_to_fuzz_specified = test_to_fuzz != kUnspecified; if (is_test_to_fuzz_specified) { @@ -140,7 +153,12 @@ internal::Runtime::instance().SetFuzzTimeLimit(duration); } - internal::RegisterFuzzTestsAsGoogleTests(argc, argv); + std::vector<std::string> crashing_files; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + crashing_files = internal::GetFileOrFilesInDir(absl::StrCat( + "security/laser/sundew/blaze/corpora/", *argv[0], "/crashing_inputs")); +#endif + internal::RegisterFuzzTestsAsGoogleTests(argc, argv, crashing_files); const RunMode run_mode = is_test_to_fuzz_specified || is_duration_specified ? RunMode::kFuzz
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index be8fd66..16a5e6f 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc
@@ -19,6 +19,7 @@ #include <cstring> #include <functional> #include <memory> +#include <optional> #include <random> #include <string> #include <thread> @@ -26,6 +27,7 @@ #include <vector> #include "absl/algorithm/container.h" +#include "absl/strings/string_view.h" #include "./centipede/runner_interface.h" #include "./fuzztest/internal/domains/domain_base.h" #include "./fuzztest/internal/logging.h" @@ -154,9 +156,10 @@ const FuzzTest& test, std::unique_ptr<UntypedFixtureDriver> fixture_driver) : test_(test), fuzzer_impl_(test_, std::move(fixture_driver)) {} -void CentipedeFuzzerAdaptor::RunInUnitTestMode() { +void CentipedeFuzzerAdaptor::RunInUnitTestMode( + std::optional<absl::string_view> input) { // Run the unit test mode directly without using Centipede. - fuzzer_impl_.RunInUnitTestMode(); + fuzzer_impl_.RunInUnitTestMode(input); } int CentipedeFuzzerAdaptor::RunInFuzzingMode(int* argc, char*** argv) {
diff --git a/fuzztest/internal/centipede_adaptor.h b/fuzztest/internal/centipede_adaptor.h index 4ce3033..e0d256f 100644 --- a/fuzztest/internal/centipede_adaptor.h +++ b/fuzztest/internal/centipede_adaptor.h
@@ -27,7 +27,7 @@ public: CentipedeFuzzerAdaptor(const FuzzTest& test, std::unique_ptr<UntypedFixtureDriver> fixture_driver); - void RunInUnitTestMode() override; + void RunInUnitTestMode(std::optional<absl::string_view> input) override; int RunInFuzzingMode(int* argc, char*** argv) override; private:
diff --git a/fuzztest/internal/fixture_driver.h b/fuzztest/internal/fixture_driver.h index 60fd108..b053268 100644 --- a/fuzztest/internal/fixture_driver.h +++ b/fuzztest/internal/fixture_driver.h
@@ -99,10 +99,10 @@ public: TypedFixtureDriver(std::unique_ptr<TypedDomainInterface<ValueType>> domain, std::vector<GenericDomainCorpusType> seeds, - SeedProvider seed_provider) + const SeedProvider& seed_provider) : domain_(std::move(domain)), seeds_(std::move(seeds)), - seed_provider_(std::move(seed_provider)) {} + seed_provider_(seed_provider) {} std::vector<GenericDomainCorpusType> GetSeeds() const final { std::vector<GenericDomainCorpusType> seeds = GetSeedsFromSeedProvider(); @@ -158,7 +158,7 @@ std::unique_ptr<TypedDomainInterface<ValueType>> domain_; std::vector<GenericDomainCorpusType> seeds_; - SeedProvider seed_provider_; + const SeedProvider& seed_provider_; }; // ForceVectorForStringView is a temporary hack for realiably @@ -220,12 +220,12 @@ explicit FixtureDriver(TargetFunction target_function, const DomainT& domain, std::vector<GenericDomainCorpusType> seeds, - SeedProvider seed_provider) + const SeedProvider& seed_provider) : FixtureDriver::TypedFixtureDriver( absl::WrapUnique( static_cast<TypedDomainInterface<value_type_t<DomainT>>*>( domain.Clone().release())), - std::move(seeds), std::move(seed_provider)), + std::move(seeds), seed_provider), target_function_(target_function) {} void Test(MoveOnlyAny&& args_untyped) const override { @@ -283,12 +283,12 @@ explicit FixtureDriver(TargetFunction target_function, const DomainT& domain, std::vector<GenericDomainCorpusType> seeds, - SeedProvider seed_provider) + const SeedProvider& seed_provider) : FixtureDriver::TypedFixtureDriver( absl::WrapUnique( static_cast<TypedDomainInterface<value_type_t<DomainT>>*>( domain.Clone().release())), - std::move(seeds), std::move(seed_provider)), + std::move(seeds), seed_provider), target_function_(target_function) {} void Test(MoveOnlyAny&& args_untyped) const override {
diff --git a/fuzztest/internal/googletest_adaptor.cc b/fuzztest/internal/googletest_adaptor.cc index e2c2952..eb30ecd 100644 --- a/fuzztest/internal/googletest_adaptor.cc +++ b/fuzztest/internal/googletest_adaptor.cc
@@ -1,18 +1,36 @@ #include "./fuzztest/internal/googletest_adaptor.h" +#include <optional> +#include <string> +#include <vector> + #include "gtest/gtest.h" +#include "absl/functional/function_ref.h" +#include "absl/strings/str_cat.h" #include "./fuzztest/internal/registry.h" namespace fuzztest::internal { -void RegisterFuzzTestsAsGoogleTests(int* argc, char*** argv) { +void RunExpectExit(absl::FunctionRef<void()> test) { +#if defined(__APPLE__) || defined(_MSC_VER) + test(); +#else + EXPECT_EXIT(test(), ::testing::ExitedWithCode(0), ""); +#endif +} + +#define run +void RegisterFuzzTestsAsGoogleTests( + int* argc, char*** argv, const std::vector<std::string>& crashing_inputs) { ::fuzztest::internal::ForEachTest([&](auto& test) { auto fixture_factory = [argc, argv, &test]() -> ::fuzztest::internal::GTest_TestAdaptor* { - return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv); + return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv, + std::nullopt); }; auto test_factory = [argc, argv, &test]() -> ::testing::Test* { - return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv); + return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv, + std::nullopt); }; if (test.uses_fixture()) { ::testing::RegisterTest(test.suite_name(), test.test_name(), nullptr,
diff --git a/fuzztest/internal/googletest_adaptor.h b/fuzztest/internal/googletest_adaptor.h index 9b96d22..64b99d9 100644 --- a/fuzztest/internal/googletest_adaptor.h +++ b/fuzztest/internal/googletest_adaptor.h
@@ -17,9 +17,11 @@ #include <cstdlib> #include <memory> +#include <optional> #include <utility> #include "gtest/gtest.h" +#include "absl/strings/string_view.h" #include "./fuzztest/internal/registry.h" #include "./fuzztest/internal/runtime.h" @@ -27,13 +29,18 @@ class GTest_TestAdaptor : public ::testing::Test { public: - explicit GTest_TestAdaptor(FuzzTest& test, int* argc, char*** argv) - : test_(test), argc_(argc), argv_(argv) {} + explicit GTest_TestAdaptor(FuzzTest& test, int* argc, char*** argv, + std::optional<absl::string_view> input) + : test_(test), argc_(argc), argv_(argv), input_(input) {} void TestBody() override { - auto test = std::move(test_).make(); + std::cout << "In Test Body..." << std::endl; + std::cout << "unit test? " + << (Runtime::instance().run_mode() == RunMode::kUnitTest) + << std::endl; + auto test = test_.make(); if (Runtime::instance().run_mode() == RunMode::kUnitTest) { - test->RunInUnitTestMode(); + test->RunInUnitTestMode(input_); } else { ASSERT_EQ(0, test->RunInFuzzingMode(argc_, argv_)) << "Fuzzing failure."; } @@ -55,6 +62,7 @@ FuzzTest& test_; int* argc_; char*** argv_; + std::optional<absl::string_view> input_; }; template <typename Base, typename TestPartResult> @@ -77,7 +85,8 @@ }; // Registers FUZZ_TEST as GoogleTest TEST-s. -void RegisterFuzzTestsAsGoogleTests(int* argc, char*** argv); +void RegisterFuzzTestsAsGoogleTests( + int* argc, char*** argv, const std::vector<std::string>& crashing_inputs); } // namespace fuzztest::internal
diff --git a/fuzztest/internal/io.cc b/fuzztest/internal/io.cc index ea817c4..adfed70 100644 --- a/fuzztest/internal/io.cc +++ b/fuzztest/internal/io.cc
@@ -65,6 +65,10 @@ FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS"); } +std::vector<std::string> GetFileOrFilesInDir(std::string_view file_or_dir) { + FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS"); +} + #else // defined(__APPLE__) bool WriteFile(std::string_view filename, std::string_view contents) { @@ -133,6 +137,36 @@ return out; } +namespace { + +void GetAllFilesRecursively(std::string_view dir, + std::vector<std::string>& files) { + std::vector<std::string> entries = ListDirectory(dir); + for (const auto& entry : entries) { + absl::FPrintF(GetStderr(), "entry: %s\n", entry); + if (!std::filesystem::is_directory(entry)) { + files.push_back(entry); + } else { + GetAllFilesRecursively(entry, files); + } + } +} + +} // namespace + +std::vector<std::string> GetFileOrFilesInDir(std::string_view file_or_dir) { + // Try as a directory path first. + std::vector<std::string> files; + GetAllFilesRecursively(file_or_dir, files); + // If not, consider it a file path. + if (files.empty()) { + if (std::filesystem::is_regular_file(file_or_dir)) { + files.push_back(std::string(file_or_dir)); + } + } + return files; +} + #endif // defined(STUB_FILESYSTEM) } // namespace fuzztest::internal
diff --git a/fuzztest/internal/io.h b/fuzztest/internal/io.h index 27a39d9..d253c2c 100644 --- a/fuzztest/internal/io.h +++ b/fuzztest/internal/io.h
@@ -46,6 +46,8 @@ // returns an empty list. std::vector<std::string> ListDirectory(std::string_view dir); +std::vector<std::string> GetFileOrFilesInDir(std::string_view file_or_dir); + } // namespace fuzztest::internal #endif // FUZZTEST_FUZZTEST_INTERNAL_IO_H_
diff --git a/fuzztest/internal/registration.h b/fuzztest/internal/registration.h index c9e032c..7b0e940 100644 --- a/fuzztest/internal/registration.h +++ b/fuzztest/internal/registration.h
@@ -26,6 +26,7 @@ #include "absl/functional/any_invocable.h" #include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" #include "absl/types/span.h" #include "./fuzztest/domain.h" #include "./fuzztest/internal/domains/aggregate_of_impl.h" @@ -41,6 +42,7 @@ const char* file = nullptr; int line = 0; bool uses_fixture = false; + std::function<void*(absl::string_view)> factory; }; // Use base classes to progressively add members/behavior to the registerer
diff --git a/fuzztest/internal/registry.h b/fuzztest/internal/registry.h index 6978073..6190489 100644 --- a/fuzztest/internal/registry.h +++ b/fuzztest/internal/registry.h
@@ -15,6 +15,8 @@ #ifndef FUZZTEST_FUZZTEST_INTERNAL_REGISTRY_H_ #define FUZZTEST_FUZZTEST_INTERNAL_REGISTRY_H_ +#include <stdbool.h> + #include <memory> #include <string_view> #include <type_traits> @@ -82,13 +84,13 @@ return [target_function = reg.target_function_, domain = reg.GetDomains(), - seeds = reg.seeds(), seed_provider = reg.seed_provider()]( - const FuzzTest& test) mutable -> std::unique_ptr<FuzzTestFuzzer> { + seeds = reg.seeds(), seed_provider = std::move(reg.seed_provider())]( + const FuzzTest& test) -> std::unique_ptr<FuzzTestFuzzer> { return std::make_unique<FuzzerImpl>( test, std::make_unique<FixtureDriverImpl<decltype(domain), Fixture, TargetFunction, SeedProvider>>( - target_function, domain, seeds, std::move(seed_provider))); + target_function, domain, seeds, seed_provider)); }; } };
diff --git a/fuzztest/internal/runtime.cc b/fuzztest/internal/runtime.cc index d2089db..041245e 100644 --- a/fuzztest/internal/runtime.cc +++ b/fuzztest/internal/runtime.cc
@@ -57,6 +57,8 @@ namespace fuzztest::internal { +extern void RunExpectExit(absl::FunctionRef<void()> test); + void (*crash_handler_hook)(); void Runtime::DumpReproducer(std::string_view outdir) const { @@ -315,9 +317,8 @@ } bool FuzzTestFuzzerImpl::ReplayInputsIfAvailable() { - runtime_.SetRunMode(RunMode::kFuzz); - - if (const auto file_paths = GetFilesToReplay()) { + if (bool early_return; + const auto file_paths = GetFilesToReplay(early_return)) { for (const std::string& path : *file_paths) { const auto content = ReadFile(path); if (!content) { @@ -337,9 +338,10 @@ absl::FPrintF(GetStderr(), "[.] Replaying %s\n", path); RunOneInput({*corpus_value}); } - return true; + if (early_return) return true; } + runtime_.SetRunMode(RunMode::kFuzz); if (const auto to_minimize = ReadReproducerToMinimize()) { absl::FPrintF(GetStderr(), "[!] Looking for smaller mutations indefinitely: please " @@ -386,16 +388,17 @@ return false; } -std::optional<std::vector<std::string>> FuzzTestFuzzerImpl::GetFilesToReplay() { +std::optional<std::vector<std::string>> FuzzTestFuzzerImpl::GetFilesToReplay( + bool& early_return) { auto file_or_dir = absl::NullSafeStringView(getenv("FUZZTEST_REPLAY")); - if (file_or_dir.empty()) return std::nullopt; - // Try as a directory path first. - std::vector<std::string> files = ListDirectory(std::string(file_or_dir)); - // If not, consider it a file path. - if (files.empty()) { - files.push_back(std::string(file_or_dir)); + if (file_or_dir.empty()) { + early_return = false; + auto result = Runtime::instance().files_to_replay(); + if (result.empty()) return std::nullopt; + return result; } - return files; + early_return = true; + return GetFileOrFilesInDir(file_or_dir); } std::optional<corpus_type> FuzzTestFuzzerImpl::ReadReproducerToMinimize() { @@ -599,12 +602,36 @@ } } -void FuzzTestFuzzerImpl::RunInUnitTestMode() { +void FuzzTestFuzzerImpl::RunInUnitTestMode( + std::optional<absl::string_view> input) { + // std::cout << "In UnitTest mode" << std::endl; fixture_driver_->SetUpFuzzTest(); [&] { runtime_.EnableReporter(&stats_, [] { return absl::Now(); }); runtime_.SetCurrentTest(&test_); + if (input.has_value()) { + // std::cout << "has crashing input!" << std::endl; + const auto content = ReadFile(*input); + if (!content) { + absl::FPrintF(GetStderr(), + "[!] Failed to read FUZZTEST_REPLAY file or directory " + "(might be empty): %s\n", + *input); + } else { + auto corpus_value = TryParse(*content); + if (!corpus_value) { + absl::FPrintF(GetStderr(), + "[!] Skipping invalid input file %s.\n===\n%s\n===\n", + *input, *content); + } else { + absl::FPrintF(GetStderr(), "[.] Replaying %s\n", *input); + RunOneInput({*corpus_value}, /*expect_pass=*/true); + } + } + return; + } + // TODO(sbenzaquen): Currently, some infrastructure code assumes that replay // works in unit test mode, so we support it. However, we would like to // limit replaying to fuzzing mode only, where we can guarantee that only @@ -660,7 +687,7 @@ } FuzzTestFuzzerImpl::RunResult FuzzTestFuzzerImpl::RunOneInput( - const Input& input) { + const Input& input, bool expect_pass) { ++stats_.runs; auto untyped_args = params_domain_->UntypedGetValue(input.args); Runtime::Args debug_args{input.args, *params_domain_}; @@ -681,7 +708,11 @@ } fixture_driver_->SetUpIteration(); - fixture_driver_->Test(std::move(untyped_args)); + if (expect_pass) { + RunExpectExit([&]() { fixture_driver_->Test(std::move(untyped_args)); }); + } else { + fixture_driver_->Test(std::move(untyped_args)); + } fixture_driver_->TearDownIteration(); if (execution_coverage_ != nullptr) { execution_coverage_->SetIsTracing(false);
diff --git a/fuzztest/internal/runtime.h b/fuzztest/internal/runtime.h index cb46d6c..c56f31f 100644 --- a/fuzztest/internal/runtime.h +++ b/fuzztest/internal/runtime.h
@@ -64,7 +64,7 @@ class FuzzTestFuzzer { public: virtual ~FuzzTestFuzzer() = default; - virtual void RunInUnitTestMode() = 0; + virtual void RunInUnitTestMode(std::optional<absl::string_view> input) = 0; // Returns fuzzing mode's exit code. Zero indicates success. virtual int RunInFuzzingMode(int* argc, char*** argv) = 0; }; @@ -72,7 +72,7 @@ class FuzzTest; using FuzzTestFuzzerFactory = - absl::AnyInvocable<std::unique_ptr<FuzzTestFuzzer>(const FuzzTest&) &&>; + absl::AnyInvocable<std::unique_ptr<FuzzTestFuzzer>(const FuzzTest&) const>; class FuzzTest { public: @@ -93,7 +93,7 @@ const char* file() const { return test_info_.file; } int line() const { return test_info_.line; } bool uses_fixture() const { return test_info_.uses_fixture; } - auto make() && { return std::move(make_)(*this); } + auto make() const { return make_(*this); } private: BasicTestInfo test_info_; @@ -154,6 +154,15 @@ } absl::Duration fuzz_time_limit() const { return fuzz_time_limit_; } + void SetFilesToReplay(absl::Span<const std::string> files_to_replay) { + FUZZTEST_INTERNAL_CHECK(files_to_replay_.empty(), + "Replay files are set already!"); + files_to_replay_ = std::vector<std::string>(files_to_replay.begin(), + files_to_replay.end()); + } + + std::vector<std::string>& files_to_replay() { return files_to_replay_; } + void EnableReporter(const RuntimeStats* stats, absl::Time (*clock_fn)()) { reporter_enabled_ = true; stats_ = stats; @@ -203,6 +212,7 @@ RunMode run_mode_ = RunMode::kUnitTest; absl::Duration fuzz_time_limit_ = absl::InfiniteDuration(); + std::vector<std::string> files_to_replay_; bool reporter_enabled_ = false; Args* current_args_ = nullptr; @@ -233,7 +243,7 @@ private: // TODO(fniksic): Refactor to reduce code complexity and improve readability. - void RunInUnitTestMode() override; + void RunInUnitTestMode(std::optional<absl::string_view> input) override; // TODO(fniksic): Refactor to reduce code complexity and improve readability. int RunInFuzzingMode(int* argc, char*** argv) override; @@ -258,7 +268,7 @@ bool ReplayInputsIfAvailable(); - std::optional<std::vector<std::string>> GetFilesToReplay(); + std::optional<std::vector<std::string>> GetFilesToReplay(bool& early_return); std::optional<corpus_type> ReadReproducerToMinimize(); @@ -294,7 +304,7 @@ void InitializeCorpus(absl::BitGenRef prng); - RunResult RunOneInput(const Input& input); + RunResult RunOneInput(const Input& input, bool expect_pass = false); bool ShouldStop();