• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 Code Intelligence GmbH
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "fuzz_target_runner.h"
16 
17 #include <jni.h>
18 
19 #include <fstream>
20 #include <iomanip>
21 #include <iostream>
22 #include <string>
23 #include <vector>
24 
25 #include "absl/strings/escaping.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/str_format.h"
28 #include "absl/strings/str_replace.h"
29 #include "absl/strings/str_split.h"
30 #include "absl/strings/substitute.h"
31 #include "coverage_tracker.h"
32 #include "fuzzed_data_provider.h"
33 #include "gflags/gflags.h"
34 #include "glog/logging.h"
35 #include "java_reproducer.h"
36 #include "java_reproducer_templates.h"
37 #include "utils.h"
38 
39 DEFINE_string(
40     target_class, "",
41     "The Java class that contains the static fuzzerTestOneInput function");
42 DEFINE_string(target_args, "",
43               "Arguments passed to fuzzerInitialize as a String array. "
44               "Separated by space.");
45 
46 DEFINE_uint32(keep_going, 0,
47               "Continue fuzzing until N distinct exception stack traces have"
48               "been encountered. Defaults to exit after the first finding "
49               "unless --autofuzz is specified.");
50 DEFINE_bool(dedup, true,
51             "Emit a dedup token for every finding. Defaults to true and is "
52             "required for --keep_going and --ignore.");
53 DEFINE_string(
54     ignore, "",
55     "Comma-separated list of crash dedup tokens to ignore. This is useful to "
56     "continue fuzzing before a crash is fixed.");
57 
58 DEFINE_string(reproducer_path, ".",
59               "Path at which fuzzing reproducers are stored. Defaults to the "
60               "current directory.");
61 DEFINE_string(coverage_report, "",
62               "Path at which a coverage report is stored when the fuzzer "
63               "exits. If left empty, no report is generated (default)");
64 
65 DEFINE_string(autofuzz, "",
66               "Fully qualified reference to a method on the classpath that "
67               "should be fuzzed automatically (example: System.out::println). "
68               "Fuzzing will continue even after a finding; specify "
69               "--keep_going=N to stop after N findings.");
70 DEFINE_string(autofuzz_ignore, "",
71               "Fully qualified class names of exceptions to ignore during "
72               "autofuzz. Separated by comma.");
73 
74 DECLARE_bool(hooks);
75 
76 constexpr auto kManifestUtilsClass =
77     "com/code_intelligence/jazzer/runtime/ManifestUtils";
78 constexpr auto kJazzerClass =
79     "com/code_intelligence/jazzer/runtime/JazzerInternal";
80 constexpr auto kAutofuzzFuzzTargetClass =
81     "com/code_intelligence/jazzer/autofuzz/FuzzTarget";
82 
83 namespace jazzer {
84 // split a string on unescaped spaces
splitOnSpace(const std::string & s)85 std::vector<std::string> splitOnSpace(const std::string &s) {
86   if (s.empty()) {
87     return {};
88   }
89 
90   std::vector<std::string> tokens;
91   std::size_t token_begin = 0;
92   for (std::size_t i = 1; i < s.size() - 1; i++) {
93     // only split if the space is not escaped by a backslash "\"
94     if (s[i] == ' ' && s[i - 1] != '\\') {
95       // don't split on multiple spaces
96       if (i > token_begin + 1)
97         tokens.push_back(s.substr(token_begin, i - token_begin));
98       token_begin = i + 1;
99     }
100   }
101   tokens.push_back(s.substr(token_begin));
102   return tokens;
103 }
104 
FuzzTargetRunner(JVM & jvm,const std::vector<std::string> & additional_target_args)105 FuzzTargetRunner::FuzzTargetRunner(
106     JVM &jvm, const std::vector<std::string> &additional_target_args)
107     : ExceptionPrinter(jvm), jvm_(jvm), ignore_tokens_() {
108   auto &env = jvm.GetEnv();
109   if (!FLAGS_target_class.empty() && !FLAGS_autofuzz.empty()) {
110     std::cerr << "--target_class and --autofuzz cannot be specified together"
111               << std::endl;
112     exit(1);
113   }
114   if (!FLAGS_target_args.empty() && !FLAGS_autofuzz.empty()) {
115     std::cerr << "--target_args and --autofuzz cannot be specified together"
116               << std::endl;
117     exit(1);
118   }
119   if (FLAGS_autofuzz.empty() && !FLAGS_autofuzz_ignore.empty()) {
120     std::cerr << "--autofuzz_ignore requires --autofuzz" << std::endl;
121     exit(1);
122   }
123   if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) {
124     FLAGS_target_class = DetectFuzzTargetClass();
125   }
126   // If automatically detecting the fuzz target class failed, we expect it as
127   // the value of the --target_class argument.
128   if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) {
129     std::cerr << "Missing argument --target_class=<fuzz_target_class>"
130               << std::endl;
131     exit(1);
132   }
133   if (!FLAGS_autofuzz.empty()) {
134     FLAGS_target_class = kAutofuzzFuzzTargetClass;
135     if (FLAGS_keep_going == 0) {
136       FLAGS_keep_going = std::numeric_limits<gflags::uint32>::max();
137     }
138     // Pass the method reference string as the first argument to the generic
139     // autofuzz fuzz target. Subseqeuent arguments are interpreted as exception
140     // class names that should be ignored.
141     FLAGS_target_args = FLAGS_autofuzz;
142     if (!FLAGS_autofuzz_ignore.empty()) {
143       FLAGS_target_args = absl::StrCat(
144           FLAGS_target_args, " ",
145           absl::StrReplaceAll(FLAGS_autofuzz_ignore, {{",", " "}}));
146     }
147   }
148   // Set --keep_going to its real default.
149   if (FLAGS_keep_going == 0) {
150     FLAGS_keep_going = 1;
151   }
152   if ((!FLAGS_ignore.empty() || FLAGS_keep_going > 1) && !FLAGS_dedup) {
153     std::cerr << "--nodedup is not supported with --ignore or --keep_going"
154               << std::endl;
155     exit(1);
156   }
157   jazzer_ = jvm.FindClass(kJazzerClass);
158   last_finding_ =
159       env.GetStaticFieldID(jazzer_, "lastFinding", "Ljava/lang/Throwable;");
160 
161   jclass_ = jvm.FindClass(FLAGS_target_class);
162   // one of the following functions is required:
163   //    public static void fuzzerTestOneInput(byte[] input)
164   //    public static void fuzzerTestOneInput(FuzzedDataProvider data)
165   fuzzer_test_one_input_bytes_ =
166       jvm.GetStaticMethodID(jclass_, "fuzzerTestOneInput", "([B)V", false);
167   fuzzer_test_one_input_data_ = jvm.GetStaticMethodID(
168       jclass_, "fuzzerTestOneInput",
169       "(Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;)V", false);
170   bool using_bytes = fuzzer_test_one_input_bytes_ != nullptr;
171   bool using_data = fuzzer_test_one_input_data_ != nullptr;
172   // Fail if none ore both of the two possible fuzzerTestOneInput versions is
173   // defined in the class.
174   if (using_bytes == using_data) {
175     LOG(ERROR) << FLAGS_target_class
176                << " must define exactly one of the following two functions:";
177     LOG(ERROR) << "public static void fuzzerTestOneInput(byte[] ...)";
178     LOG(ERROR)
179         << "public static void fuzzerTestOneInput(FuzzedDataProvider ...)";
180     LOG(ERROR) << "Note: Fuzz targets returning boolean are no longer "
181                   "supported; exceptions should be thrown instead of "
182                   "returning true.";
183     exit(1);
184   }
185 
186   // check existence of optional methods for initialization and destruction
187   fuzzer_initialize_ =
188       jvm.GetStaticMethodID(jclass_, "fuzzerInitialize", "()V", false);
189   fuzzer_tear_down_ =
190       jvm.GetStaticMethodID(jclass_, "fuzzerTearDown", "()V", false);
191   fuzzer_initialize_with_args_ = jvm.GetStaticMethodID(
192       jclass_, "fuzzerInitialize", "([Ljava/lang/String;)V", false);
193 
194   auto fuzz_target_args_tokens = splitOnSpace(FLAGS_target_args);
195   fuzz_target_args_tokens.insert(fuzz_target_args_tokens.end(),
196                                  additional_target_args.begin(),
197                                  additional_target_args.end());
198 
199   if (fuzzer_initialize_with_args_) {
200     // fuzzerInitialize with arguments gets priority
201     jclass string_class = jvm.FindClass("java/lang/String");
202     jobjectArray arg_array = jvm.GetEnv().NewObjectArray(
203         fuzz_target_args_tokens.size(), string_class, nullptr);
204     for (jint i = 0; i < fuzz_target_args_tokens.size(); i++) {
205       jstring str = env.NewStringUTF(fuzz_target_args_tokens[i].c_str());
206       env.SetObjectArrayElement(arg_array, i, str);
207     }
208     env.CallStaticObjectMethod(jclass_, fuzzer_initialize_with_args_,
209                                arg_array);
210   } else if (fuzzer_initialize_) {
211     env.CallStaticVoidMethod(jclass_, fuzzer_initialize_);
212   } else {
213     LOG(INFO) << "did not call any fuzz target initialize functions";
214   }
215 
216   if (jthrowable exception = env.ExceptionOccurred()) {
217     LOG(ERROR) << "== Java Exception in fuzzerInitialize: ";
218     LOG(ERROR) << getStackTrace(exception);
219     std::exit(1);
220   }
221 
222   if (FLAGS_hooks) {
223     CoverageTracker::RecordInitialCoverage(env);
224   }
225   SetUpFuzzedDataProvider(jvm_.GetEnv());
226 
227   // Parse a comma-separated list of hex dedup tokens.
228   std::vector<std::string> str_ignore_tokens =
229       absl::StrSplit(FLAGS_ignore, ',');
230   for (const std::string &str_token : str_ignore_tokens) {
231     if (str_token.empty()) continue;
232     try {
233       ignore_tokens_.push_back(std::stoull(str_token, nullptr, 16));
234     } catch (...) {
235       LOG(ERROR) << "Invalid dedup token (expected up to 16 hex digits): '"
236                  << str_token << "'";
237       // Don't let libFuzzer print a crash stack trace.
238       _Exit(1);
239     }
240   }
241 }
242 
~FuzzTargetRunner()243 FuzzTargetRunner::~FuzzTargetRunner() {
244   if (FLAGS_hooks && !FLAGS_coverage_report.empty()) {
245     std::string report = CoverageTracker::ComputeCoverage(jvm_.GetEnv());
246     std::ofstream report_file(FLAGS_coverage_report);
247     if (report_file) {
248       report_file << report << std::flush;
249     } else {
250       LOG(ERROR) << "Failed to write coverage report to "
251                  << FLAGS_coverage_report;
252     }
253   }
254   if (fuzzer_tear_down_ != nullptr) {
255     std::cerr << "calling fuzzer teardown function" << std::endl;
256     jvm_.GetEnv().CallStaticVoidMethod(jclass_, fuzzer_tear_down_);
257     if (jthrowable exception = jvm_.GetEnv().ExceptionOccurred())
258       std::cerr << getStackTrace(exception) << std::endl;
259   }
260 }
261 
Run(const uint8_t * data,const std::size_t size)262 RunResult FuzzTargetRunner::Run(const uint8_t *data, const std::size_t size) {
263   auto &env = jvm_.GetEnv();
264   static std::size_t run_count = 0;
265   if (run_count < 2) {
266     run_count++;
267     // For the first two runs only, replay the coverage recorded from static
268     // initializers. libFuzzer cleared the coverage map after they ran and could
269     // fail to see any coverage, triggering an early exit, if we don't replay it
270     // here.
271     // https://github.com/llvm/llvm-project/blob/957a5e987444d3193575d6ad8afe6c75da00d794/compiler-rt/lib/fuzzer/FuzzerLoop.cpp#L804-L809
272     CoverageTracker::ReplayInitialCoverage(env);
273   }
274   if (fuzzer_test_one_input_data_ != nullptr) {
275     FeedFuzzedDataProvider(data, size);
276     env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_,
277                              GetFuzzedDataProviderJavaObject(jvm_));
278   } else {
279     jbyteArray byte_array = env.NewByteArray(size);
280     if (byte_array == nullptr) {
281       env.ExceptionDescribe();
282       throw std::runtime_error(std::string("Cannot create byte array"));
283     }
284     env.SetByteArrayRegion(byte_array, 0, size,
285                            reinterpret_cast<const jbyte *>(data));
286     env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_bytes_, byte_array);
287     env.DeleteLocalRef(byte_array);
288   }
289 
290   const auto finding = GetFinding();
291   if (finding != nullptr) {
292     jlong dedup_token = computeDedupToken(finding);
293     // Check whether this stack trace has been encountered before if
294     // `--keep_going` has been supplied.
295     if (dedup_token != 0 && FLAGS_keep_going > 1 &&
296         std::find(ignore_tokens_.cbegin(), ignore_tokens_.cend(),
297                   dedup_token) != ignore_tokens_.end()) {
298       env.DeleteLocalRef(finding);
299       return RunResult::kOk;
300     } else {
301       ignore_tokens_.push_back(dedup_token);
302       std::cout << std::endl;
303       std::cerr << "== Java Exception: " << getStackTrace(finding);
304       env.DeleteLocalRef(finding);
305       if (FLAGS_dedup) {
306         std::cout << "DEDUP_TOKEN: " << std::hex << std::setfill('0')
307                   << std::setw(16) << dedup_token << std::endl;
308       }
309       if (ignore_tokens_.size() < static_cast<std::size_t>(FLAGS_keep_going)) {
310         return RunResult::kDumpAndContinue;
311       } else {
312         return RunResult::kException;
313       }
314     }
315   }
316   return RunResult::kOk;
317 }
318 
319 // Returns a fuzzer finding as a Throwable (or nullptr if there is none),
320 // clearing any JVM exceptions in the process.
GetFinding() const321 jthrowable FuzzTargetRunner::GetFinding() const {
322   auto &env = jvm_.GetEnv();
323   jthrowable unprocessed_finding = nullptr;
324   if (env.ExceptionCheck()) {
325     unprocessed_finding = env.ExceptionOccurred();
326     env.ExceptionClear();
327   }
328   // Explicitly reported findings take precedence over uncaught exceptions.
329   if (auto reported_finding =
330           (jthrowable)env.GetStaticObjectField(jazzer_, last_finding_);
331       reported_finding != nullptr) {
332     env.DeleteLocalRef(unprocessed_finding);
333     unprocessed_finding = reported_finding;
334   }
335   jthrowable processed_finding = preprocessException(unprocessed_finding);
336   env.DeleteLocalRef(unprocessed_finding);
337   return processed_finding;
338 }
339 
DumpReproducer(const uint8_t * data,std::size_t size)340 void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) {
341   auto &env = jvm_.GetEnv();
342   std::string base64_data;
343   if (fuzzer_test_one_input_data_) {
344     // Record the data retrieved from the FuzzedDataProvider and supply it to a
345     // Java-only CannedFuzzedDataProvider in the reproducer.
346     FeedFuzzedDataProvider(data, size);
347     jobject recorder = GetRecordingFuzzedDataProviderJavaObject(jvm_);
348     env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_, recorder);
349     const auto finding = GetFinding();
350     if (finding == nullptr) {
351       LOG(ERROR) << "Failed to reproduce crash when rerunning with recorder";
352       return;
353     }
354     base64_data = SerializeRecordingFuzzedDataProvider(jvm_, recorder);
355   } else {
356     absl::string_view data_str(reinterpret_cast<const char *>(data), size);
357     absl::Base64Escape(data_str, &base64_data);
358   }
359   const char *fuzz_target_call = fuzzer_test_one_input_data_
360                                      ? kTestOneInputWithData
361                                      : kTestOneInputWithBytes;
362   std::string data_sha1 = jazzer::Sha1Hash(data, size);
363   std::string reproducer =
364       absl::Substitute(kBaseReproducer, data_sha1, base64_data,
365                        FLAGS_target_class, fuzz_target_call);
366   std::string reproducer_filename = absl::StrFormat("Crash_%s.java", data_sha1);
367   std::string reproducer_full_path = absl::StrFormat(
368       "%s%c%s", FLAGS_reproducer_path, kPathSeparator, reproducer_filename);
369   std::ofstream reproducer_out(reproducer_full_path);
370   reproducer_out << reproducer;
371   std::cout << absl::StrFormat(
372                    "reproducer_path='%s'; Java reproducer written to %s",
373                    FLAGS_reproducer_path, reproducer_full_path)
374             << std::endl;
375 }
376 
DetectFuzzTargetClass() const377 std::string FuzzTargetRunner::DetectFuzzTargetClass() const {
378   jclass manifest_utils = jvm_.FindClass(kManifestUtilsClass);
379   jmethodID detect_fuzz_target_class = jvm_.GetStaticMethodID(
380       manifest_utils, "detectFuzzTargetClass", "()Ljava/lang/String;", true);
381   auto &env = jvm_.GetEnv();
382   auto jni_fuzz_target_class = (jstring)(env.CallStaticObjectMethod(
383       manifest_utils, detect_fuzz_target_class));
384   if (env.ExceptionCheck()) {
385     env.ExceptionDescribe();
386     exit(1);
387   }
388   if (jni_fuzz_target_class == nullptr) return "";
389 
390   const char *fuzz_target_class_cstr =
391       env.GetStringUTFChars(jni_fuzz_target_class, nullptr);
392   std::string fuzz_target_class = std::string(fuzz_target_class_cstr);
393   env.ReleaseStringUTFChars(jni_fuzz_target_class, fuzz_target_class_cstr);
394   env.DeleteLocalRef(jni_fuzz_target_class);
395 
396   return fuzz_target_class;
397 }
398 }  // namespace jazzer
399