blob: 12b540445e3e81e67787b59ea50e374f5ff28a75 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/functional/callback_forward.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/test/base/chrome_test_launcher.h"
#include "chrome/test/fuzzing/in_process_fuzzer.h"
#include "chrome/test/fuzzing/in_process_fuzzer_buildflags.h"
#include "content/public/app/content_main.h"
#include "content/public/test/test_launcher.h"
#if BUILDFLAG(IS_WIN)
#include "base/strings/sys_string_conversions.h"
#endif // BUILDFLAG(IS_WIN)
// This is provided within libfuzzer, and documented, but is not its headers.
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) << ").";
}
} // namespace
InProcessFuzzerFactoryBase* g_in_process_fuzzer_factory;
InProcessFuzzer::InProcessFuzzer(InProcessFuzzerOptions options)
: options_(options) {}
InProcessFuzzer::~InProcessFuzzer() = default;
base::CommandLine::StringVector
InProcessFuzzer::GetChromiumCommandLineArguments() {
base::CommandLine::StringVector empty;
return empty;
}
void InProcessFuzzer::SetUp() {
std::optional<base::test::ScopedRunLoopTimeout> scoped_timeout;
switch (options_.run_loop_timeout_behavior) {
case RunLoopTimeoutBehavior::kContinue:
KeepRunningOnTimeout();
break;
case RunLoopTimeoutBehavior::kDeclareInfiniteLoop:
DeclareInfiniteLoopOnTimeout();
break;
case RunLoopTimeoutBehavior::kDefault:
break;
}
if (options_.run_loop_timeout) {
scoped_timeout.emplace(FROM_HERE, *options_.run_loop_timeout);
}
// Note that browser tests are being launched by the `SetUp` method.
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();
}
InProcessFuzzer* g_test;
class FuzzTestLauncherDelegate : public content::TestLauncherDelegate {
public:
FuzzTestLauncherDelegate(std::unique_ptr<InProcessFuzzer>&& fuzzer,
std::vector<std::string>&& libfuzzer_arguments)
: fuzzer_(std::move(fuzzer)),
libfuzzer_arguments_(std::move(libfuzzer_arguments)) {
content_main_delegate_ =
std::make_unique<ChromeTestChromeMainDelegate>(base::TimeTicks::Now());
}
int RunTestSuite(int argc, char** argv) override {
fuzzer_->Run(libfuzzer_arguments_);
return 0;
}
#if !BUILDFLAG(IS_ANDROID)
// Android browser tests set the ContentMainDelegate itself for the test
// harness to use, and do not go through ContentMain() in TestLauncher.
content::ContentMainDelegate* CreateContentMainDelegate() override {
return &*content_main_delegate_;
}
#endif
private:
std::unique_ptr<InProcessFuzzer> fuzzer_;
std::unique_ptr<content::ContentMainDelegate> content_main_delegate_;
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;
base::IgnoreResult(LLVMFuzzerRunDriver(&argc, &argv2, fuzz_callback));
if (exit_after_fuzz_case_) {
LOG(INFO) << "Early exit requested - exiting after LLVMFuzzerRunDriver.";
exit(0);
}
g_test = nullptr;
}
int InProcessFuzzer::DoFuzz(const uint8_t* data, size_t size) {
// We actually exit before running the *next* fuzz case to give an opportunity
// to return the return value to the fuzzing engine.
if (exit_after_fuzz_case_) {
LOG(INFO) << "Early exit requested - exiting after fuzz case.";
exit(0);
}
return Fuzz(data, size);
}
// Main function for running in process fuzz tests.
// This aims to replicate //chrome browser tests as much as possible; we want
// the whole browser environment to be available for this sort of test in as
// realistic a fashion as possible.
int main(int argc, char** argv) {
base::AtExitManager atexit_manager;
base::CommandLine::Init(argc, argv);
std::unique_ptr<InProcessFuzzer> fuzzer =
g_in_process_fuzzer_factory->CreateInProcessFuzzer();
// Oh dear, you've got to the part of the code relating to command lines.
// I'm sorry.
// Here are our constraints:
// * Both libfuzzer/centipede and Chromium expect a full command line
// * We set the format of neither command line
// * Chromium will launch other Chromium processes, giving them a command
// line.
// * The centipede runner will launch our fuzzer, giving it a command line.
// So, at this point, we have to figure out heuristics for what's up.
// Are we the original fuzzer process, in which case we pass the CLI to
// libfuzzer/centipede, and ask for a suitable Chromium command line from
// our fuzz test? Or, are we a child Chromium process which has been
// launched from a previous Chromium process? Well, dear reader, there are
// no telltail arguments guaranteed to be on either, so we're going to
// use a heuristic. If the first argument starts with --, we're assuming
// we're a Chromium child.
bool we_are_probably_a_chromium_child_process = false;
if (base::CommandLine::ForCurrentProcess()->argv().size() > 1) {
if (base::StartsWith(base::CommandLine::ForCurrentProcess()->argv()[1],
FILE_PATH_LITERAL("--"))) {
we_are_probably_a_chromium_child_process = true;
}
}
std::vector<std::string> libfuzzer_arguments;
if (we_are_probably_a_chromium_child_process) {
// If we're a Chromium child, we don't alter the command-line,
// and in fact the libfuzzer code will never run, so we don't need to
// pass any arguments through to libfuzzer.
} else {
#if BUILDFLAG(IS_WIN)
// Convert std::wstring (Windows command lines) to std::string
// (as needed by libfuzzer).
auto wide_argv = base::CommandLine::ForCurrentProcess()->argv();
for (auto arg : wide_argv) {
libfuzzer_arguments.push_back(base::SysWideToUTF8(arg));
}
#else
libfuzzer_arguments = base::CommandLine::ForCurrentProcess()->argv();
#endif // BUILDFLAG(IS_WIN)
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 BUILDFLAG(IS_CENTIPEDE)
// TODO(1038952): make libfuzzer compatible with single-process mode.
// As it stands, single-process mode works with centipede (and is probably
// desirable both in terms of fuzzing speed and correctly gathering
// coverage information) but not yet with libfuzzer.
chromium_arguments.push_back(FILE_PATH_LITERAL("--single-process"));
#endif // BUILDFLAG(IS_CENTIPEDE)
chromium_arguments.push_back(FILE_PATH_LITERAL("--no-sandbox"));
chromium_arguments.push_back(FILE_PATH_LITERAL("--no-zygote"));
chromium_arguments.push_back(FILE_PATH_LITERAL("--disable-gpu"));
base::CommandLine::ForCurrentProcess()->InitFromArgv(chromium_arguments);
// Various bits of setup are done by base::TestSuite::Initialize.
// As we're not a functional test suite, most of those things are not
// necessary, but at least this is:
TestTimeouts::Initialize();
}
FuzzTestLauncherDelegate* fuzzer_launcher_delegate =
new FuzzTestLauncherDelegate(std::move(fuzzer),
std::move(libfuzzer_arguments));
return LaunchChromeTests(1, fuzzer_launcher_delegate, argc, argv);
}