diff options
author | Fabian Meumertzheim <meumertzheim@code-intelligence.com> | 2021-03-22 14:48:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-22 14:48:58 +0100 |
commit | 71ac55c6fc9d808bcc8a8e8d895f7f20141bec86 (patch) | |
tree | dfa557a023d1413799c24dbd1373d8c42c2ee8bb | |
parent | 20d72b43a58f5ffcb807245a854d7eb178c4b8b6 (diff) | |
download | jazzer-api-71ac55c6fc9d808bcc8a8e8d895f7f20141bec86.tar.gz |
Do not intercept JVM-internal C stdlib calls (#45)
* Replace uses of quick_exit and at_quick_exit
quick_exit is not supported on macOS, but can easily replaced by a call
to _Exit after running our cleanup manually.
* Run buildifier --lint=fix -r .
* Build libFuzzer from source
Building libFuzzer from source is easy and has multiple advantages:
* The clang distributed with XCode on macOS does not include libFuzzer.
* Applying a small patch to libFuzzer will allow us to replace the
--wrap linker feature, which is not supported on platforms other than
Linux.
* Replace -Wl,--wrap with a source code patch
* Pin non-native rules_python
* Print exit code on test failure
* Do not intercept JVM-internal C stdlib calls
The JVM frequently calls strcmp/memcmp/..., which fills up the table of
recent compares with entries that are either duplicates of values
already reported by the bytecode instrumentation or JDK-internal strings
that are not relevant for fuzzing.
This commit adds an ignorelist to the C stdlib interceptors that filters
out calls from known JVM libraries. If the fuzz target has not yet
loaded a native library, all such callbacks are ignored, which greatly
improves fuzzer performance for string-heavy targets. E.g.,
JsonSanitizerDenylistFuzzer takes < 1 million runs now when it used to
take over 3 million.
22 files changed, 376 insertions, 52 deletions
diff --git a/BUILD.bazel b/BUILD.bazel index 8c30be59..3f81fdd2 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -2,7 +2,6 @@ load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "define_kt_toolchain") load("@io_bazel_rules_kotlin//kotlin/internal:opts.bzl", "kt_javac_options", "kt_kotlinc_options") load("@rules_pkg//:pkg.bzl", "pkg_tar") -load("@rules_jvm_external//:defs.bzl", "java_export") kt_kotlinc_options( name = "kotlinc_options", diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index c6a0f0cb..d54cdba4 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -14,8 +14,14 @@ http_archive( ], ) -# bazelbuild/bazel-skylib +# bazelbuild/rules_python +http_archive( + name = "rules_python", + sha256 = "b6d46438523a3ec0f3cead544190ee13223a52f6a6765a29eae7b7cc24cc83a0", + url = "https://github.com/bazelbuild/rules_python/releases/download/0.1.0/rules_python-0.1.0.tar.gz", +) +# bazelbuild/bazel-skylib http_archive( name = "bazel_skylib", sha256 = "ebdf850bfef28d923a2cc67ddca86355a449b5e4f38b0a70e584dc24e5984aa6", @@ -78,7 +84,6 @@ http_archive( urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/%s/rules_kotlin_release.tgz" % rules_kotlin_version], ) -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains") load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories") kotlin_repositories() @@ -144,8 +149,6 @@ rules_pkg_version = "0.3.0" rules_pkg_sha = "6b5969a7acd7b60c02f816773b06fcf32fbe8ba0c7919ccdc2df4f8fb923804a" -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - http_archive( name = "rules_pkg", sha256 = rules_pkg_sha, @@ -160,7 +163,6 @@ load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") rules_pkg_dependencies() # bazelbuild/rules_foreign_cc - rules_foreign_cc_commit = "da99da47a0befc3dfbf65739190cd374f836f21d" http_archive( @@ -175,7 +177,6 @@ load("@rules_foreign_cc//:workspace_definitions.bzl", "rules_foreign_cc_dependen rules_foreign_cc_dependencies() # libjpeg_turbo - http_archive( name = "libjpeg_turbo", build_file = "//third_party:libjpeg_turbo.BUILD", @@ -185,7 +186,6 @@ http_archive( ) # JaCoCo - jacoco_commit = "178d49870056b8a1f8ea6915e804d28b0dda5609" jacoco_sha = "da48fb5ae4ec3ffc659d4de18232aedea99476935f4ce4b0605f2d6aa1dc2553" @@ -200,3 +200,16 @@ http_archive( strip_prefix = "jacoco-%s" % jacoco_commit, url = "https://github.com/jacoco/jacoco/archive/178d49870056b8a1f8ea6915e804d28b0dda5609.tar.gz", ) + +# libFuzzer +http_archive( + name = "libFuzzer", + build_file = "//third_party:libFuzzer.BUILD", + patches = [ + "//third_party:libFuzzer-make-interceptors-configurable.patch", + "//third_party:libFuzzer-pass-death-callback-to-jazzer.patch", + ], + sha256 = "a78949f86fc9852f51b11ceb3e6c2c61bb6e4ebb073198cebddc82451f708adf", + strip_prefix = "llvm-project-llvmorg-12.0.0-rc3", + url = "https://github.com/llvm/llvm-project/archive/llvmorg-12.0.0-rc3.tar.gz", +) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 43e8a488..47ebab3c 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -69,6 +69,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { "div" -> setOf(InstrumentationType.DIV) "gep" -> setOf(InstrumentationType.GEP) "indir" -> setOf(InstrumentationType.INDIR) + "native" -> setOf(InstrumentationType.NATIVE) "all" -> InstrumentationType.values().toSet() else -> { println("WARN: Skipping unknown instrumentation type $it") diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index 0e304f45..35ee3959 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -18,6 +18,7 @@ import com.code_intelligence.jazzer.instrumentor.ClassInstrumentor import com.code_intelligence.jazzer.instrumentor.Hook import com.code_intelligence.jazzer.instrumentor.InstrumentationType import com.code_intelligence.jazzer.instrumentor.loadHooks +import com.code_intelligence.jazzer.runtime.NativeLibHooks import com.code_intelligence.jazzer.runtime.TraceCmpHooks import com.code_intelligence.jazzer.runtime.TraceDivHooks import com.code_intelligence.jazzer.runtime.TraceIndirHooks @@ -81,6 +82,7 @@ internal class RuntimeInstrumentor( InstrumentationType.CMP -> TraceCmpHooks::class.java InstrumentationType.DIV -> TraceDivHooks::class.java InstrumentationType.INDIR -> TraceIndirHooks::class.java + InstrumentationType.NATIVE -> NativeLibHooks::class.java else -> null } } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel index d68ec102..ee16b40c 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") java_binary( diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt index 50904e61..78793842 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt @@ -23,6 +23,7 @@ enum class InstrumentationType { DIV, GEP, INDIR, + NATIVE, } internal interface Instrumentor { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java new file mode 100644 index 00000000..495cad7c --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java @@ -0,0 +1,35 @@ +// 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. + +package com.code_intelligence.jazzer.runtime; + +import com.code_intelligence.jazzer.api.HookType; +import com.code_intelligence.jazzer.api.MethodHook; +import java.lang.invoke.MethodHandle; + +@SuppressWarnings("unused") +final public class NativeLibHooks { + @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", + targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V") + @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", + targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V") + @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "load", + targetMethodDescriptor = "(Ljava/lang/String;)V") + @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "load", + targetMethodDescriptor = "(Ljava/lang/String;)V") + public static void + loadLibraryHook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + TraceDataFlowNativeCallbacks.handleLibraryLoad(); + } +} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index 147386ae..f779cec6 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -73,4 +73,6 @@ final public class TraceDataFlowNativeCallbacks { // as the stack layout required for the call can't be achieved without local variables. return Long.compare(arg1, arg2); } + + public static native void handleLibraryLoad(); } diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index 04cf32e3..9f2fe2d5 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_java//java:defs.bzl", "java_binary") + def java_fuzz_target_test( name, target_class, @@ -29,14 +31,12 @@ def java_fuzz_target_test( "Jazzer-Fuzz-Target-Class: %s" % target_class, ] if hook_classes: - deploy_manifest_lines += [ - "Jazzer-Hook-Classes: %s" % ":".join(hook_classes), - ] + deploy_manifest_lines.append("Jazzer-Hook-Classes: %s" % ":".join(hook_classes)) # Deps can only be specified on java_binary targets with sources, which # excludes e.g. Kotlin libraries wrapped into java_binary via runtime_deps. target_deps = deps + ["//agent/src/main/java/com/code_intelligence/jazzer/api"] if srcs else [] - native.java_binary( + java_binary( name = target_name, srcs = srcs, visibility = ["//visibility:private"], diff --git a/bazel/fuzz_target_test_wrapper.sh b/bazel/fuzz_target_test_wrapper.sh index 7a2a9f7f..061ed3ae 100755 --- a/bazel/fuzz_target_test_wrapper.sh +++ b/bazel/fuzz_target_test_wrapper.sh @@ -24,5 +24,6 @@ if [ $exit_code -eq 77 ] || [ $exit_code -eq 76 ] then exit 0 else + echo "Unexpected exit code: $exit_code" exit 1 fi diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 2c17b947..599fcac0 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -1,9 +1,4 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") -load( - "@bazel_tools//tools/jdk:default_java_toolchain.bzl", - "java_runtime_files", -) -load("@bazel_skylib//rules:common_settings.bzl", "string_flag") cc_library( name = "sanitizer_hooks_with_pc", @@ -47,6 +42,9 @@ cc_library( "signal_handler.h", "utils.h", ], + linkopts = [ + "-ldl", + ], visibility = ["//visibility:public"], deps = [ ":sanitizer_hooks_with_pc", @@ -64,13 +62,10 @@ cc_binary( data = [ "//agent:jazzer_agent_deploy.jar", ], - linkopts = [ - "-Wl,--wrap=__sanitizer_set_death_callback", - "-fsanitize=fuzzer", - ], visibility = ["//visibility:public"], deps = [ ":jvm_tooling_lib", + "@libFuzzer", ], ) @@ -81,12 +76,12 @@ cc_binary( "//agent:jazzer_agent_deploy.jar", ], linkopts = [ - "-Wl,--wrap=__sanitizer_set_death_callback", - "-fsanitize=fuzzer,address", + "-fsanitize=address", ], visibility = ["//visibility:public"], deps = [ ":jvm_tooling_lib", + "@libFuzzer", ], ) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index ae3602e0..0d83264d 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -166,7 +166,7 @@ FuzzTargetRunner::FuzzTargetRunner( LOG(ERROR) << "Invalid dedup token (expected up to 16 hex digits): '" << str_token << "'"; // Don't let libFuzzer print a crash stack trace. - std::quick_exit(1); + _Exit(1); } } } diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp index d1c754af..398d69d8 100644 --- a/driver/libfuzzer_callbacks.cpp +++ b/driver/libfuzzer_callbacks.cpp @@ -14,10 +14,14 @@ #include "libfuzzer_callbacks.h" -#include <algorithm> +#include <fstream> #include <iostream> +#include <utility> +#include <vector> +#include "absl/strings/match.h" #include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" #include "glog/logging.h" #include "sanitizer_hooks_with_pc.h" #include "third_party/jni/jni.h" @@ -172,6 +176,106 @@ void JNICALL libfuzzerPcIndirCallback(JNIEnv &env, jclass cls, jint caller_id, static_cast<uintptr_t>(callee_id)); } +bool is_using_native_libraries = false; +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. + bool should_intercept = 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; + }); + if (should_intercept) { + std::cout << " PC: " << caller_pc << std::endl; + } + return should_intercept; +} + +/** + * 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"); + 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 ranges. + 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) { + if (is_using_native_libraries) return; + LOG(INFO) << "detected a native library load, enabling interception for libc " + "functions"; + is_using_native_libraries = true; + for (const auto &lib_name : kLibrariesToIgnoreForInterception) + ignoreLibraryForInterception(lib_name); +} + 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); @@ -263,6 +367,15 @@ bool registerFuzzerCallbacks(JNIEnv &env) { 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(); } diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp index 2accc40d..4d51104e 100644 --- a/driver/libfuzzer_driver.cpp +++ b/driver/libfuzzer_driver.cpp @@ -14,6 +14,8 @@ #include "libfuzzer_driver.h" +#include <dlfcn.h> + #include <algorithm> #include <filesystem> #include <fstream> @@ -48,13 +50,16 @@ DECLARE_bool(fake_pcs); // Defined in jvm_tooling.cpp DECLARE_string(id_sync_file); -extern "C" void __real___sanitizer_set_death_callback(void (*callback)()); - -// We use the linker opt -Wl,--wrap=__sanitizer_set_death_callback to wrap the -// symbol defined by sanitizers_common to receive libFuzzer's death callback. -extern "C" void __wrap___sanitizer_set_death_callback(void (*callback)()) { +// We apply a patch to libFuzzer to make it call this function instead of +// __sanitizer_set_death_callback to pass us the death callback. +extern "C" [[maybe_unused]] void __jazzer_set_death_callback( + void (*callback)()) { jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_ = callback; - __real___sanitizer_set_death_callback(callback); + void *sanitizer_set_death_callback = + dlsym(RTLD_NEXT, "__sanitizer_set_death_callback"); + if (sanitizer_set_death_callback != nullptr) + reinterpret_cast<void (*)(void (*)())>(sanitizer_set_death_callback)( + callback); } namespace { @@ -148,7 +153,6 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( } }; std::atexit(cleanup_fn); - std::at_quick_exit(cleanup_fn); } initJvm(*argv_start); diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp index 7f921321..8bbed9a1 100644 --- a/driver/libfuzzer_fuzz_target.cpp +++ b/driver/libfuzzer_fuzz_target.cpp @@ -50,10 +50,7 @@ extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { << std::endl; } gLibfuzzerDriver = std::make_unique<Driver>(argc, argv); - // Run even if we use std::quick_exit to prevent libFuzzer stack trace - // printing. std::atexit(&driver_cleanup); - std::at_quick_exit(&driver_cleanup); return 0; } @@ -62,23 +59,19 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) { auto result = gLibfuzzerDriver->TestOneInput(data, size); if (result != jazzer::RunResult::kOk) { // Fuzzer triggered an exception or assertion in Java code. Skip the - // uninformative libFuzzer stack trace if possible. - if (Driver::libfuzzer_print_crashing_input_ != nullptr) { - std::cerr << "== libFuzzer crashing input ==\n"; - Driver::libfuzzer_print_crashing_input_(); - // DumpReproducer needs to be called after libFuzzer printed its final - // stats as otherwise it would report incorrect coverage. - gLibfuzzerDriver->DumpReproducer(data, size); - if (result == jazzer::RunResult::kDumpAndContinue) { - // Continue fuzzing after printing the crashing input. - return 0; - } - // Exit directly without invoking libFuzzer's atexit hook. - std::quick_exit(Driver::kErrorExitCode); - } else { - // libFuzzer failed to register its death callback, exit normally. - std::exit(1); + // uninformative libFuzzer stack trace. + std::cerr << "== libFuzzer crashing input ==\n"; + Driver::libfuzzer_print_crashing_input_(); + // DumpReproducer needs to be called after libFuzzer printed its final + // stats as otherwise it would report incorrect coverage. + gLibfuzzerDriver->DumpReproducer(data, size); + if (result == jazzer::RunResult::kDumpAndContinue) { + // Continue fuzzing after printing the crashing input. + return 0; } + // Exit directly without invoking libFuzzer's atexit hook. + driver_cleanup(); + _Exit(Driver::kErrorExitCode); } return 0; } diff --git a/driver/sanitizer_symbols_for_tests.cpp b/driver/sanitizer_symbols_for_tests.cpp index fb2f6d08..b671ff66 100644 --- a/driver/sanitizer_symbols_for_tests.cpp +++ b/driver/sanitizer_symbols_for_tests.cpp @@ -34,5 +34,5 @@ void __sanitizer_cov_trace_div4(uint32_t val) {} void __sanitizer_cov_trace_div8(uint64_t val) {} void __sanitizer_cov_trace_gep(uintptr_t idx) {} void __sanitizer_cov_trace_pc_indir(uintptr_t callee) {} -void __real___sanitizer_set_death_callback(void (*callback)()) {} +void __sanitizer_set_death_callback(void (*callback)()) {} } diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 9b5951a0..4bc7deb8 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_binary") load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel index fd65eb9b..b391ce54 100644 --- a/third_party/BUILD.bazel +++ b/third_party/BUILD.bazel @@ -2,5 +2,8 @@ exports_files([ "gflags-use-double-dash-args.patch", "jacoco-make-probe-inserter-subclassable.patch", "jacoco_internal.BUILD", + "libFuzzer-make-interceptors-configurable.patch", + "libFuzzer-pass-death-callback-to-jazzer.patch", + "libFuzzer.BUILD", "libjpeg_turbo.BUILD", ]) diff --git a/third_party/jni/BUILD.bazel b/third_party/jni/BUILD.bazel index 4d5fdd00..cda76ef0 100644 --- a/third_party/jni/BUILD.bazel +++ b/third_party/jni/BUILD.bazel @@ -1,3 +1,5 @@ +load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") + cc_library( name = "jni", visibility = ["//visibility:public"], diff --git a/third_party/libFuzzer-make-interceptors-configurable.patch b/third_party/libFuzzer-make-interceptors-configurable.patch new file mode 100644 index 00000000..9420c4aa --- /dev/null +++ b/third_party/libFuzzer-make-interceptors-configurable.patch @@ -0,0 +1,109 @@ +diff --git compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp +index b87798603fda..10e34ee86cce 100644 +--- compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp ++++ compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp +@@ -147,11 +147,18 @@ DEFINE_REAL(char *, strstr, const char *, const char *) + DEFINE_REAL(char *, strcasestr, const char *, const char *) + DEFINE_REAL(void *, memmem, const void *, size_t, const void *, size_t) + ++extern "C" __attribute__((weak)) bool ++__sanitizer_weak_is_relevant_pc(void * caller_pc) { ++ return false; ++} ++ + ATTRIBUTE_INTERFACE int bcmp(const char *s1, const char *s2, size_t n) { + if (!FuzzerInited) + return internal_memcmp(s1, s2, n); + int result = REAL(bcmp)(s1, s2, n); +- __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_memcmp(caller_pc, s1, s2, n, result); + return result; + } + +@@ -159,7 +166,9 @@ ATTRIBUTE_INTERFACE int memcmp(const void *s1, const void *s2, size_t n) { + if (!FuzzerInited) + return internal_memcmp(s1, s2, n); + int result = REAL(memcmp)(s1, s2, n); +- __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_memcmp(caller_pc, s1, s2, n, result); + return result; + } + +@@ -167,7 +176,9 @@ ATTRIBUTE_INTERFACE int strncmp(const char *s1, const char *s2, size_t n) { + if (!FuzzerInited) + return internal_strncmp(s1, s2, n); + int result = REAL(strncmp)(s1, s2, n); +- __sanitizer_weak_hook_strncmp(GET_CALLER_PC(), s1, s2, n, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_strncmp(caller_pc, s1, s2, n, result); + return result; + } + +@@ -175,21 +186,27 @@ ATTRIBUTE_INTERFACE int strcmp(const char *s1, const char *s2) { + if (!FuzzerInited) + return internal_strcmp(s1, s2); + int result = REAL(strcmp)(s1, s2); +- __sanitizer_weak_hook_strcmp(GET_CALLER_PC(), s1, s2, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_strcmp(caller_pc, s1, s2, result); + return result; + } + + ATTRIBUTE_INTERFACE int strncasecmp(const char *s1, const char *s2, size_t n) { + ensureFuzzerInited(); + int result = REAL(strncasecmp)(s1, s2, n); +- __sanitizer_weak_hook_strncasecmp(GET_CALLER_PC(), s1, s2, n, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_strncasecmp(caller_pc, s1, s2, n, result); + return result; + } + + ATTRIBUTE_INTERFACE int strcasecmp(const char *s1, const char *s2) { + ensureFuzzerInited(); + int result = REAL(strcasecmp)(s1, s2); +- __sanitizer_weak_hook_strcasecmp(GET_CALLER_PC(), s1, s2, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_strcasecmp(caller_pc, s1, s2, result); + return result; + } + +@@ -197,14 +214,18 @@ ATTRIBUTE_INTERFACE char *strstr(const char *s1, const char *s2) { + if (!FuzzerInited) + return internal_strstr(s1, s2); + char *result = REAL(strstr)(s1, s2); +- __sanitizer_weak_hook_strstr(GET_CALLER_PC(), s1, s2, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_strstr(caller_pc, s1, s2, result); + return result; + } + + ATTRIBUTE_INTERFACE char *strcasestr(const char *s1, const char *s2) { + ensureFuzzerInited(); + char *result = REAL(strcasestr)(s1, s2); +- __sanitizer_weak_hook_strcasestr(GET_CALLER_PC(), s1, s2, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_strcasestr(caller_pc, s1, s2, result); + return result; + } + +@@ -212,7 +233,9 @@ ATTRIBUTE_INTERFACE + void *memmem(const void *s1, size_t len1, const void *s2, size_t len2) { + ensureFuzzerInited(); + void *result = REAL(memmem)(s1, len1, s2, len2); +- __sanitizer_weak_hook_memmem(GET_CALLER_PC(), s1, len1, s2, len2, result); ++ void *caller_pc = GET_CALLER_PC(); ++ if (__sanitizer_weak_is_relevant_pc(caller_pc)) ++ __sanitizer_weak_hook_memmem(caller_pc, s1, len1, s2, len2, result); + return result; + } + diff --git a/third_party/libFuzzer-pass-death-callback-to-jazzer.patch b/third_party/libFuzzer-pass-death-callback-to-jazzer.patch new file mode 100644 index 00000000..3fb9fbb0 --- /dev/null +++ b/third_party/libFuzzer-pass-death-callback-to-jazzer.patch @@ -0,0 +1,28 @@ +diff --git compiler-rt/lib/fuzzer/FuzzerExtFunctions.def compiler-rt/lib/fuzzer/FuzzerExtFunctions.def +index 51edf8444e94..e31f0040268b 100644 +--- compiler-rt/lib/fuzzer/FuzzerExtFunctions.def ++++ compiler-rt/lib/fuzzer/FuzzerExtFunctions.def +@@ -42,7 +42,7 @@ EXT_FUNC(__sanitizer_symbolize_pc, void, + EXT_FUNC(__sanitizer_get_module_and_offset_for_pc, int, + (void *pc, char *module_path, + size_t module_path_len,void **pc_offset), false); +-EXT_FUNC(__sanitizer_set_death_callback, void, (void (*)(void)), true); ++EXT_FUNC(__jazzer_set_death_callback, void, (void (*)(void)), true); + EXT_FUNC(__sanitizer_set_report_fd, void, (void*), false); + EXT_FUNC(__msan_scoped_disable_interceptor_checks, void, (), false); + EXT_FUNC(__msan_scoped_enable_interceptor_checks, void, (), false); +diff --git compiler-rt/lib/fuzzer/FuzzerLoop.cpp compiler-rt/lib/fuzzer/FuzzerLoop.cpp +index 149742b4c2fe..7b361423cc32 100644 +--- compiler-rt/lib/fuzzer/FuzzerLoop.cpp ++++ compiler-rt/lib/fuzzer/FuzzerLoop.cpp +@@ -138,8 +138,8 @@ void Fuzzer::HandleMalloc(size_t Size) { + Fuzzer::Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, + FuzzingOptions Options) + : CB(CB), Corpus(Corpus), MD(MD), Options(Options) { +- if (EF->__sanitizer_set_death_callback) +- EF->__sanitizer_set_death_callback(StaticDeathCallback); ++ if (EF->__jazzer_set_death_callback) ++ EF->__jazzer_set_death_callback(StaticDeathCallback); + assert(!F); + F = this; + TPC.ResetMaps(); diff --git a/third_party/libFuzzer.BUILD b/third_party/libFuzzer.BUILD new file mode 100644 index 00000000..4bd464a4 --- /dev/null +++ b/third_party/libFuzzer.BUILD @@ -0,0 +1,21 @@ +# Based on https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/compiler-rt/lib/fuzzer/build.sh +LIB_FUZZER_PATH = "compiler-rt/lib/fuzzer" + +cc_library( + name = "libFuzzer", + srcs = glob([ + LIB_FUZZER_PATH + "/*.cpp", + ]), + hdrs = glob([ + LIB_FUZZER_PATH + "/*.h", + LIB_FUZZER_PATH + "/*.def", + ]), + copts = [ + "-g", + "-O2", + "-fno-omit-frame-pointer", + "-std=c++11", + ], + alwayslink = True, + visibility = ["//visibility:public"], +) |