• 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 "libfuzzer_callbacks.h"
16 
17 #include <jni.h>
18 
19 #include <fstream>
20 #include <iostream>
21 #include <mutex>
22 #include <utility>
23 #include <vector>
24 
25 #include "absl/strings/match.h"
26 #include "absl/strings/str_format.h"
27 #include "absl/strings/str_split.h"
28 #include "gflags/gflags.h"
29 #include "glog/logging.h"
30 #include "sanitizer_hooks_with_pc.h"
31 
32 DEFINE_bool(
33     fake_pcs, false,
34     "Supply synthetic Java program counters to libFuzzer trace hooks to "
35     "make value profiling more effective. Enabled by default if "
36     "-use_value_profile=1 is specified.");
37 
38 namespace {
39 
40 const char kLibfuzzerTraceDataFlowHooksClass[] =
41     "com/code_intelligence/jazzer/runtime/"
42     "TraceDataFlowNativeCallbacks";
43 
44 extern "C" {
45 void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1,
46                                   const void *s2, std::size_t n, int result);
47 void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1,
48                                          const void *s2, std::size_t n1,
49                                          std::size_t n2, int result);
50 void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1,
51                                   const char *s2, int result);
52 void __sanitizer_weak_hook_strstr(void *caller_pc, const char *s1,
53                                   const char *s2, const char *result);
54 void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2);
55 void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2);
56 
57 void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases);
58 
59 void __sanitizer_cov_trace_div4(uint32_t val);
60 void __sanitizer_cov_trace_div8(uint64_t val);
61 
62 void __sanitizer_cov_trace_gep(uintptr_t idx);
63 }
64 
idToPc(jint id)65 inline __attribute__((always_inline)) void *idToPc(jint id) {
66   return reinterpret_cast<void *>(static_cast<uintptr_t>(id));
67 }
68 
libfuzzerStringCompareCallback(JNIEnv & env,jclass cls,jstring s1,jstring s2,jint result,jint id)69 void JNICALL libfuzzerStringCompareCallback(JNIEnv &env, jclass cls, jstring s1,
70                                             jstring s2, jint result, jint id) {
71   const char *s1_native = env.GetStringUTFChars(s1, nullptr);
72   if (env.ExceptionCheck()) env.ExceptionDescribe();
73   std::size_t n1 = env.GetStringUTFLength(s1);
74   if (env.ExceptionCheck()) env.ExceptionDescribe();
75   const char *s2_native = env.GetStringUTFChars(s2, nullptr);
76   if (env.ExceptionCheck()) env.ExceptionDescribe();
77   std::size_t n2 = env.GetStringUTFLength(s2);
78   if (env.ExceptionCheck()) env.ExceptionDescribe();
79   __sanitizer_weak_hook_compare_bytes(idToPc(id), s1_native, s2_native, n1, n2,
80                                       result);
81   env.ReleaseStringUTFChars(s1, s1_native);
82   if (env.ExceptionCheck()) env.ExceptionDescribe();
83   env.ReleaseStringUTFChars(s2, s2_native);
84   if (env.ExceptionCheck()) env.ExceptionDescribe();
85 }
86 
libfuzzerStringContainCallback(JNIEnv & env,jclass cls,jstring s1,jstring s2,jint id)87 void JNICALL libfuzzerStringContainCallback(JNIEnv &env, jclass cls, jstring s1,
88                                             jstring s2, jint id) {
89   const char *s1_native = env.GetStringUTFChars(s1, nullptr);
90   if (env.ExceptionCheck()) env.ExceptionDescribe();
91   const char *s2_native = env.GetStringUTFChars(s2, nullptr);
92   if (env.ExceptionCheck()) env.ExceptionDescribe();
93   // libFuzzer currently ignores the result, which allows us to simply pass a
94   // valid but arbitrary pointer here instead of performing an actual strstr
95   // operation.
96   __sanitizer_weak_hook_strstr(idToPc(id), s1_native, s2_native, s1_native);
97   env.ReleaseStringUTFChars(s1, s1_native);
98   if (env.ExceptionCheck()) env.ExceptionDescribe();
99   env.ReleaseStringUTFChars(s2, s2_native);
100   if (env.ExceptionCheck()) env.ExceptionDescribe();
101 }
102 
libfuzzerByteCompareCallback(JNIEnv & env,jclass cls,jbyteArray b1,jbyteArray b2,jint result,jint id)103 void JNICALL libfuzzerByteCompareCallback(JNIEnv &env, jclass cls,
104                                           jbyteArray b1, jbyteArray b2,
105                                           jint result, jint id) {
106   jbyte *b1_native = env.GetByteArrayElements(b1, nullptr);
107   if (env.ExceptionCheck()) env.ExceptionDescribe();
108   jbyte *b2_native = env.GetByteArrayElements(b2, nullptr);
109   if (env.ExceptionCheck()) env.ExceptionDescribe();
110   jint b1_length = env.GetArrayLength(b1);
111   if (env.ExceptionCheck()) env.ExceptionDescribe();
112   jint b2_length = env.GetArrayLength(b2);
113   if (env.ExceptionCheck()) env.ExceptionDescribe();
114   __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native,
115                                       b1_length, b2_length, result);
116   env.ReleaseByteArrayElements(b1, b1_native, JNI_ABORT);
117   if (env.ExceptionCheck()) env.ExceptionDescribe();
118   env.ReleaseByteArrayElements(b2, b2_native, JNI_ABORT);
119   if (env.ExceptionCheck()) env.ExceptionDescribe();
120 }
121 
libfuzzerLongCompareCallback(JNIEnv & env,jclass cls,jlong value1,jlong value2,jint id)122 void JNICALL libfuzzerLongCompareCallback(JNIEnv &env, jclass cls, jlong value1,
123                                           jlong value2, jint id) {
124   __sanitizer_cov_trace_cmp8(value1, value2);
125 }
126 
libfuzzerLongCompareCallbackWithPc(JNIEnv & env,jclass cls,jlong value1,jlong value2,jint id)127 void JNICALL libfuzzerLongCompareCallbackWithPc(JNIEnv &env, jclass cls,
128                                                 jlong value1, jlong value2,
129                                                 jint id) {
130   __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2);
131 }
132 
libfuzzerIntCompareCallback(JNIEnv & env,jclass cls,jint value1,jint value2,jint id)133 void JNICALL libfuzzerIntCompareCallback(JNIEnv &env, jclass cls, jint value1,
134                                          jint value2, jint id) {
135   __sanitizer_cov_trace_cmp4(value1, value2);
136 }
137 
libfuzzerIntCompareCallbackWithPc(JNIEnv & env,jclass cls,jint value1,jint value2,jint id)138 void JNICALL libfuzzerIntCompareCallbackWithPc(JNIEnv &env, jclass cls,
139                                                jint value1, jint value2,
140                                                jint id) {
141   __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2);
142 }
143 
libfuzzerSwitchCaseCallback(JNIEnv & env,jclass cls,jlong switch_value,jlongArray libfuzzer_case_values,jint id)144 void JNICALL libfuzzerSwitchCaseCallback(JNIEnv &env, jclass cls,
145                                          jlong switch_value,
146                                          jlongArray libfuzzer_case_values,
147                                          jint id) {
148   jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr);
149   if (env.ExceptionCheck()) env.ExceptionDescribe();
150   __sanitizer_cov_trace_switch(switch_value,
151                                reinterpret_cast<uint64_t *>(case_values));
152   env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT);
153   if (env.ExceptionCheck()) env.ExceptionDescribe();
154 }
155 
libfuzzerSwitchCaseCallbackWithPc(JNIEnv & env,jclass cls,jlong switch_value,jlongArray libfuzzer_case_values,jint id)156 void JNICALL libfuzzerSwitchCaseCallbackWithPc(JNIEnv &env, jclass cls,
157                                                jlong switch_value,
158                                                jlongArray libfuzzer_case_values,
159                                                jint id) {
160   jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr);
161   if (env.ExceptionCheck()) env.ExceptionDescribe();
162   __sanitizer_cov_trace_switch_with_pc(
163       idToPc(id), switch_value, reinterpret_cast<uint64_t *>(case_values));
164   env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT);
165   if (env.ExceptionCheck()) env.ExceptionDescribe();
166 }
167 
libfuzzerLongDivCallback(JNIEnv & env,jclass cls,jlong value,jint id)168 void JNICALL libfuzzerLongDivCallback(JNIEnv &env, jclass cls, jlong value,
169                                       jint id) {
170   __sanitizer_cov_trace_div8(value);
171 }
172 
libfuzzerLongDivCallbackWithPc(JNIEnv & env,jclass cls,jlong value,jint id)173 void JNICALL libfuzzerLongDivCallbackWithPc(JNIEnv &env, jclass cls,
174                                             jlong value, jint id) {
175   __sanitizer_cov_trace_div8_with_pc(idToPc(id), value);
176 }
177 
libfuzzerIntDivCallback(JNIEnv & env,jclass cls,jint value,jint id)178 void JNICALL libfuzzerIntDivCallback(JNIEnv &env, jclass cls, jint value,
179                                      jint id) {
180   __sanitizer_cov_trace_div4(value);
181 }
182 
libfuzzerIntDivCallbackWithPc(JNIEnv & env,jclass cls,jint value,jint id)183 void JNICALL libfuzzerIntDivCallbackWithPc(JNIEnv &env, jclass cls, jint value,
184                                            jint id) {
185   __sanitizer_cov_trace_div4_with_pc(idToPc(id), value);
186 }
187 
libfuzzerGepCallback(JNIEnv & env,jclass cls,jlong idx,jint id)188 void JNICALL libfuzzerGepCallback(JNIEnv &env, jclass cls, jlong idx, jint id) {
189   __sanitizer_cov_trace_gep(static_cast<uintptr_t>(idx));
190 }
191 
libfuzzerGepCallbackWithPc(JNIEnv & env,jclass cls,jlong idx,jint id)192 void JNICALL libfuzzerGepCallbackWithPc(JNIEnv &env, jclass cls, jlong idx,
193                                         jint id) {
194   __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast<uintptr_t>(idx));
195 }
196 
libfuzzerPcIndirCallback(JNIEnv & env,jclass cls,jint caller_id,jint callee_id)197 void JNICALL libfuzzerPcIndirCallback(JNIEnv &env, jclass cls, jint caller_id,
198                                       jint callee_id) {
199   __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id),
200                                          static_cast<uintptr_t>(callee_id));
201 }
202 
203 bool is_using_native_libraries = false;
204 std::once_flag ignore_list_flag;
205 std::vector<std::pair<uintptr_t, uintptr_t>> ignore_for_interception_ranges;
206 
__sanitizer_weak_is_relevant_pc(void * caller_pc)207 extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc(
208     void *caller_pc) {
209   // If the fuzz target is not using native libraries, calls to strcmp, memcmp,
210   // etc. should never be intercepted. The values reported if they were at best
211   // duplicate the values received from our bytecode instrumentation and at
212   // worst pollute the table of recent compares with string internal to the JDK.
213   if (!is_using_native_libraries) return false;
214   // If the fuzz target is using native libraries, intercept calls only if they
215   // don't originate from those address ranges that are known to belong to the
216   // JDK.
217   return std::none_of(ignore_for_interception_ranges.cbegin(),
218                       ignore_for_interception_ranges.cend(),
219                       [caller_pc](const auto &range) {
220                         uintptr_t start;
221                         uintptr_t end;
222                         std::tie(start, end) = range;
223                         auto address = reinterpret_cast<uintptr_t>(caller_pc);
224                         return start <= address && address <= end;
225                       });
226 }
227 
228 /**
229  * Adds the address ranges of executable segmentes of the library lib_name to
230  * the ignorelist for C standard library function interception (strcmp, memcmp,
231  * ...).
232  */
ignoreLibraryForInterception(const std::string & lib_name)233 void ignoreLibraryForInterception(const std::string &lib_name) {
234   const auto num_address_ranges = ignore_for_interception_ranges.size();
235   std::ifstream loaded_libs("/proc/self/maps");
236   if (!loaded_libs) {
237     // This early exit is taken e.g. on macOS, where /proc does not exist.
238     return;
239   }
240   std::string line;
241   while (std::getline(loaded_libs, line)) {
242     if (!absl::StrContains(line, lib_name)) continue;
243     // clang-format off
244     // A typical line looks as follows:
245     // 7f15356c9000-7f1536367000 r-xp 0020d000 fd:01 19275673         /usr/lib/jvm/java-15-openjdk-amd64/lib/server/libjvm.so
246     // clang-format on
247     std::vector<std::string_view> parts =
248         absl::StrSplit(line, ' ', absl::SkipEmpty());
249     if (parts.size() != 6) {
250       std::cout << "ERROR: Invalid format for /proc/self/maps\n"
251                 << line << std::endl;
252       exit(1);
253     }
254     // Skip non-executable address rang"s.
255     if (!absl::StrContains(parts[1], "x")) continue;
256     std::string_view range_str = parts[0];
257     std::vector<std::string> range = absl::StrSplit(range_str, "-");
258     if (range.size() != 2) {
259       std::cout
260           << "ERROR: Unexpected address range format in /proc/self/maps line: "
261           << range_str << std::endl;
262       exit(1);
263     }
264     std::size_t pos;
265     auto start = std::stoull(range[0], &pos, 16);
266     if (pos != range[0].size()) {
267       std::cout
268           << "ERROR: Unexpected address range format in /proc/self/maps line: "
269           << range_str << std::endl;
270       exit(1);
271     }
272     auto end = std::stoull(range[1], &pos, 16);
273     if (pos != range[0].size()) {
274       std::cout
275           << "ERROR: Unexpected address range format in /proc/self/maps line: "
276           << range_str << std::endl;
277       exit(1);
278     }
279     ignore_for_interception_ranges.emplace_back(start, end);
280   }
281   const auto num_code_segments =
282       ignore_for_interception_ranges.size() - num_address_ranges;
283   LOG(INFO) << "added " << num_code_segments
284             << " code segment of native library " << lib_name
285             << " to interceptor ignorelist";
286 }
287 
288 const std::vector<std::string> kLibrariesToIgnoreForInterception = {
289     // The driver executable itself can be treated just like a library.
290     "jazzer_driver", "libinstrument.so", "libjava.so",
291     "libjimage.so",  "libjli.so",        "libjvm.so",
292     "libnet.so",     "libverify.so",     "libzip.so",
293 };
294 
handleLibraryLoad(JNIEnv & env,jclass cls)295 void JNICALL handleLibraryLoad(JNIEnv &env, jclass cls) {
296   std::call_once(ignore_list_flag, [] {
297     LOG(INFO)
298         << "detected a native library load, enabling interception for libc "
299            "functions";
300     for (const auto &lib_name : kLibrariesToIgnoreForInterception)
301       ignoreLibraryForInterception(lib_name);
302     // Enable the ignore list after it has been populated since vector is not
303     // thread-safe with respect to concurrent writes and reads.
304     is_using_native_libraries = true;
305   });
306 }
307 
registerCallback(JNIEnv & env,const char * java_hooks_class_name,const JNINativeMethod * methods,int num_methods)308 void registerCallback(JNIEnv &env, const char *java_hooks_class_name,
309                       const JNINativeMethod *methods, int num_methods) {
310   auto java_hooks_class = env.FindClass(java_hooks_class_name);
311   if (java_hooks_class == nullptr) {
312     env.ExceptionDescribe();
313     throw std::runtime_error(
314         absl::StrFormat("could not find class %s", java_hooks_class_name));
315   }
316   LOG(INFO) << "registering hooks for class " << java_hooks_class_name;
317   env.RegisterNatives(java_hooks_class, methods, num_methods);
318   if (env.ExceptionCheck()) {
319     env.ExceptionDescribe();
320     throw std::runtime_error("could not register native callbacks");
321   }
322 }
323 }  // namespace
324 
325 namespace jazzer {
326 
registerFuzzerCallbacks(JNIEnv & env)327 bool registerFuzzerCallbacks(JNIEnv &env) {
328   if (FLAGS_fake_pcs) {
329     LOG(INFO) << "using callback variants with fake pcs";
330     CalibrateTrampoline();
331   }
332   {
333     JNINativeMethod string_methods[]{
334         {(char *)"traceMemcmp", (char *)"([B[BII)V",
335          (void *)&libfuzzerByteCompareCallback},
336         {(char *)"traceStrcmp",
337          (char *)"(Ljava/lang/String;Ljava/lang/String;II)V",
338          (void *)&libfuzzerStringCompareCallback},
339         {(char *)"traceStrstr",
340          (char *)"(Ljava/lang/String;Ljava/lang/String;I)V",
341          (void *)&libfuzzerStringContainCallback}};
342 
343     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, string_methods,
344                      sizeof(string_methods) / sizeof(string_methods[0]));
345   }
346 
347   {
348     JNINativeMethod cmp_methods[]{
349         {(char *)"traceCmpLong", (char *)"(JJI)V",
350          (void *)(FLAGS_fake_pcs ? &libfuzzerLongCompareCallbackWithPc
351                                  : &libfuzzerLongCompareCallback)},
352         {(char *)"traceCmpInt", (char *)"(III)V",
353          (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc
354                                  : &libfuzzerIntCompareCallback)},
355         // libFuzzer internally treats const comparisons the same as
356         // non-constant cmps.
357         {(char *)"traceConstCmpInt", (char *)"(III)V",
358          (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc
359                                  : &libfuzzerIntCompareCallback)},
360         {(char *)"traceSwitch", (char *)"(J[JI)V",
361          (void *)(FLAGS_fake_pcs ? &libfuzzerSwitchCaseCallbackWithPc
362                                  : &libfuzzerSwitchCaseCallback)}};
363 
364     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, cmp_methods,
365                      sizeof(cmp_methods) / sizeof(cmp_methods[0]));
366   }
367 
368   {
369     JNINativeMethod div_methods[]{
370         {(char *)"traceDivLong", (char *)"(JI)V",
371          (void *)(FLAGS_fake_pcs ? &libfuzzerLongDivCallbackWithPc
372                                  : &libfuzzerLongDivCallback)},
373         {(char *)"traceDivInt", (char *)"(II)V",
374          (void *)(FLAGS_fake_pcs ? &libfuzzerIntDivCallbackWithPc
375                                  : &libfuzzerIntDivCallback)}};
376 
377     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, div_methods,
378                      sizeof(div_methods) / sizeof(div_methods[0]));
379   }
380 
381   {
382     JNINativeMethod gep_methods[]{
383         {(char *)"traceGep", (char *)"(JI)V",
384          (void *)(FLAGS_fake_pcs ? &libfuzzerGepCallbackWithPc
385                                  : &libfuzzerGepCallback)}};
386 
387     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, gep_methods,
388                      sizeof(gep_methods) / sizeof(gep_methods[0]));
389   }
390 
391   {
392     JNINativeMethod indir_methods[]{{(char *)"tracePcIndir", (char *)"(II)V",
393                                      (void *)(&libfuzzerPcIndirCallback)}};
394 
395     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, indir_methods,
396                      sizeof(indir_methods) / sizeof(indir_methods[0]));
397   }
398 
399   {
400     JNINativeMethod native_methods[]{{(char *)"handleLibraryLoad",
401                                       (char *)"()V",
402                                       (void *)(&handleLibraryLoad)}};
403 
404     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, native_methods,
405                      sizeof(native_methods) / sizeof(native_methods[0]));
406   }
407 
408   return env.ExceptionCheck();
409 }
410 
411 }  // namespace jazzer
412