File: in_process_fuzzer.cc

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (371 lines) | stat: -rw-r--r-- 14,895 bytes parent folder | download | duplicates (3)
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);
}