412 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			412 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
| // Copyright 2021 Code Intelligence GmbH
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //      http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| #include "libfuzzer_callbacks.h"
 | |
| 
 | |
| #include <jni.h>
 | |
| 
 | |
| #include <fstream>
 | |
| #include <iostream>
 | |
| #include <mutex>
 | |
| #include <utility>
 | |
| #include <vector>
 | |
| 
 | |
| #include "absl/strings/match.h"
 | |
| #include "absl/strings/str_format.h"
 | |
| #include "absl/strings/str_split.h"
 | |
| #include "gflags/gflags.h"
 | |
| #include "glog/logging.h"
 | |
| #include "sanitizer_hooks_with_pc.h"
 | |
| 
 | |
| DEFINE_bool(
 | |
|     fake_pcs, false,
 | |
|     "Supply synthetic Java program counters to libFuzzer trace hooks to "
 | |
|     "make value profiling more effective. Enabled by default if "
 | |
|     "-use_value_profile=1 is specified.");
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| const char kLibfuzzerTraceDataFlowHooksClass[] =
 | |
|     "com/code_intelligence/jazzer/runtime/"
 | |
|     "TraceDataFlowNativeCallbacks";
 | |
| 
 | |
| extern "C" {
 | |
| void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1,
 | |
|                                   const void *s2, std::size_t n, int result);
 | |
| void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1,
 | |
|                                          const void *s2, std::size_t n1,
 | |
|                                          std::size_t n2, int result);
 | |
| void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1,
 | |
|                                   const char *s2, int result);
 | |
| void __sanitizer_weak_hook_strstr(void *caller_pc, const char *s1,
 | |
|                                   const char *s2, const char *result);
 | |
| void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2);
 | |
| void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2);
 | |
| 
 | |
| void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases);
 | |
| 
 | |
| void __sanitizer_cov_trace_div4(uint32_t val);
 | |
| void __sanitizer_cov_trace_div8(uint64_t val);
 | |
| 
 | |
| void __sanitizer_cov_trace_gep(uintptr_t idx);
 | |
| }
 | |
| 
 | |
| inline __attribute__((always_inline)) void *idToPc(jint id) {
 | |
|   return reinterpret_cast<void *>(static_cast<uintptr_t>(id));
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerStringCompareCallback(JNIEnv &env, jclass cls, jstring s1,
 | |
|                                             jstring s2, jint result, jint id) {
 | |
|   const char *s1_native = env.GetStringUTFChars(s1, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   std::size_t n1 = env.GetStringUTFLength(s1);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   const char *s2_native = env.GetStringUTFChars(s2, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   std::size_t n2 = env.GetStringUTFLength(s2);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   __sanitizer_weak_hook_compare_bytes(idToPc(id), s1_native, s2_native, n1, n2,
 | |
|                                       result);
 | |
|   env.ReleaseStringUTFChars(s1, s1_native);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   env.ReleaseStringUTFChars(s2, s2_native);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerStringContainCallback(JNIEnv &env, jclass cls, jstring s1,
 | |
|                                             jstring s2, jint id) {
 | |
|   const char *s1_native = env.GetStringUTFChars(s1, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   const char *s2_native = env.GetStringUTFChars(s2, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   // libFuzzer currently ignores the result, which allows us to simply pass a
 | |
|   // valid but arbitrary pointer here instead of performing an actual strstr
 | |
|   // operation.
 | |
|   __sanitizer_weak_hook_strstr(idToPc(id), s1_native, s2_native, s1_native);
 | |
|   env.ReleaseStringUTFChars(s1, s1_native);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   env.ReleaseStringUTFChars(s2, s2_native);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerByteCompareCallback(JNIEnv &env, jclass cls,
 | |
|                                           jbyteArray b1, jbyteArray b2,
 | |
|                                           jint result, jint id) {
 | |
|   jbyte *b1_native = env.GetByteArrayElements(b1, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   jbyte *b2_native = env.GetByteArrayElements(b2, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   jint b1_length = env.GetArrayLength(b1);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   jint b2_length = env.GetArrayLength(b2);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native,
 | |
|                                       b1_length, b2_length, result);
 | |
|   env.ReleaseByteArrayElements(b1, b1_native, JNI_ABORT);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   env.ReleaseByteArrayElements(b2, b2_native, JNI_ABORT);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerLongCompareCallback(JNIEnv &env, jclass cls, jlong value1,
 | |
|                                           jlong value2, jint id) {
 | |
|   __sanitizer_cov_trace_cmp8(value1, value2);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerLongCompareCallbackWithPc(JNIEnv &env, jclass cls,
 | |
|                                                 jlong value1, jlong value2,
 | |
|                                                 jint id) {
 | |
|   __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerIntCompareCallback(JNIEnv &env, jclass cls, jint value1,
 | |
|                                          jint value2, jint id) {
 | |
|   __sanitizer_cov_trace_cmp4(value1, value2);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerIntCompareCallbackWithPc(JNIEnv &env, jclass cls,
 | |
|                                                jint value1, jint value2,
 | |
|                                                jint id) {
 | |
|   __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerSwitchCaseCallback(JNIEnv &env, jclass cls,
 | |
|                                          jlong switch_value,
 | |
|                                          jlongArray libfuzzer_case_values,
 | |
|                                          jint id) {
 | |
|   jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   __sanitizer_cov_trace_switch(switch_value,
 | |
|                                reinterpret_cast<uint64_t *>(case_values));
 | |
|   env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerSwitchCaseCallbackWithPc(JNIEnv &env, jclass cls,
 | |
|                                                jlong switch_value,
 | |
|                                                jlongArray libfuzzer_case_values,
 | |
|                                                jint id) {
 | |
|   jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
|   __sanitizer_cov_trace_switch_with_pc(
 | |
|       idToPc(id), switch_value, reinterpret_cast<uint64_t *>(case_values));
 | |
|   env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT);
 | |
|   if (env.ExceptionCheck()) env.ExceptionDescribe();
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerLongDivCallback(JNIEnv &env, jclass cls, jlong value,
 | |
|                                       jint id) {
 | |
|   __sanitizer_cov_trace_div8(value);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerLongDivCallbackWithPc(JNIEnv &env, jclass cls,
 | |
|                                             jlong value, jint id) {
 | |
|   __sanitizer_cov_trace_div8_with_pc(idToPc(id), value);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerIntDivCallback(JNIEnv &env, jclass cls, jint value,
 | |
|                                      jint id) {
 | |
|   __sanitizer_cov_trace_div4(value);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerIntDivCallbackWithPc(JNIEnv &env, jclass cls, jint value,
 | |
|                                            jint id) {
 | |
|   __sanitizer_cov_trace_div4_with_pc(idToPc(id), value);
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerGepCallback(JNIEnv &env, jclass cls, jlong idx, jint id) {
 | |
|   __sanitizer_cov_trace_gep(static_cast<uintptr_t>(idx));
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerGepCallbackWithPc(JNIEnv &env, jclass cls, jlong idx,
 | |
|                                         jint id) {
 | |
|   __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast<uintptr_t>(idx));
 | |
| }
 | |
| 
 | |
| void JNICALL libfuzzerPcIndirCallback(JNIEnv &env, jclass cls, jint caller_id,
 | |
|                                       jint callee_id) {
 | |
|   __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id),
 | |
|                                          static_cast<uintptr_t>(callee_id));
 | |
| }
 | |
| 
 | |
| bool is_using_native_libraries = false;
 | |
| std::once_flag ignore_list_flag;
 | |
| std::vector<std::pair<uintptr_t, uintptr_t>> ignore_for_interception_ranges;
 | |
| 
 | |
| extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc(
 | |
|     void *caller_pc) {
 | |
|   // If the fuzz target is not using native libraries, calls to strcmp, memcmp,
 | |
|   // etc. should never be intercepted. The values reported if they were at best
 | |
|   // duplicate the values received from our bytecode instrumentation and at
 | |
|   // worst pollute the table of recent compares with string internal to the JDK.
 | |
|   if (!is_using_native_libraries) return false;
 | |
|   // If the fuzz target is using native libraries, intercept calls only if they
 | |
|   // don't originate from those address ranges that are known to belong to the
 | |
|   // JDK.
 | |
|   return std::none_of(ignore_for_interception_ranges.cbegin(),
 | |
|                       ignore_for_interception_ranges.cend(),
 | |
|                       [caller_pc](const auto &range) {
 | |
|                         uintptr_t start;
 | |
|                         uintptr_t end;
 | |
|                         std::tie(start, end) = range;
 | |
|                         auto address = reinterpret_cast<uintptr_t>(caller_pc);
 | |
|                         return start <= address && address <= end;
 | |
|                       });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Adds the address ranges of executable segmentes of the library lib_name to
 | |
|  * the ignorelist for C standard library function interception (strcmp, memcmp,
 | |
|  * ...).
 | |
|  */
 | |
| void ignoreLibraryForInterception(const std::string &lib_name) {
 | |
|   const auto num_address_ranges = ignore_for_interception_ranges.size();
 | |
|   std::ifstream loaded_libs("/proc/self/maps");
 | |
|   if (!loaded_libs) {
 | |
|     // This early exit is taken e.g. on macOS, where /proc does not exist.
 | |
|     return;
 | |
|   }
 | |
|   std::string line;
 | |
|   while (std::getline(loaded_libs, line)) {
 | |
|     if (!absl::StrContains(line, lib_name)) continue;
 | |
|     // clang-format off
 | |
|     // A typical line looks as follows:
 | |
|     // 7f15356c9000-7f1536367000 r-xp 0020d000 fd:01 19275673         /usr/lib/jvm/java-15-openjdk-amd64/lib/server/libjvm.so
 | |
|     // clang-format on
 | |
|     std::vector<std::string_view> parts =
 | |
|         absl::StrSplit(line, ' ', absl::SkipEmpty());
 | |
|     if (parts.size() != 6) {
 | |
|       std::cout << "ERROR: Invalid format for /proc/self/maps\n"
 | |
|                 << line << std::endl;
 | |
|       exit(1);
 | |
|     }
 | |
|     // Skip non-executable address rang"s.
 | |
|     if (!absl::StrContains(parts[1], "x")) continue;
 | |
|     std::string_view range_str = parts[0];
 | |
|     std::vector<std::string> range = absl::StrSplit(range_str, "-");
 | |
|     if (range.size() != 2) {
 | |
|       std::cout
 | |
|           << "ERROR: Unexpected address range format in /proc/self/maps line: "
 | |
|           << range_str << std::endl;
 | |
|       exit(1);
 | |
|     }
 | |
|     std::size_t pos;
 | |
|     auto start = std::stoull(range[0], &pos, 16);
 | |
|     if (pos != range[0].size()) {
 | |
|       std::cout
 | |
|           << "ERROR: Unexpected address range format in /proc/self/maps line: "
 | |
|           << range_str << std::endl;
 | |
|       exit(1);
 | |
|     }
 | |
|     auto end = std::stoull(range[1], &pos, 16);
 | |
|     if (pos != range[0].size()) {
 | |
|       std::cout
 | |
|           << "ERROR: Unexpected address range format in /proc/self/maps line: "
 | |
|           << range_str << std::endl;
 | |
|       exit(1);
 | |
|     }
 | |
|     ignore_for_interception_ranges.emplace_back(start, end);
 | |
|   }
 | |
|   const auto num_code_segments =
 | |
|       ignore_for_interception_ranges.size() - num_address_ranges;
 | |
|   LOG(INFO) << "added " << num_code_segments
 | |
|             << " code segment of native library " << lib_name
 | |
|             << " to interceptor ignorelist";
 | |
| }
 | |
| 
 | |
| const std::vector<std::string> kLibrariesToIgnoreForInterception = {
 | |
|     // The driver executable itself can be treated just like a library.
 | |
|     "jazzer_driver", "libinstrument.so", "libjava.so",
 | |
|     "libjimage.so",  "libjli.so",        "libjvm.so",
 | |
|     "libnet.so",     "libverify.so",     "libzip.so",
 | |
| };
 | |
| 
 | |
| void JNICALL handleLibraryLoad(JNIEnv &env, jclass cls) {
 | |
|   std::call_once(ignore_list_flag, [] {
 | |
|     LOG(INFO)
 | |
|         << "detected a native library load, enabling interception for libc "
 | |
|            "functions";
 | |
|     for (const auto &lib_name : kLibrariesToIgnoreForInterception)
 | |
|       ignoreLibraryForInterception(lib_name);
 | |
|     // Enable the ignore list after it has been populated since vector is not
 | |
|     // thread-safe with respect to concurrent writes and reads.
 | |
|     is_using_native_libraries = true;
 | |
|   });
 | |
| }
 | |
| 
 | |
| void registerCallback(JNIEnv &env, const char *java_hooks_class_name,
 | |
|                       const JNINativeMethod *methods, int num_methods) {
 | |
|   auto java_hooks_class = env.FindClass(java_hooks_class_name);
 | |
|   if (java_hooks_class == nullptr) {
 | |
|     env.ExceptionDescribe();
 | |
|     throw std::runtime_error(
 | |
|         absl::StrFormat("could not find class %s", java_hooks_class_name));
 | |
|   }
 | |
|   LOG(INFO) << "registering hooks for class " << java_hooks_class_name;
 | |
|   env.RegisterNatives(java_hooks_class, methods, num_methods);
 | |
|   if (env.ExceptionCheck()) {
 | |
|     env.ExceptionDescribe();
 | |
|     throw std::runtime_error("could not register native callbacks");
 | |
|   }
 | |
| }
 | |
| }  // namespace
 | |
| 
 | |
| namespace jazzer {
 | |
| 
 | |
| bool registerFuzzerCallbacks(JNIEnv &env) {
 | |
|   if (FLAGS_fake_pcs) {
 | |
|     LOG(INFO) << "using callback variants with fake pcs";
 | |
|     CalibrateTrampoline();
 | |
|   }
 | |
|   {
 | |
|     JNINativeMethod string_methods[]{
 | |
|         {(char *)"traceMemcmp", (char *)"([B[BII)V",
 | |
|          (void *)&libfuzzerByteCompareCallback},
 | |
|         {(char *)"traceStrcmp",
 | |
|          (char *)"(Ljava/lang/String;Ljava/lang/String;II)V",
 | |
|          (void *)&libfuzzerStringCompareCallback},
 | |
|         {(char *)"traceStrstr",
 | |
|          (char *)"(Ljava/lang/String;Ljava/lang/String;I)V",
 | |
|          (void *)&libfuzzerStringContainCallback}};
 | |
| 
 | |
|     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, string_methods,
 | |
|                      sizeof(string_methods) / sizeof(string_methods[0]));
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     JNINativeMethod cmp_methods[]{
 | |
|         {(char *)"traceCmpLong", (char *)"(JJI)V",
 | |
|          (void *)(FLAGS_fake_pcs ? &libfuzzerLongCompareCallbackWithPc
 | |
|                                  : &libfuzzerLongCompareCallback)},
 | |
|         {(char *)"traceCmpInt", (char *)"(III)V",
 | |
|          (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc
 | |
|                                  : &libfuzzerIntCompareCallback)},
 | |
|         // libFuzzer internally treats const comparisons the same as
 | |
|         // non-constant cmps.
 | |
|         {(char *)"traceConstCmpInt", (char *)"(III)V",
 | |
|          (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc
 | |
|                                  : &libfuzzerIntCompareCallback)},
 | |
|         {(char *)"traceSwitch", (char *)"(J[JI)V",
 | |
|          (void *)(FLAGS_fake_pcs ? &libfuzzerSwitchCaseCallbackWithPc
 | |
|                                  : &libfuzzerSwitchCaseCallback)}};
 | |
| 
 | |
|     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, cmp_methods,
 | |
|                      sizeof(cmp_methods) / sizeof(cmp_methods[0]));
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     JNINativeMethod div_methods[]{
 | |
|         {(char *)"traceDivLong", (char *)"(JI)V",
 | |
|          (void *)(FLAGS_fake_pcs ? &libfuzzerLongDivCallbackWithPc
 | |
|                                  : &libfuzzerLongDivCallback)},
 | |
|         {(char *)"traceDivInt", (char *)"(II)V",
 | |
|          (void *)(FLAGS_fake_pcs ? &libfuzzerIntDivCallbackWithPc
 | |
|                                  : &libfuzzerIntDivCallback)}};
 | |
| 
 | |
|     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, div_methods,
 | |
|                      sizeof(div_methods) / sizeof(div_methods[0]));
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     JNINativeMethod gep_methods[]{
 | |
|         {(char *)"traceGep", (char *)"(JI)V",
 | |
|          (void *)(FLAGS_fake_pcs ? &libfuzzerGepCallbackWithPc
 | |
|                                  : &libfuzzerGepCallback)}};
 | |
| 
 | |
|     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, gep_methods,
 | |
|                      sizeof(gep_methods) / sizeof(gep_methods[0]));
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     JNINativeMethod indir_methods[]{{(char *)"tracePcIndir", (char *)"(II)V",
 | |
|                                      (void *)(&libfuzzerPcIndirCallback)}};
 | |
| 
 | |
|     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, indir_methods,
 | |
|                      sizeof(indir_methods) / sizeof(indir_methods[0]));
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     JNINativeMethod native_methods[]{{(char *)"handleLibraryLoad",
 | |
|                                       (char *)"()V",
 | |
|                                       (void *)(&handleLibraryLoad)}};
 | |
| 
 | |
|     registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, native_methods,
 | |
|                      sizeof(native_methods) / sizeof(native_methods[0]));
 | |
|   }
 | |
| 
 | |
|   return env.ExceptionCheck();
 | |
| }
 | |
| 
 | |
| }  // namespace jazzer
 |