xref: /aosp_15_r20/external/cronet/testing/libfuzzer/fuzztest_wrapper.cpp (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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