#include "chrome/test/fuzzing/in_process_fuzzer.h"
#include <vector>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/strings/string_util.h"
#include "base/test/bind.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/test_timeouts.h"
#include "chrome/renderer/chrome_content_renderer_client.h"
#include "chrome/test/base/chrome_test_launcher.h"
#include "chrome/test/fuzzing/in_process_fuzzer_buildflags.h"
#include "content/public/app/content_main.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/public/test/test_launcher.h"
#include "third_party/blink/public/web/web_testing_support.h"
#if BUILDFLAG(IS_WIN)
#include "base/strings/sys_string_conversions.h"
#endif
extern "C" int LLVMFuzzerRunDriver(int* argc,
char*** argv,
int (*UserCb)(const uint8_t* Data,
size_t Size));
namespace {
std::string_view RunLoopTimeoutBehaviorToString(
RunLoopTimeoutBehavior behavior) {
switch (behavior) {
case RunLoopTimeoutBehavior::kDefault:
return "kDefault";
case RunLoopTimeoutBehavior::kContinue:
return "kContinue";
case RunLoopTimeoutBehavior::kDeclareInfiniteLoop:
return "kDeclareInfiniteLoop";
}
return "kUnknown";
}
void LogRunLoopTimeoutCallback(RunLoopTimeoutBehavior behavior) {
LOG(INFO) << "Custom RunLoop timeout callback triggered ("
<< RunLoopTimeoutBehaviorToString(behavior) << ").";
}
}
InProcessFuzzerFactoryBase* g_in_process_fuzzer_factory;
InProcessFuzzer::InProcessFuzzer(InProcessFuzzerOptions options)
: options_(options) {}
InProcessFuzzer::~InProcessFuzzer() = default;
bool InProcessFuzzer::UseSingleProcessMode() {
return true;
}
base::CommandLine::StringVector
InProcessFuzzer::GetChromiumCommandLineArguments() {
base::CommandLine::StringVector empty;
return empty;
}
void InProcessFuzzer::SetUp() {
base::test::ScopedRunLoopTimeout scoped_timeout(FROM_HERE,
base::TimeDelta::Max());
switch (options_.run_loop_timeout_behavior) {
case RunLoopTimeoutBehavior::kContinue:
KeepRunningOnTimeout();
break;
case RunLoopTimeoutBehavior::kDeclareInfiniteLoop:
DeclareInfiniteLoopOnTimeout();
break;
case RunLoopTimeoutBehavior::kDefault:
break;
}
InProcessBrowserTest::SetUp();
}
void InProcessFuzzer::KeepRunningOnTimeout() {
base::test::ScopedRunLoopTimeout::SetTimeoutCallbackForTesting(
std::make_unique<base::test::ScopedRunLoopTimeout::TimeoutCallback>(
base::IgnoreArgs<const base::Location&,
base::RepeatingCallback<std::string()>,
const base::Location&>(base::BindRepeating(
&LogRunLoopTimeoutCallback, RunLoopTimeoutBehavior::kContinue))));
}
void InProcessFuzzer::DeclareInfiniteLoopOnTimeout() {
base::test::ScopedRunLoopTimeout::SetTimeoutCallbackForTesting(
std::make_unique<base::test::ScopedRunLoopTimeout::TimeoutCallback>(
base::IgnoreArgs<const base::Location&,
base::RepeatingCallback<std::string()>,
const base::Location&>(
base::BindRepeating(&InProcessFuzzer::DeclareInfiniteLoop,
base::Unretained(this)))
.Then(base::BindRepeating(
&LogRunLoopTimeoutCallback,
RunLoopTimeoutBehavior::kDeclareInfiniteLoop))));
}
void InProcessFuzzer::Run(
const std::vector<std::string>& libfuzzer_command_line) {
libfuzzer_command_line_ = libfuzzer_command_line;
SetUp();
TearDown();
}
void InProcessFuzzer::SetUpOnMainThread() {
InProcessBrowserTest::SetUpOnMainThread();
#if BUILDFLAG(IS_POSIX)
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
signal(SIGSEGV, SIG_DFL);
#endif
#endif
}
bool InProcessFuzzer::InMergeMode() const {
for (const auto& arg : libfuzzer_command_line_) {
if (arg.starts_with("-merge")) {
return true;
}
}
return false;
}
InProcessFuzzer* g_test;
class InternalsObjectFrameInjector : public content::RenderFrameObserver {
public:
explicit InternalsObjectFrameInjector(content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame) {}
void DidClearWindowObject() override {
blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
blink::WebTestingSupport::InjectInternalsObject(frame);
}
void OnDestruct() override { delete this; }
};
class InternalsObjectRendererInjector : public ChromeContentRendererClient {
public:
void RenderFrameCreated(content::RenderFrame* render_frame) override {
new InternalsObjectFrameInjector(render_frame);
}
};
class FuzzerChromeMainDelegate : public ChromeTestChromeMainDelegate {
public:
FuzzerChromeMainDelegate() = default;
content::ContentRendererClient* CreateContentRendererClient() override {
return new InternalsObjectRendererInjector();
}
};
class FuzzerTestLauncherDelegate : public content::TestLauncherDelegate {
public:
FuzzerTestLauncherDelegate(std::unique_ptr<InProcessFuzzer>&& fuzzer,
std::vector<std::string>&& libfuzzer_arguments)
: fuzzer_(std::move(fuzzer)),
libfuzzer_arguments_(std::move(libfuzzer_arguments)) {
}
int RunTestSuite(int argc, char** argv) override {
fuzzer_->Run(libfuzzer_arguments_);
return 0;
}
#if !BUILDFLAG(IS_ANDROID)
content::ContentMainDelegate* CreateContentMainDelegate() override {
return new FuzzerChromeMainDelegate();
}
#endif
private:
std::unique_ptr<InProcessFuzzer> fuzzer_;
std::vector<std::string> libfuzzer_arguments_;
};
int fuzz_callback(const uint8_t* data, size_t size) {
return g_test->DoFuzz(data, size);
}
void InProcessFuzzer::RunTestOnMainThread() {
std::vector<char*> argv;
for (const auto& arg : libfuzzer_command_line_) {
argv.push_back((char*)arg.data());
}
argv.push_back(nullptr);
int argc = argv.size() - 1;
char** argv2 = argv.data();
g_test = this;
auto res = LLVMFuzzerRunDriver(&argc, &argv2, fuzz_callback);
if (exit_after_fuzz_case_) {
LOG(INFO) << "Early exit requested - exiting after LLVMFuzzerRunDriver.";
exit(0);
}
exit(res);
}
int InProcessFuzzer::DoFuzz(const uint8_t* data, size_t size) {
if (exit_after_fuzz_case_) {
LOG(INFO) << "Early exit requested - exiting after fuzz case.";
exit(0);
}
std::optional<base::test::ScopedRunLoopTimeout> scoped_timeout;
if (options_.run_loop_timeout) {
scoped_timeout.emplace(FROM_HERE, *options_.run_loop_timeout);
}
return Fuzz(data, size);
}
class ChildProcessTestLauncherDelegate : public content::TestLauncherDelegate {
public:
ChildProcessTestLauncherDelegate() = default;
int RunTestSuite(int argc, char** argv) override {
LOG(FATAL) << "Trying to run tests in child";
}
#if !BUILDFLAG(IS_ANDROID)
content::ContentMainDelegate* CreateContentMainDelegate() override {
return new FuzzerChromeMainDelegate();
}
#endif
};
int main(int argc, char** argv) {
base::AtExitManager atexit_manager;
base::CommandLine::Init(argc, argv);
if (base::CommandLine::ForCurrentProcess()->argv().size() > 1) {
if (base::StartsWith(base::CommandLine::ForCurrentProcess()->argv()[1],
FILE_PATH_LITERAL("--"))) {
ChildProcessTestLauncherDelegate delegate;
return LaunchChromeTests(1, &delegate, argc, argv);
}
}
std::unique_ptr<InProcessFuzzer> fuzzer =
g_in_process_fuzzer_factory->CreateInProcessFuzzer();
#if BUILDFLAG(IS_WIN)
std::vector<std::string> libfuzzer_arguments;
auto wide_argv = base::CommandLine::ForCurrentProcess()->argv();
for (auto arg : wide_argv) {
libfuzzer_arguments.push_back(base::SysWideToUTF8(arg));
}
#else
std::vector<std::string> libfuzzer_arguments =
base::CommandLine::ForCurrentProcess()->argv();
#endif
base::CommandLine::StringType executable_name =
base::CommandLine::ForCurrentProcess()->argv().at(0);
base::CommandLine::StringVector chromium_arguments =
fuzzer->GetChromiumCommandLineArguments();
chromium_arguments.insert(chromium_arguments.begin(), executable_name);
chromium_arguments.push_back(FILE_PATH_LITERAL("--single-process-tests"));
if (fuzzer->UseSingleProcessMode()) {
chromium_arguments.push_back(FILE_PATH_LITERAL("--single-process"));
chromium_arguments.push_back(
FILE_PATH_LITERAL("--disable-crashpad-for-testing"));
} else {
#if BUILDFLAG(IS_CENTIPEDE)
unsetenv("CENTIPEDE_RUNNER_FLAGS");
#endif
}
chromium_arguments.push_back(FILE_PATH_LITERAL("--no-zygote"));
chromium_arguments.push_back(FILE_PATH_LITERAL("--no-sandbox"));
chromium_arguments.push_back(FILE_PATH_LITERAL("--disable-gpu"));
chromium_arguments.push_back(
FILE_PATH_LITERAL("--enable-unsafe-swiftshader"));
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
chromium_arguments.push_back(
FILE_PATH_LITERAL("--disable-in-process-stack-traces"));
#endif
base::CommandLine::ForCurrentProcess()->InitFromArgv(chromium_arguments);
TestTimeouts::Initialize();
FuzzerTestLauncherDelegate fuzzer_launcher_delegate(
std::move(fuzzer), std::move(libfuzzer_arguments));
return LaunchChromeTests(1, &fuzzer_launcher_delegate, argc, argv);
}