aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/code_intelligence/jazzer/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/code_intelligence/jazzer/runtime')
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel180
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/Constants.java21
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java169
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java41
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java82
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java50
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/Mutator.java34
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java39
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java598
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java125
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java47
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java35
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/bootstrap_shade_rules4
-rwxr-xr-xsrc/main/java/com/code_intelligence/jazzer/runtime/verify_shading.sh27
14 files changed, 1452 insertions, 0 deletions
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
new file mode 100644
index 00000000..c31c86e4
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
@@ -0,0 +1,180 @@
+load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar")
+load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library")
+load("//bazel:compat.bzl", "SKIP_ON_WINDOWS")
+load("//bazel:jar.bzl", "strip_jar")
+
+# The transitive dependencies of this target will be appended to the search path
+# of the bootstrap class loader. They will be visible to all classes - care must
+# be taken to shade everything and generally keep this target as small as
+# possible.
+java_binary(
+ name = "jazzer_bootstrap_unshaded",
+ create_executable = False,
+ runtime_deps = [":jazzer_bootstrap_lib"],
+)
+
+java_library(
+ name = "jazzer_bootstrap_lib",
+ visibility = ["//src/main/java/com/code_intelligence/jazzer:__pkg__"],
+ runtime_deps = [
+ ":runtime",
+ "//sanitizers",
+ ],
+)
+
+# These classes with public Bazel visibility are contained in jazzer_bootstrap.jar
+# and will thus be available on the bootstrap class path. This target can be
+# passed to the `deploy_env` attribute of the Jazzer `java_binary` to ensure that
+# it doesn't bundle in these classes.
+java_binary(
+ name = "jazzer_bootstrap_env",
+ create_executable = False,
+ visibility = ["//src/main/java/com/code_intelligence/jazzer:__pkg__"],
+ runtime_deps = [
+ "//src/main/java/com/code_intelligence/jazzer/api:hooks",
+ "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider",
+ ],
+)
+
+jar_jar(
+ name = "jazzer_bootstrap_unstripped",
+ input_jar = ":jazzer_bootstrap_unshaded_deploy.jar",
+ rules = "bootstrap_shade_rules",
+)
+
+strip_jar(
+ name = "jazzer_bootstrap",
+ out = "jazzer_bootstrap.jar",
+ jar = ":jazzer_bootstrap_unstripped",
+ paths_to_keep = [
+ "com/code_intelligence/jazzer/**",
+ "jaz/**",
+ "META-INF/MANIFEST.MF",
+ ],
+ visibility = [
+ "//src/main/java/com/code_intelligence/jazzer/agent:__pkg__",
+ "//src/main/java/com/code_intelligence/jazzer/android:__pkg__",
+ ],
+)
+
+sh_test(
+ name = "jazzer_bootstrap_shading_test",
+ srcs = ["verify_shading.sh"],
+ args = [
+ "$(rootpath jazzer_bootstrap.jar)",
+ ],
+ data = [
+ "jazzer_bootstrap.jar",
+ "@local_jdk//:bin/jar",
+ ],
+ tags = [
+ # Coverage instrumentation necessarily adds files to the jar that we
+ # wouldn't want to release and thus causes this test to fail.
+ "no-coverage",
+ ],
+ target_compatible_with = SKIP_ON_WINDOWS,
+)
+
+# At runtime, the AgentInstaller appends jazzer_bootstrap.jar to the bootstrap
+# class loader's search path - these classes must not be available on the
+# regular classpath. Since dependents should not have to resort to reflection to
+# access these classes they know will be there at runtime, this compile-time
+# only dependency can be used as a replacement.
+java_library(
+ name = "jazzer_bootstrap_compile_only",
+ neverlink = True,
+ visibility = [
+ "//src/main/java/com/code_intelligence/jazzer/autofuzz:__pkg__",
+ "//src/main/java/com/code_intelligence/jazzer/driver:__pkg__",
+ "//src/main/java/com/code_intelligence/jazzer/instrumentor:__pkg__",
+ ],
+ exports = [
+ ":fuzz_target_runner_natives",
+ ":runtime",
+ ],
+)
+
+# The following targets must only be referenced directly by tests or native implementations.
+
+java_jni_library(
+ name = "coverage_map",
+ srcs = ["CoverageMap.java"],
+ native_libs = select({
+ "@platforms//os:android": ["//src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver"],
+ "//conditions:default": [],
+ }),
+ visibility = [
+ "//src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__",
+ "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__",
+ "//src/test:__subpackages__",
+ ],
+ deps = [
+ "//src/main/java/com/code_intelligence/jazzer/runtime:constants",
+ "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider",
+ ],
+)
+
+java_jni_library(
+ name = "trace_data_flow_native_callbacks",
+ srcs = ["TraceDataFlowNativeCallbacks.java"],
+ visibility = [
+ "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__",
+ ],
+ deps = ["@org_ow2_asm_asm//jar"],
+)
+
+java_jni_library(
+ name = "fuzz_target_runner_natives",
+ srcs = ["FuzzTargetRunnerNatives.java"],
+ visibility = ["//src/main/native/com/code_intelligence/jazzer/driver:__pkg__"],
+ deps = [
+ ":constants",
+ ],
+)
+
+java_jni_library(
+ name = "mutator",
+ srcs = ["Mutator.java"],
+ visibility = [
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer:__pkg__",
+ "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__",
+ ],
+)
+
+java_library(
+ name = "runtime",
+ srcs = [
+ "HardToCatchError.java",
+ "JazzerInternal.java",
+ "NativeLibHooks.java",
+ "TraceCmpHooks.java",
+ "TraceDivHooks.java",
+ "TraceIndirHooks.java",
+ ],
+ visibility = [
+ "//src/main/java/com/code_intelligence/jazzer/android:__pkg__",
+ "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__",
+ "//src/test:__subpackages__",
+ ],
+ runtime_deps = [
+ ":fuzz_target_runner_natives",
+ ":mutator",
+ # Access to Unsafe is possible without any tricks if the class that does it is loaded by the
+ # bootstrap loader. We thus want Jazzer to use this class from jazzer_bootstrap.
+ "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider",
+ ],
+ deps = [
+ ":constants",
+ ":coverage_map",
+ ":trace_data_flow_native_callbacks",
+ "//src/main/java/com/code_intelligence/jazzer/api:hooks",
+ ],
+)
+
+# This target exposes a class that can safely be loaded in both the system and the bootstrap class
+# loader as it provides true constants that do not change over the lifetime of the JVM.
+java_library(
+ name = "constants",
+ srcs = ["Constants.java"],
+ visibility = ["//visibility:public"],
+)
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/Constants.java b/src/main/java/com/code_intelligence/jazzer/runtime/Constants.java
new file mode 100644
index 00000000..92f4a3ca
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/Constants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 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;
+
+public final class Constants {
+ public static final boolean IS_ANDROID = System.getProperty("java.vm.vendor").contains("Android");
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java
new file mode 100644
index 00000000..a945a30a
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java
@@ -0,0 +1,169 @@
+// 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 static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID;
+
+import com.code_intelligence.jazzer.utils.UnsafeProvider;
+import com.github.fmeum.rules_jni.RulesJni;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import sun.misc.Unsafe;
+
+/**
+ * Represents the Java view on a libFuzzer 8 bit counter coverage map. By using a direct ByteBuffer,
+ * the counters are shared directly with native code.
+ */
+final public class CoverageMap {
+ static {
+ RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver");
+ }
+
+ private static final String ENV_MAX_NUM_COUNTERS = "JAZZER_MAX_NUM_COUNTERS";
+
+ private static final int MAX_NUM_COUNTERS = System.getenv(ENV_MAX_NUM_COUNTERS) != null
+ ? Integer.parseInt(System.getenv(ENV_MAX_NUM_COUNTERS))
+ : 1 << 20;
+
+ private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe();
+ private static final Class<?> LOG;
+ private static final MethodHandle LOG_INFO;
+ private static final MethodHandle LOG_ERROR;
+
+ static {
+ try {
+ LOG = Class.forName(
+ "com.code_intelligence.jazzer.utils.Log", false, ClassLoader.getSystemClassLoader());
+ LOG_INFO = MethodHandles.lookup().findStatic(
+ LOG, "info", MethodType.methodType(void.class, String.class));
+ LOG_ERROR = MethodHandles.lookup().findStatic(
+ LOG, "error", MethodType.methodType(void.class, String.class, Throwable.class));
+ } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * The collection of coverage counters directly interacted with by classes that are instrumented
+ * for coverage. The instrumentation assumes that this is always one contiguous block of memory,
+ * so it is allocated once at maximum size. Using a larger number here increases the memory usage
+ * of all fuzz targets, but has otherwise no impact on performance.
+ */
+ public static final long countersAddress = UNSAFE.allocateMemory(MAX_NUM_COUNTERS);
+
+ static {
+ UNSAFE.setMemory(countersAddress, MAX_NUM_COUNTERS, (byte) 0);
+ initialize(countersAddress);
+ }
+
+ private static final int INITIAL_NUM_COUNTERS = 1 << 9;
+
+ static {
+ registerNewCounters(0, INITIAL_NUM_COUNTERS);
+ }
+
+ /**
+ * The number of coverage counters that are currently registered with libFuzzer. This number grows
+ * dynamically as classes are instrumented and should be kept as low as possible as libFuzzer has
+ * to iterate over the whole map for every execution.
+ */
+ private static int currentNumCounters = INITIAL_NUM_COUNTERS;
+
+ // Called via reflection.
+ @SuppressWarnings("unused")
+ public static void enlargeIfNeeded(int nextId) {
+ int newNumCounters = currentNumCounters;
+ while (nextId >= newNumCounters) {
+ newNumCounters = 2 * newNumCounters;
+ if (newNumCounters > MAX_NUM_COUNTERS) {
+ logError(
+ String.format(
+ "Maximum number (%s) of coverage counters exceeded. Try to limit the scope of a single fuzz target as "
+ + "much as possible to keep the fuzzer fast. If that is not possible, the maximum number of "
+ + "counters can be increased via the %s environment variable.",
+ MAX_NUM_COUNTERS, ENV_MAX_NUM_COUNTERS),
+ null);
+ System.exit(1);
+ }
+ }
+ if (newNumCounters > currentNumCounters) {
+ registerNewCounters(currentNumCounters, newNumCounters);
+ currentNumCounters = newNumCounters;
+ logInfo("New number of coverage counters: " + currentNumCounters);
+ }
+ }
+
+ // Called by the coverage instrumentation.
+ @SuppressWarnings("unused")
+ public static void recordCoverage(final int id) {
+ if (IS_ANDROID) {
+ enlargeIfNeeded(id);
+ }
+
+ final long address = countersAddress + id;
+ final byte counter = UNSAFE.getByte(address);
+ UNSAFE.putByte(address, (byte) (counter == -1 ? 1 : counter + 1));
+ }
+
+ public static Set<Integer> getCoveredIds() {
+ Set<Integer> coveredIds = new HashSet<>();
+ for (int id = 0; id < currentNumCounters; id++) {
+ if (UNSAFE.getByte(countersAddress + id) > 0) {
+ coveredIds.add(id);
+ }
+ }
+ return Collections.unmodifiableSet(coveredIds);
+ }
+
+ public static void replayCoveredIds(Set<Integer> coveredIds) {
+ for (int id : coveredIds) {
+ UNSAFE.putByte(countersAddress + id, (byte) 1);
+ }
+ }
+
+ private static void logInfo(String message) {
+ try {
+ LOG_INFO.invokeExact(message);
+ } catch (Throwable error) {
+ // Should not be reached, Log.error does not throw.
+ error.printStackTrace();
+ System.err.println("Failed to call Log.info:");
+ System.err.println(message);
+ }
+ }
+
+ private static void logError(String message, Throwable t) {
+ try {
+ LOG_ERROR.invokeExact(message, t);
+ } catch (Throwable error) {
+ // Should not be reached, Log.error does not throw.
+ error.printStackTrace();
+ System.err.println("Failed to call Log.error:");
+ System.err.println(message);
+ }
+ }
+
+ // Returns the IDs of all blocks that have been covered in at least one run (not just the current
+ // one).
+ public static native int[] getEverCoveredIds();
+
+ private static native void initialize(long countersAddress);
+
+ private static native void registerNewCounters(int oldNumCounters, int newNumCounters);
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java b/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java
new file mode 100644
index 00000000..bbf74fdb
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java
@@ -0,0 +1,41 @@
+// Copyright 2022 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.github.fmeum.rules_jni.RulesJni;
+
+/**
+ * The native functions used by FuzzTargetRunner.
+ *
+ * <p>This class has to be loaded by the bootstrap class loader since the native library it loads
+ * links in libFuzzer and the Java hooks, which have to be on the bootstrap path so that they are
+ * seen by Java standard library classes, need to be able to call native libFuzzer callbacks.
+ */
+public class FuzzTargetRunnerNatives {
+ static {
+ if (!Constants.IS_ANDROID && FuzzTargetRunnerNatives.class.getClassLoader() != null) {
+ throw new IllegalStateException(
+ "FuzzTargetRunnerNatives must be loaded in the bootstrap loader");
+ }
+ RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver");
+ }
+
+ public static native int startLibFuzzer(
+ byte[][] args, Class<?> runner, boolean useExperimentalMutator);
+
+ public static native void printCrashingInput();
+
+ public static native void temporarilyDisableLibfuzzerExitHook();
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java b/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java
new file mode 100644
index 00000000..cf136051
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java
@@ -0,0 +1,82 @@
+// 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 java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * An Error that rethrows itself when any of its getters is invoked.
+ */
+public class HardToCatchError extends Error {
+ public HardToCatchError() {
+ super();
+ }
+
+ @Override
+ public String getMessage() {
+ throw this;
+ }
+
+ @Override
+ public String getLocalizedMessage() {
+ throw this;
+ }
+
+ @Override
+ public synchronized Throwable initCause(Throwable cause) {
+ throw this;
+ }
+
+ @Override
+ public String toString() {
+ throw this;
+ }
+
+ @Override
+ public void printStackTrace() {
+ throw this;
+ }
+
+ @Override
+ public void printStackTrace(PrintStream s) {
+ throw this;
+ }
+
+ @Override
+ public void printStackTrace(PrintWriter s) {
+ throw this;
+ }
+
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ throw this;
+ }
+
+ @Override
+ public int hashCode() {
+ throw this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ throw this;
+ }
+
+ @Override
+ public Object clone() {
+ throw this;
+ }
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java b/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java
new file mode 100644
index 00000000..3b368531
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java
@@ -0,0 +1,50 @@
+// 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 java.util.ArrayList;
+
+final public class JazzerInternal {
+ public static Throwable lastFinding;
+ // The value is only relevant when regression testing. Read by the bytecode emitted by
+ // HookMethodVisitor to enable hooks only when invoked from a @FuzzTest.
+ //
+ // Alternatives considered:
+ // Making this thread local rather than global may potentially allow to run fuzz tests in
+ // parallel with regular unit tests, but it is next to impossible to determine which thread is
+ // currently doing work for a fuzz test versus a regular unit test. Instead, @FuzzTest is
+ // annotated with @Isolated.
+ @SuppressWarnings("unused") public static boolean hooksEnabled = true;
+
+ private static final ArrayList<Runnable> onFuzzTargetReadyCallbacks = new ArrayList<>();
+
+ // Accessed from api.Jazzer via reflection.
+ public static void reportFindingFromHook(Throwable finding) {
+ lastFinding = finding;
+ // Throw an Error that is hard to catch (short of outright ignoring it) in order to quickly
+ // terminate the execution of the fuzz target. The finding will be reported as soon as the fuzz
+ // target returns even if this Error is swallowed.
+ throw new HardToCatchError();
+ }
+
+ public static void registerOnFuzzTargetReadyCallback(Runnable callback) {
+ onFuzzTargetReadyCallbacks.add(callback);
+ }
+
+ public static void onFuzzTargetReady(String fuzzTargetClass) {
+ onFuzzTargetReadyCallbacks.forEach(Runnable::run);
+ onFuzzTargetReadyCallbacks.clear();
+ }
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/Mutator.java b/src/main/java/com/code_intelligence/jazzer/runtime/Mutator.java
new file mode 100644
index 00000000..2d9a7f65
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/Mutator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 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.github.fmeum.rules_jni.RulesJni;
+
+public final class Mutator {
+ public static final boolean SHOULD_MOCK =
+ Boolean.parseBoolean(System.getenv("JAZZER_MOCK_LIBFUZZER_MUTATOR"));
+
+ static {
+ if (!SHOULD_MOCK) {
+ RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver");
+ }
+ }
+
+ public static native int defaultMutateNative(byte[] buffer, int size);
+
+ private Mutator() {}
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java b/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
new file mode 100644
index 00000000..8572f05a
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
@@ -0,0 +1,39 @@
+// 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) {
+ if (Constants.IS_ANDROID) {
+ return;
+ }
+
+ TraceDataFlowNativeCallbacks.handleLibraryLoad();
+ }
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java
new file mode 100644
index 00000000..7fb15866
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java
@@ -0,0 +1,598 @@
+// 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;
+import java.util.*;
+
+@SuppressWarnings("unused")
+final public class TraceCmpHooks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte", targetMethod = "compare",
+ targetMethodDescriptor = "(BB)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(BB)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short", targetMethod = "compare",
+ targetMethodDescriptor = "(SS)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(SS)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "compare", targetMethodDescriptor = "(II)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(II)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "kotlin.jvm.internal.Intrinsics ",
+ targetMethod = "compare", targetMethodDescriptor = "(II)I")
+ public static void
+ integerCompare(MethodHandle method, Object alwaysNull, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpInt((int) arguments[0], (int) arguments[1], hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Byte;)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Short;)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Integer;)I")
+ public static void
+ integerCompareTo(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpInt((int) thisObject, (int) arguments[0], hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", targetMethod = "compare",
+ targetMethodDescriptor = "(JJ)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(JJ)I")
+ public static void
+ longCompare(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpLong((long) arguments[0], (long) arguments[1], hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Long;)I")
+ public static void
+ longCompareTo(MethodHandle method, Long thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpLong(thisObject, (long) arguments[0], hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "kotlin.jvm.internal.Intrinsics ",
+ targetMethod = "compare", targetMethodDescriptor = "(JJ)I")
+ public static void
+ longCompareKt(MethodHandle method, Object alwaysNull, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpLong((long) arguments[0], (long) arguments[1], hookId);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "equalsIgnoreCase")
+ public static void
+ equals(MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean areEqual) {
+ if (!areEqual && arguments.length == 1 && arguments[0] instanceof String) {
+ // The precise value of the result of the comparison is not used by libFuzzer as long as it is
+ // non-zero.
+ TraceDataFlowNativeCallbacks.traceStrcmp(thisObject, (String) arguments[0], 1, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Object", targetMethod = "equals")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.CharSequence", targetMethod = "equals")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Number", targetMethod = "equals")
+ public static void
+ genericEquals(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean areEqual) {
+ if (!areEqual && arguments.length == 1 && arguments[0] != null
+ && thisObject.getClass() == arguments[0].getClass()) {
+ TraceDataFlowNativeCallbacks.traceGenericCmp(thisObject, arguments[0], hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "clojure.lang.Util", targetMethod = "equiv")
+ public static void genericStaticEquals(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean areEqual) {
+ if (!areEqual && arguments.length == 2 && arguments[0] != null && arguments[1] != null
+ && arguments[1].getClass() == arguments[0].getClass()) {
+ TraceDataFlowNativeCallbacks.traceGenericCmp(arguments[0], arguments[1], hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "compareTo")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "compareToIgnoreCase")
+ public static void
+ compareTo(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ if (returnValue != 0 && arguments.length == 1 && arguments[0] instanceof String) {
+ TraceDataFlowNativeCallbacks.traceStrcmp(
+ thisObject, (String) arguments[0], returnValue, hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contentEquals")
+ public static void
+ contentEquals(MethodHandle method, String thisObject, Object[] arguments, int hookId,
+ Boolean areEqualContents) {
+ if (!areEqualContents && arguments.length == 1 && arguments[0] instanceof CharSequence) {
+ TraceDataFlowNativeCallbacks.traceStrcmp(
+ thisObject, ((CharSequence) arguments[0]).toString(), 1, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "regionMatches", targetMethodDescriptor = "(ZILjava/lang/String;II)Z")
+ public static void
+ regionsMatches5(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (!returnValue) {
+ int toffset = (int) arguments[1];
+ String other = (String) arguments[2];
+ int ooffset = (int) arguments[3];
+ int len = (int) arguments[4];
+ regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "regionMatches", targetMethodDescriptor = "(ILjava/lang/String;II)Z")
+ public static void
+ regionMatches4(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (!returnValue) {
+ int toffset = (int) arguments[0];
+ String other = (String) arguments[1];
+ int ooffset = (int) arguments[2];
+ int len = (int) arguments[3];
+ regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId);
+ }
+ }
+
+ private static void regionMatchesInternal(
+ String thisString, int toffset, String other, int ooffset, int len, int hookId) {
+ if (toffset < 0 || ooffset < 0)
+ return;
+ int cappedThisStringEnd = Math.min(toffset + len, thisString.length());
+ int cappedOtherStringEnd = Math.min(ooffset + len, other.length());
+ String thisPart = thisString.substring(toffset, cappedThisStringEnd);
+ String otherPart = other.substring(ooffset, cappedOtherStringEnd);
+ TraceDataFlowNativeCallbacks.traceStrcmp(thisPart, otherPart, 1, hookId);
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contains")
+ public static void
+ contains(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean doesContain) {
+ if (!doesContain && arguments.length == 1 && arguments[0] instanceof CharSequence) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ thisObject, ((CharSequence) arguments[0]).toString(), hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "indexOf")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "lastIndexOf")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.StringBuffer", targetMethod = "indexOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuffer",
+ targetMethod = "lastIndexOf")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", targetMethod = "indexOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuilder",
+ targetMethod = "lastIndexOf")
+ public static void
+ indexOf(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ if (returnValue == -1 && arguments.length >= 1 && arguments[0] instanceof String) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ thisObject.toString(), (String) arguments[0], hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "startsWith")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "endsWith")
+ public static void
+ startsWith(MethodHandle method, String thisObject, Object[] arguments, int hookId,
+ Boolean doesStartOrEndsWith) {
+ if (!doesStartOrEndsWith && arguments.length >= 1 && arguments[0] instanceof String) {
+ TraceDataFlowNativeCallbacks.traceStrstr(thisObject, (String) arguments[0], hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "replace",
+ targetMethodDescriptor =
+ "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;")
+ public static void
+ replace(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) {
+ String original = (String) thisObject;
+ // Report only if the replacement was not successful.
+ if (original.equals(returnValue)) {
+ String target = arguments[0].toString();
+ TraceDataFlowNativeCallbacks.traceStrstr(original, target, hookId);
+ }
+ }
+
+ // For standard Kotlin packages, which are named according to the pattern kotlin.*, we append a
+ // whitespace to the package name of the target class so that they are not mangled due to shading.
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.jvm.internal.Intrinsics ",
+ targetMethod = "areEqual")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ", targetMethod = "equals")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "equals$default")
+ public static void
+ equalsKt(MethodHandle method, Object alwaysNull, Object[] arguments, int hookId,
+ Boolean equalStrings) {
+ if (!equalStrings && arguments.length >= 2 && arguments[0] instanceof String
+ && arguments[1] instanceof String) {
+ TraceDataFlowNativeCallbacks.traceStrcmp(
+ (String) arguments[0], (String) arguments[1], 1, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "contentEquals")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "contentEquals$default")
+ public static void
+ contentEqualKt(MethodHandle method, Object alwaysNull, Object[] arguments, int hookId,
+ Boolean equalStrings) {
+ if (!equalStrings && arguments.length >= 2 && arguments[0] instanceof CharSequence
+ && arguments[1] instanceof CharSequence) {
+ TraceDataFlowNativeCallbacks.traceStrcmp(
+ arguments[0].toString(), arguments[1].toString(), 1, hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ", targetMethod = "compareTo")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "compareTo$default")
+ public static void
+ compareToKt(
+ MethodHandle method, Object alwaysNull, Object[] arguments, int hookId, Integer returnValue) {
+ if (returnValue != 0 && arguments.length >= 2 && arguments[0] instanceof String
+ && arguments[1] instanceof String) {
+ TraceDataFlowNativeCallbacks.traceStrcmp(
+ (String) arguments[0], (String) arguments[1], 1, hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ", targetMethod = "endsWith")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "endsWith$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "startsWith")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "startsWith$default")
+ public static void
+ startsWithKt(MethodHandle method, Object alwaysNull, Object[] arguments, int hookId,
+ Boolean doesStartOrEndsWith) {
+ if (!doesStartOrEndsWith && arguments.length >= 2 && arguments[0] instanceof CharSequence
+ && arguments[1] instanceof CharSequence) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ arguments[0].toString(), arguments[1].toString(), hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ", targetMethod = "contains")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "contains$default")
+ public static void
+ containsKt(
+ MethodHandle method, Object alwaysNull, Object[] arguments, int hookId, Boolean doesContain) {
+ if (!doesContain && arguments.length >= 2 && arguments[0] instanceof CharSequence
+ && arguments[1] instanceof CharSequence) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ arguments[0].toString(), arguments[1].toString(), hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ", targetMethod = "indexOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "indexOf$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "lastIndexOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "lastIndexOf$default")
+ public static void
+ indexOfKt(
+ MethodHandle method, Object alwaysNull, Object[] arguments, int hookId, Integer returnValue) {
+ if (returnValue != -1 || arguments.length < 2 || !(arguments[0] instanceof CharSequence)) {
+ return;
+ }
+ if (arguments[1] instanceof String) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ arguments[0].toString(), (String) arguments[1], hookId);
+ } else if (arguments[1] instanceof Character) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ arguments[0].toString(), ((Character) arguments[1]).toString(), hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ", targetMethod = "replace")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replace$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceAfter")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceAfter$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceAfterLast")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceAfterLast$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceBefore")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceBefore$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceBeforeLast")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceBeforeLast$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceFirst")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "replaceFirst$default")
+ public static void
+ replaceKt(
+ MethodHandle method, Object alwaysNull, Object[] arguments, int hookId, String returnValue) {
+ if (arguments.length < 2 || !(arguments[0] instanceof String)) {
+ return;
+ }
+ String original = (String) arguments[0];
+ if (!original.equals(returnValue)) {
+ return;
+ }
+
+ // We currently don't handle the overloads that take a regex as a second argument.
+ if (arguments[1] instanceof String || arguments[1] instanceof Character) {
+ TraceDataFlowNativeCallbacks.traceStrstr(original, arguments[1].toString(), hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "regionMatches",
+ targetMethodDescriptor = "(Ljava/lang/String;ILjava/lang/String;IIZ)Z")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "regionMatches$default",
+ targetMethodDescriptor = "(Ljava/lang/String;ILjava/lang/String;IIZILjava/lang/Object;)Z")
+ public static void
+ regionMatchesKt(MethodHandle method, Object alwaysNull, Object[] arguments, int hookId,
+ Boolean doesRegionMatch) {
+ if (!doesRegionMatch) {
+ String thisString = arguments[0].toString();
+ int thisOffset = (int) arguments[1];
+ String other = arguments[2].toString();
+ int otherOffset = (int) arguments[3];
+ int length = (int) arguments[4];
+ regionMatchesInternal(thisString, thisOffset, other, otherOffset, length, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "indexOfAny")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "indexOfAny$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "lastIndexOfAny")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "lastIndexOfAny$default")
+ public static void
+ indexOfAnyKt(
+ MethodHandle method, Object alwaysNull, Object[] arguments, int hookId, Integer returnValue) {
+ if (returnValue == -1 && arguments.length >= 2 && arguments[0] instanceof CharSequence) {
+ guideTowardContainmentOfFirstElement(arguments[0].toString(), arguments[1], hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ", targetMethod = "findAnyOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "findAnyOf$default")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "findLastAnyOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "kotlin.text.StringsKt ",
+ targetMethod = "findLastAnyOf$default")
+ public static void
+ findAnyKt(
+ MethodHandle method, Object alwaysNull, Object[] arguments, int hookId, Object returnValue) {
+ if (returnValue == null && arguments.length >= 2 && arguments[0] instanceof CharSequence) {
+ guideTowardContainmentOfFirstElement(arguments[0].toString(), arguments[1], hookId);
+ }
+ }
+
+ private static void guideTowardContainmentOfFirstElement(
+ String containingString, Object candidateCollectionObj, int hookId) {
+ if (candidateCollectionObj instanceof Collection<?>) {
+ Collection<?> strings = (Collection<?>) candidateCollectionObj;
+ if (strings.isEmpty()) {
+ return;
+ }
+ Object firstElementObj = strings.iterator().next();
+ if (firstElementObj instanceof CharSequence) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ containingString, firstElementObj.toString(), hookId);
+ }
+ } else if (candidateCollectionObj.getClass().isArray()) {
+ if (candidateCollectionObj.getClass().getComponentType() == char.class) {
+ char[] chars = (char[]) candidateCollectionObj;
+ if (chars.length > 0) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ containingString, Character.toString(chars[0]), hookId);
+ }
+ }
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals",
+ targetMethodDescriptor = "([B[B)Z")
+ public static void
+ arraysEquals(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (returnValue)
+ return;
+ byte[] first = (byte[]) arguments[0];
+ byte[] second = (byte[]) arguments[1];
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals",
+ targetMethodDescriptor = "([BII[BII)Z")
+ public static void
+ arraysEqualsRange(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (returnValue)
+ return;
+ byte[] first =
+ Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]);
+ byte[] second =
+ Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]);
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare",
+ targetMethodDescriptor = "([B[B)I")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "([B[B)I")
+ public static void
+ arraysCompare(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ if (returnValue == 0)
+ return;
+ byte[] first = (byte[]) arguments[0];
+ byte[] second = (byte[]) arguments[1];
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare",
+ targetMethodDescriptor = "([BII[BII)I")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "([BII[BII)I")
+ public static void
+ arraysCompareRange(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ if (returnValue == 0)
+ return;
+ byte[] first =
+ Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]);
+ byte[] second =
+ Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]);
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId);
+ }
+
+ // The maximal number of elements of a non-TreeMap Map that will be sorted and searched for the
+ // key closest to the current lookup key in the mapGet hook.
+ private static final int MAX_NUM_KEYS_TO_ENUMERATE = 100;
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Map", targetMethod = "get")
+ public static void mapGet(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ if (returnValue != null)
+ return;
+ if (arguments.length != 1) {
+ return;
+ }
+ if (thisObject == null)
+ return;
+ final Map map = (Map) thisObject;
+ if (map.size() == 0)
+ return;
+ final Object currentKey = arguments[0];
+ if (currentKey == null)
+ return;
+ // Find two valid map keys that bracket currentKey.
+ // This is a generalization of libFuzzer's __sanitizer_cov_trace_switch:
+ // https://github.com/llvm/llvm-project/blob/318942de229beb3b2587df09e776a50327b5cef0/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L564
+ Object lowerBoundKey = null;
+ Object upperBoundKey = null;
+ try {
+ if (map instanceof TreeMap) {
+ final TreeMap treeMap = (TreeMap) map;
+ try {
+ lowerBoundKey = treeMap.floorKey(currentKey);
+ upperBoundKey = treeMap.ceilingKey(currentKey);
+ } catch (ClassCastException ignored) {
+ // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be
+ // compared to the maps keys.
+ }
+ } else if (currentKey instanceof Comparable) {
+ final Comparable comparableCurrentKey = (Comparable) currentKey;
+ // Find two keys that bracket currentKey.
+ // Note: This is not deterministic if map.size() > MAX_NUM_KEYS_TO_ENUMERATE.
+ int enumeratedKeys = 0;
+ for (Object validKey : map.keySet()) {
+ if (!(validKey instanceof Comparable))
+ continue;
+ final Comparable comparableValidKey = (Comparable) validKey;
+ // If the key sorts lower than the non-existing key, but higher than the current lower
+ // bound, update the lower bound and vice versa for the upper bound.
+ try {
+ if (comparableValidKey.compareTo(comparableCurrentKey) < 0
+ && (lowerBoundKey == null || comparableValidKey.compareTo(lowerBoundKey) > 0)) {
+ lowerBoundKey = validKey;
+ }
+ if (comparableValidKey.compareTo(comparableCurrentKey) > 0
+ && (upperBoundKey == null || comparableValidKey.compareTo(upperBoundKey) < 0)) {
+ upperBoundKey = validKey;
+ }
+ } catch (ClassCastException ignored) {
+ // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be
+ // compared to the maps keys.
+ }
+ if (enumeratedKeys++ > MAX_NUM_KEYS_TO_ENUMERATE)
+ break;
+ }
+ }
+ } catch (ConcurrentModificationException ignored) {
+ // map was modified by another thread, skip this invocation
+ return;
+ }
+ // Modify the hook ID so that compares against distinct valid keys are traced separately.
+ if (lowerBoundKey != null) {
+ TraceDataFlowNativeCallbacks.traceGenericCmp(
+ currentKey, lowerBoundKey, hookId + lowerBoundKey.hashCode());
+ }
+ if (upperBoundKey != null) {
+ TraceDataFlowNativeCallbacks.traceGenericCmp(
+ currentKey, upperBoundKey, hookId + upperBoundKey.hashCode());
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "org.junit.jupiter.api.Assertions",
+ targetMethod = "assertNotEquals",
+ targetMethodDescriptor = "(Ljava/lang/Object;Ljava/lang/Object;)V")
+ @MethodHook(type = HookType.AFTER, targetClassName = "org.junit.jupiter.api.Assertions",
+ targetMethod = "assertNotEquals",
+ targetMethodDescriptor = "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V")
+ @MethodHook(type = HookType.AFTER, targetClassName = "org.junit.jupiter.api.Assertions",
+ targetMethod = "assertNotEquals",
+ targetMethodDescriptor =
+ "(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/Supplier;)V")
+ public static void
+ assertEquals(MethodHandle method, Object node, Object[] args, int hookId, Object alwaysNull) {
+ if (args[0] != null && args[1] != null && args[0].getClass() == args[1].getClass()) {
+ TraceDataFlowNativeCallbacks.traceGenericCmp(args[0], args[1], hookId);
+ }
+ }
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java
new file mode 100644
index 00000000..777eb0a2
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java
@@ -0,0 +1,125 @@
+// 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.github.fmeum.rules_jni.RulesJni;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import org.objectweb.asm.Type;
+
+@SuppressWarnings("unused")
+final public class TraceDataFlowNativeCallbacks {
+ // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently
+ // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is more
+ // likely to insert literal null bytes, having both the fuzzer input and the reported string
+ // comparisons be CESU8 should perform even better than the current implementation using modified
+ // UTF-8.
+ private static final Charset FUZZED_DATA_CHARSET = Charset.forName("CESU8");
+
+ static {
+ RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver");
+ }
+
+ // It is possible for RulesJni#loadLibrary to trigger a hook even though it isn't instrumented if
+ // it uses regexes, which it does with at least some JDKs due to its use of String#format. This
+ // led to exceptions in the past when the hook ended up calling traceStrcmp or traceStrstr before
+ // the static initializer was run: FUZZED_DATA_CHARSET used to be initialized after the call and
+ // thus still had the value null when encodeForLibFuzzer was called, resulting in an NPE in
+ // String#getBytes(Charset). Just switching the order may actually make this bug worse: It could
+ // now lead to traceMemcmp being called before the native library has been loaded. We guard
+ // against this by making the hooks noops when static initialization of this class hasn't
+ // completed yet.
+ private static final boolean NATIVE_INITIALIZED = true;
+
+ public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc);
+
+ public static void traceStrcmp(String s1, String s2, int result, int pc) {
+ if (NATIVE_INITIALIZED) {
+ traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc);
+ }
+ }
+
+ public static void traceStrstr(String s1, String s2, int pc) {
+ if (NATIVE_INITIALIZED) {
+ traceStrstr0(encodeForLibFuzzer(s2), pc);
+ }
+ }
+
+ public static void traceReflectiveCall(Executable callee, int pc) {
+ String className = callee.getDeclaringClass().getCanonicalName();
+ String executableName = callee.getName();
+ String descriptor;
+ if (callee instanceof Method) {
+ descriptor = Type.getMethodDescriptor((Method) callee);
+ } else {
+ descriptor = Type.getConstructorDescriptor((Constructor<?>) callee);
+ }
+ tracePcIndir(Arrays.hashCode(new String[] {className, executableName, descriptor}), pc);
+ }
+
+ public static int traceCmpLongWrapper(long arg1, long arg2, int pc) {
+ traceCmpLong(arg1, arg2, pc);
+ // Long.compare serves as a substitute for the lcmp opcode, which can't be used directly
+ // as the stack layout required for the call can't be achieved without local variables.
+ return Long.compare(arg1, arg2);
+ }
+
+ // The caller has to ensure that arg1 and arg2 have the same class.
+ public static void traceGenericCmp(Object arg1, Object arg2, int pc) {
+ if (arg1 instanceof CharSequence) {
+ traceStrcmp(arg1.toString(), arg2.toString(), 1, pc);
+ } else if (arg1 instanceof Integer) {
+ traceCmpInt((int) arg1, (int) arg2, pc);
+ } else if (arg1 instanceof Long) {
+ traceCmpLong((long) arg1, (long) arg2, pc);
+ } else if (arg1 instanceof Short) {
+ traceCmpInt((short) arg1, (short) arg2, pc);
+ } else if (arg1 instanceof Byte) {
+ traceCmpInt((byte) arg1, (byte) arg2, pc);
+ } else if (arg1 instanceof Character) {
+ traceCmpInt((char) arg1, (char) arg2, pc);
+ } else if (arg1 instanceof Number) {
+ traceCmpLong(((Number) arg1).longValue(), ((Number) arg2).longValue(), pc);
+ } else if (arg1 instanceof byte[]) {
+ traceMemcmp((byte[]) arg1, (byte[]) arg2, 1, pc);
+ }
+ }
+
+ /* trace-cmp */
+ public static native void traceCmpInt(int arg1, int arg2, int pc);
+ public static native void traceConstCmpInt(int arg1, int arg2, int pc);
+ public static native void traceCmpLong(long arg1, long arg2, int pc);
+ public static native void traceSwitch(long val, long[] cases, int pc);
+ /* trace-div */
+ public static native void traceDivInt(int val, int pc);
+ public static native void traceDivLong(long val, int pc);
+ /* trace-gep */
+ public static native void traceGep(long val, int pc);
+ /* indirect-calls */
+ public static native void tracePcIndir(int callee, int caller);
+
+ public static native void handleLibraryLoad();
+
+ private static byte[] encodeForLibFuzzer(String str) {
+ // libFuzzer string hooks only ever consume the first 64 bytes, so we can definitely cut the
+ // string off after 64 characters.
+ return str.substring(0, Math.min(str.length(), 64)).getBytes(FUZZED_DATA_CHARSET);
+ }
+
+ private static native void traceStrstr0(byte[] needle, int pc);
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java
new file mode 100644
index 00000000..c4991eb5
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java
@@ -0,0 +1,47 @@
+// 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 TraceDivHooks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "divideUnsigned", targetMethodDescriptor = "(II)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "remainderUnsigned", targetMethodDescriptor = "(II)I")
+ public static void
+ intUnsignedDivide(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ // Since the arguments are to be treated as unsigned integers we need a long to fit the
+ // divisor.
+ TraceDataFlowNativeCallbacks.traceDivLong(Integer.toUnsignedLong((int) arguments[1]), hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "divideUnsigned", targetMethodDescriptor = "(JJ)J")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "remainderUnsigned", targetMethodDescriptor = "(JJ)J")
+ public static void
+ longUnsignedDivide(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ long divisor = (long) arguments[1];
+ // Run the callback only if the divisor, which is regarded as an unsigned long, fits in a
+ // signed long, i.e., does not have the sign bit set.
+ if (divisor > 0) {
+ TraceDataFlowNativeCallbacks.traceDivLong(divisor, hookId);
+ }
+ }
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java b/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java
new file mode 100644
index 00000000..897ede6c
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.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;
+import java.lang.reflect.Executable;
+
+@SuppressWarnings("unused")
+final public class TraceIndirHooks {
+ // The reflection hook is of type AFTER as it should only report calls that did not fail because
+ // of incorrect arguments passed.
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.reflect.Method", targetMethod = "invoke")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.reflect.Constructor",
+ targetMethod = "newInstance")
+ public static void
+ methodInvoke(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ TraceDataFlowNativeCallbacks.traceReflectiveCall((Executable) thisObject, hookId);
+ }
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/bootstrap_shade_rules b/src/main/java/com/code_intelligence/jazzer/runtime/bootstrap_shade_rules
new file mode 100644
index 00000000..0cafcf0a
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/bootstrap_shade_rules
@@ -0,0 +1,4 @@
+rule com.github.fmeum.rules_jni.** com.code_intelligence.jazzer.bootstrap.@0
+rule kotlin.** com.code_intelligence.jazzer.bootstrap.@0
+rule net.sf.jsqlparser.** com.code_intelligence.jazzer.bootstrap.@0
+rule org.objectweb.asm.** com.code_intelligence.jazzer.bootstrap.@0
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/verify_shading.sh b/src/main/java/com/code_intelligence/jazzer/runtime/verify_shading.sh
new file mode 100755
index 00000000..b3a74ea9
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/verify_shading.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env sh
+# Copyright 2022 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.
+
+[ -f "$1" ] || exit 1
+# List all files in the jar and exclude an allowed list of files.
+# Since grep fails if there is no match, ! ... | grep ... fails if there is a
+# match.
+! external/local_jdk/bin/jar tf "$1" | \
+ grep -v \
+ -e '^com/$' \
+ -e '^com/code_intelligence/$' \
+ -e '^com/code_intelligence/jazzer/' \
+ -e '^jaz/' \
+ -e '^META-INF/$' \
+ -e '^META-INF/MANIFEST.MF$'