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 /**
16 * A native wrapper around the FuzzTargetRunner Java class that executes it as a
17 * libFuzzer fuzz target.
18 */
19
20 #include "fuzz_target_runner.h"
21
22 #ifndef _WIN32
23 #include <dlfcn.h>
24 #endif
25 #include <jni.h>
26
27 #include <iostream>
28 #include <limits>
29 #include <string>
30 #include <vector>
31
32 #include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h"
33
34 extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv,
35 int (*UserCb)(const uint8_t *Data,
36 size_t Size));
37
38 namespace {
39 jclass gRunner;
40 jmethodID gRunOneId;
41 JavaVM *gJavaVm;
42 JNIEnv *gEnv;
43
44 // A libFuzzer-registered callback that outputs the crashing input, but does
45 // not include a stack trace.
46 void (*gLibfuzzerPrintCrashingInput)() = nullptr;
47
testOneInput(const uint8_t * data,const std::size_t size)48 int testOneInput(const uint8_t *data, const std::size_t size) {
49 JNIEnv &env = *gEnv;
50 jint jsize =
51 std::min(size, static_cast<size_t>(std::numeric_limits<jint>::max()));
52 int res = env.CallStaticIntMethod(gRunner, gRunOneId, data, jsize);
53 if (env.ExceptionCheck()) {
54 env.ExceptionDescribe();
55 _Exit(1);
56 }
57 return res;
58 }
59 } // namespace
60
61 namespace jazzer {
DumpJvmStackTraces()62 void DumpJvmStackTraces() {
63 JNIEnv *env = nullptr;
64 if (gJavaVm->AttachCurrentThread(reinterpret_cast<void **>(&env), nullptr) !=
65 JNI_OK) {
66 std::cerr << "WARN: AttachCurrentThread failed in DumpJvmStackTraces"
67 << std::endl;
68 return;
69 }
70 jmethodID dumpStack =
71 env->GetStaticMethodID(gRunner, "dumpAllStackTraces", "()V");
72 if (env->ExceptionCheck()) {
73 env->ExceptionDescribe();
74 return;
75 }
76 env->CallStaticVoidMethod(gRunner, dumpStack);
77 if (env->ExceptionCheck()) {
78 env->ExceptionDescribe();
79 return;
80 }
81 // Do not detach as we may be the main thread (but the JVM exits anyway).
82 }
83 } // namespace jazzer
84
85 [[maybe_unused]] jint
Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_startLibFuzzer(JNIEnv * env,jclass runner,jobjectArray args)86 Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_startLibFuzzer(
87 JNIEnv *env, jclass runner, jobjectArray args) {
88 gEnv = env;
89 env->GetJavaVM(&gJavaVm);
90 gRunner = reinterpret_cast<jclass>(env->NewGlobalRef(runner));
91 gRunOneId = env->GetStaticMethodID(runner, "runOne", "(JI)I");
92 if (gRunOneId == nullptr) {
93 env->ExceptionDescribe();
94 _Exit(1);
95 }
96
97 int argc = env->GetArrayLength(args);
98 if (env->ExceptionCheck()) {
99 env->ExceptionDescribe();
100 _Exit(1);
101 }
102 std::vector<std::string> argv_strings;
103 std::vector<const char *> argv_c;
104 for (jsize i = 0; i < argc; i++) {
105 auto arg_jni =
106 reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(args, i));
107 if (arg_jni == nullptr) {
108 env->ExceptionDescribe();
109 _Exit(1);
110 }
111 jbyte *arg_c = env->GetByteArrayElements(arg_jni, nullptr);
112 if (arg_c == nullptr) {
113 env->ExceptionDescribe();
114 _Exit(1);
115 }
116 std::size_t arg_size = env->GetArrayLength(arg_jni);
117 if (env->ExceptionCheck()) {
118 env->ExceptionDescribe();
119 _Exit(1);
120 }
121 argv_strings.emplace_back(reinterpret_cast<const char *>(arg_c), arg_size);
122 env->ReleaseByteArrayElements(arg_jni, arg_c, JNI_ABORT);
123 if (env->ExceptionCheck()) {
124 env->ExceptionDescribe();
125 _Exit(1);
126 }
127 }
128 for (jsize i = 0; i < argc; i++) {
129 argv_c.emplace_back(argv_strings[i].c_str());
130 }
131 // Null-terminate argv.
132 argv_c.emplace_back(nullptr);
133
134 const char **argv = argv_c.data();
135 return LLVMFuzzerRunDriver(&argc, const_cast<char ***>(&argv), testOneInput);
136 }
137
138 [[maybe_unused]] void
Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput(JNIEnv *,jclass)139 Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput(
140 JNIEnv *, jclass) {
141 if (gLibfuzzerPrintCrashingInput == nullptr) {
142 std::cerr << "<not available>" << std::endl;
143 } else {
144 gLibfuzzerPrintCrashingInput();
145 }
146 }
147
148 [[maybe_unused]] void
Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit(JNIEnv *,jclass,jint exit_code)149 Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit(
150 JNIEnv *, jclass, jint exit_code) {
151 _Exit(exit_code);
152 }
153
154 // We apply a patch to libFuzzer to make it call this function instead of
155 // __sanitizer_set_death_callback to pass us the death callback.
__jazzer_set_death_callback(void (* callback)())156 extern "C" [[maybe_unused]] void __jazzer_set_death_callback(
157 void (*callback)()) {
158 gLibfuzzerPrintCrashingInput = callback;
159 #ifndef _WIN32
160 void *sanitizer_set_death_callback =
161 dlsym(RTLD_DEFAULT, "__sanitizer_set_death_callback");
162 if (sanitizer_set_death_callback != nullptr) {
163 (reinterpret_cast<void (*)(void (*)())>(sanitizer_set_death_callback))(
164 []() {
165 ::jazzer::DumpJvmStackTraces();
166 gLibfuzzerPrintCrashingInput();
167 // Ideally, we would be able to perform a graceful shutdown of the
168 // JVM. However, doing this directly results in a nested bug report by
169 // ASan or UBSan, likely because something about the stack/thread
170 // context in which they generate reports is incompatible with the JVM
171 // shutdown process. use_sigaltstack=0 does not help though, so this
172 // might be on us.
173 });
174 }
175 #endif
176 }
177