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 #include "testing/libfuzzer/fuzztest_wrapper_buildflags.h"
20
21 extern const char* kFuzzerBinary;
22 extern const char* kFuzzerArgs;
23
24 #if BUILDFLAG(USE_CENTIPEDE)
25
26 namespace {
HandleReplayMode(auto & args)27 void HandleReplayMode(auto& args) {
28 // We're handling a centipede based fuzzer. If the last argument is a
29 // filepath, we're trying to replay a testcase, since it doesn't make sense
30 // to get a filepath when running with the centipede binary.
31 if (args.size() <= 1) {
32 return;
33 }
34 base::FilePath test_case(args.back());
35 if (!base::PathExists(test_case)) {
36 return;
37 }
38
39 auto env = base::Environment::Create();
40 #if BUILDFLAG(IS_WIN)
41 auto env_value = base::WideToUTF8(args.back());
42 #else
43 auto env_value = args.back();
44 #endif
45 env->SetVar("FUZZTEST_REPLAY", env_value);
46 env->UnSetVar("CENTIPEDE_RUNNER_FLAGS");
47 std::cerr << "FuzzTest wrapper setting env var: FUZZTEST_REPLAY="
48 << args.back() << '\n';
49
50 // We must not add the testcase to the command line, as this will not be
51 // parsed correctly by centipede.
52 args.pop_back();
53 }
54 } // namespace
55
56 #endif // BUILDFLAG(USE_CENTIPEDE)
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 #if BUILDFLAG(USE_CENTIPEDE)
74 HandleReplayMode(args);
75 #endif // BUILDFLAG(USE_CENTIPEDE)
76
77 bool skipped_first = false;
78 for (auto arg : args) {
79 if (!skipped_first) {
80 skipped_first = true;
81 continue;
82 }
83 // We avoid AppendArguments because it parses switches then reorders things.
84 cmdline.AppendArgNative(arg);
85 }
86 std::cerr << "FuzzTest wrapper launching:" << cmdline.GetCommandLineString()
87 << "\n";
88 base::Process p = base::LaunchProcess(cmdline, launch_options);
89 int exit_code;
90 p.WaitForExit(&exit_code);
91 return exit_code;
92 }
93
94 #if defined(WIN32)
95 #define ALWAYS_EXPORT __declspec(dllexport)
96 #else
97 #define ALWAYS_EXPORT __attribute__((visibility("default")))
98 #endif
99
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)100 ALWAYS_EXPORT extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data,
101 size_t size) {
102 // No-op. This symbol exists to ensure that this binary is detected as
103 // a fuzzer by ClusterFuzz's heuristics. It never actually gets called.
104 return -1;
105 }
106