1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
|
// 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 "chrome/test/fuzzing/in_process_fuzzer.h"
#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/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 // 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;
bool InProcessFuzzer::UseSingleProcessMode() {
return true;
}
base::CommandLine::StringVector
InProcessFuzzer::GetChromiumCommandLineArguments() {
base::CommandLine::StringVector empty;
return empty;
}
void InProcessFuzzer::SetUp() {
// Overrides the default 60s run loop timeout set by `BrowserTestBase`. See
// https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/browser_test_base.cc?q=ScopedRunLoopTimeout.
// All of the fuzzing engines that we use are having timeouts features, and
// this timeout can vary depending on the number of tested testcases. We must
// let the engines handle timeouts, and set the maximum here.
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;
}
// 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();
// All of the engines that are being used to run those fuzzers are handling
// process interruption. In case we let Chrome handle those signals itself,
// we end up exiting the fuzzing process, and the engine records the last
// run as a crash since it cannot not determine the reason why the process
// terminated.
#if BUILDFLAG(IS_POSIX)
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
// In case we're being built with a memory tool (asan, msan...), we should
// let it handle this signal so that we get better reporting.
// As of now, since both in-process stack traces and the crashpad handler are
// being disabled, this is the only signal that we need to reset since it's
// being set in
// https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/browser_test_base.cc?q=SignalHandler
signal(SIGSEGV, SIG_DFL);
#endif // BUILDFLAG(MEMORY_TOOL_REPLACES_ALLOCATOR)
#endif // BUILDFLAG(IS_POSIX)
}
bool InProcessFuzzer::InMergeMode() const {
for (const auto& arg : libfuzzer_command_line_) {
if (arg.starts_with("-merge")) {
return true;
}
}
return false;
}
InProcessFuzzer* g_test;
// The following three classes are only meant to inject `internals` into JS.
// This object is needed by our IPC based fuzzers, and could also be needed by
// other in the future.
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)
// 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 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);
}
// Performing exit here allows us to skip the browser shutdown phase thus
// avoid potential hangs. This is the behaviour already observed in libfuzzer
// which performs exits within LLVMFuzzerRunDriver. It is important to use the
// LLVMFuzzerRunDriver return value for the exit value since this helps
// centipede determine which input crashed when rerunning inputs one-by-one.
exit(res);
}
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);
}
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);
}
/// Used only for child processes (e.g. renderers etc.)
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)
// 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 new FuzzerChromeMainDelegate();
}
#endif
};
// 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);
// 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.
if (base::CommandLine::ForCurrentProcess()->argv().size() > 1) {
if (base::StartsWith(base::CommandLine::ForCurrentProcess()->argv()[1],
FILE_PATH_LITERAL("--"))) {
// 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.
// Ensure we don't create the InProcessFuzzer in this branch as it
// initializes unwelcome things in its base class constructors.
ChildProcessTestLauncherDelegate delegate;
return LaunchChromeTests(1, &delegate, argc, argv);
}
}
// We are the outermost process, let's run the fuzzer.
std::unique_ptr<InProcessFuzzer> fuzzer =
g_in_process_fuzzer_factory->CreateInProcessFuzzer();
#if BUILDFLAG(IS_WIN)
// Convert std::wstring (Windows command lines) to std::string
// (as needed by libfuzzer).
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 // 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 (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)
// Static destructors in centipede runner code will open
// shared memory handles if they find the runner flags lurking in this
// environment variable. If child processes do this, they'll
// unfortunately clobber valid information from the main process
// that it's trying to pass back to the external centipede executor.
// Fortunately, this variable is only read during centipede's
// static initializers, so we can now safely clear it in our parent
// (browser) process in order to ensure it's unset for all children.
// TODO(crbug.com/383356867) - stop depending on centipede internals
// like this
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)
// We disable in-process stack trace handling in case we're using memory
// tools so that we get better reporting on what happened in case of
// SIGSEGV.
chromium_arguments.push_back(
FILE_PATH_LITERAL("--disable-in-process-stack-traces"));
#endif
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();
FuzzerTestLauncherDelegate fuzzer_launcher_delegate(
std::move(fuzzer), std::move(libfuzzer_arguments));
return LaunchChromeTests(1, &fuzzer_launcher_delegate, argc, argv);
}
|