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();