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