1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // A wrapper which knows to execute a given fuzzer within a fuzztest
6 // executable that contains multiple fuzzers.
7 // The fuzzer binary is assumed to be in the same directory as this binary.
8
9 #include <iostream>
10
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/path_service.h"
15 #include "base/process/launch.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19
20 extern const char* kFuzzerBinary;
21 extern const char* kFuzzerArgs;
22
23 namespace {
HandleReplayModeIfNeeded(auto & args)24 void HandleReplayModeIfNeeded(auto& args) {
25 // For libfuzzer fuzzers, nothing needs to be done. To detect whether we're
26 // running a libfuzzer fuzztest, we check for `undefok` in the fuzzer args
27 // provided at compile time, which is only set for libfuzzer.
28 if (kFuzzerArgs && std::string_view(kFuzzerArgs).find("-undefok=") !=
29 std::string_view::npos) {
30 return;
31 }
32
33 // We're handling a centipede based fuzzer. If the last argument is a
34 // filepath, we're trying to replay a testcase, since it doesn't make sense
35 // to get a filepath when running with the centipede binary.
36 base::FilePath test_case(args.back());
37 if (!base::PathExists(test_case)) {
38 return;
39 }
40
41 auto env = base::Environment::Create();
42 #if BUILDFLAG(IS_WIN)
43 auto env_value = base::WideToUTF8(args.back());
44 #else
45 auto env_value = args.back();
46 #endif
47 env->SetVar("FUZZTEST_REPLAY", env_value);
48 env->UnSetVar("CENTIPEDE_RUNNER_FLAGS");
49 std::cerr << "FuzzTest wrapper setting env var: FUZZTEST_REPLAY="
50 << args.back() << '\n';
51
52 // We must not add the testcase to the command line, as this will not be
53 // parsed correctly by centipede.
54 args.pop_back();
55 }
56 } // namespace
57
main(int argc,const char * const * argv)58 int main(int argc, const char* const* argv) {
59 base::CommandLine::Init(argc, argv);
60 base::FilePath fuzzer_path;
61 if (!base::PathService::Get(base::DIR_EXE, &fuzzer_path)) {
62 return -1;
63 }
64 fuzzer_path = fuzzer_path.AppendASCII(kFuzzerBinary);
65 base::LaunchOptions launch_options;
66 base::CommandLine cmdline(fuzzer_path);
67 std::vector<std::string_view> additional_args = base::SplitStringPiece(
68 kFuzzerArgs, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
69 for (auto arg : additional_args) {
70 cmdline.AppendArg(arg);
71 }
72 auto args = base::CommandLine::ForCurrentProcess()->argv();
73 HandleReplayModeIfNeeded(args);
74
75 bool skipped_first = false;
76 for (auto arg : args) {
77 if (!skipped_first) {
78 skipped_first = true;
79 continue;
80 }
81 // We avoid AppendArguments because it parses switches then reorders things.
82 cmdline.AppendArgNative(arg);
83 }
84 std::cerr << "FuzzTest wrapper launching:" << cmdline.GetCommandLineString()
85 << "\n";
86 base::Process p = base::LaunchProcess(cmdline, launch_options);
87 int exit_code;
88 p.WaitForExit(&exit_code);
89 return exit_code;
90 }
91
92 #if defined(WIN32)
93 #define ALWAYS_EXPORT __declspec(dllexport)
94 #else
95 #define ALWAYS_EXPORT __attribute__((visibility("default")))
96 #endif
97
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)98 ALWAYS_EXPORT extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data,
99 size_t size) {
100 // No-op. This symbol exists to ensure that this binary is detected as
101 // a fuzzer by ClusterFuzz's heuristics. It never actually gets called.
102 return -1;
103 }
104