diff options
Diffstat (limited to 'src/jmh/java/com')
20 files changed, 1280 insertions, 0 deletions
diff --git a/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel b/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel new file mode 100644 index 00000000..0a88ca09 --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel @@ -0,0 +1,6 @@ +java_plugin( + name = "JmhGeneratorAnnotationProcessor", + processor_class = "org.openjdk.jmh.generators.BenchmarkProcessor", + visibility = ["//src/jmh/java:__subpackages__"], + deps = ["@maven//:org_openjdk_jmh_jmh_generator_annprocess"], +) diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel new file mode 100644 index 00000000..1dbcdbff --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -0,0 +1,105 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") +load("//bazel:kotlin.bzl", "ktlint") +load("//src/jmh/java/com/code_intelligence/jazzer:jmh.bzl", "JMH_TEST_ARGS") + +java_binary( + name = "CoverageInstrumentationBenchmark", + main_class = "org.openjdk.jmh.Main", + runtime_deps = [ + ":coverage_instrumentation_benchmark", + ], +) + +java_test( + name = "CoverageInstrumentationBenchmarkTest", + args = JMH_TEST_ARGS, + jvm_flags = [ + "-XX:CompileCommand=print,*CoverageMap.recordCoverage", + ], + main_class = "org.openjdk.jmh.Main", + # Directly invoke JMH's main without using a testrunner. + use_testrunner = False, + runtime_deps = [ + ":coverage_instrumentation_benchmark", + ], +) + +java_library( + name = "coverage_instrumentation_benchmark", + srcs = ["CoverageInstrumentationBenchmark.java"], + plugins = ["//src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"], + runtime_deps = [ + "@maven//:com_mikesamuel_json_sanitizer", + ], + deps = [ + ":kotlin_strategies", + ":strategies", + "//src/main/java/com/code_intelligence/jazzer/instrumentor", + "@maven//:org_openjdk_jmh_jmh_core", + ], +) + +java_library( + name = "strategies", + srcs = [ + "DirectByteBuffer2CoverageMap.java", + "DirectByteBufferCoverageMap.java", + "Unsafe2CoverageMap.java", + "UnsafeBranchfreeCoverageMap.java", + "UnsafeCoverageMap.java", + "UnsafeSimpleIncrementCoverageMap.java", + ], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/instrumentor", + "@jazzer_jacoco//:jacoco_internal", + "@org_ow2_asm_asm//jar", + ], +) + +kt_jvm_library( + name = "kotlin_strategies", + srcs = ["DirectByteBufferStrategy.kt"], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/instrumentor", + "@jazzer_jacoco//:jacoco_internal", + "@org_ow2_asm_asm//jar", + ], +) + +java_binary( + name = "EdgeCoverageInstrumentationBenchmark", + main_class = "org.openjdk.jmh.Main", + runtime_deps = [ + ":edge_coverage_instrumentation_benchmark", + ], +) + +java_test( + name = "EdgeCoverageInstrumentationBenchmarkTest", + args = JMH_TEST_ARGS, + main_class = "org.openjdk.jmh.Main", + # Directly invoke JMH's main without using a testrunner. + use_testrunner = False, + runtime_deps = [ + ":edge_coverage_instrumentation_benchmark", + ], +) + +java_jni_library( + name = "edge_coverage_instrumentation_benchmark", + srcs = [ + "EdgeCoverageInstrumentation.java", + "EdgeCoverageTarget.java", + ], + native_libs = ["//src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver"], + plugins = ["//src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/instrumentor", + "//src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", + "//src/test/java/com/code_intelligence/jazzer/instrumentor:patch_test_utils", + "@maven//:org_openjdk_jmh_jmh_core", + ], +) + +ktlint() diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java new file mode 100644 index 00000000..4401da7d --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java @@ -0,0 +1,178 @@ +// 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.instrumentor; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +/** + * This benchmark compares the throughput of a typical fuzz target when instrumented with different + * edge coverage instrumentation strategies and coverage map implementations. + * + * The benchmark currently uses the OWASP json-sanitizer as its target, which has the following + * desirable properties for a benchmark: + * - It is a reasonably sized project that does not consist of many different classes. + * - It is very heavy on computation with a high density of branching. + * - It is entirely CPU bound with no IO and does not call expensive methods from the standard + * library. + * With these properties, results obtained from this benchmark should provide reasonable lower + * bounds on the relative slowdown introduced by the various approaches to instrumentations. + */ +@State(Scope.Benchmark) +public class CoverageInstrumentationBenchmark { + private static final String TARGET_CLASSNAME = "com.google.json.JsonSanitizer"; + private static final String TARGET_PACKAGE = + TARGET_CLASSNAME.substring(0, TARGET_CLASSNAME.lastIndexOf('.')); + private static final String TARGET_METHOD = "sanitize"; + private static final MethodType TARGET_TYPE = MethodType.methodType(String.class, String.class); + + // This is part of the benchmark's state and not a constant to prevent constant folding. + String TARGET_ARG = + "{\"foo\":1123987,\"bar\":[true, false],\"baz\":{\"foo\":\"132รค3\",\"bar\":1.123e-005}}"; + + MethodHandle uninstrumented_sanitize; + MethodHandle local_DirectByteBuffer_NeverZero_sanitize; + MethodHandle staticMethod_DirectByteBuffer_NeverZero_sanitize; + MethodHandle staticMethod_DirectByteBuffer2_NeverZero_sanitize; + MethodHandle staticMethod_Unsafe_NeverZero_sanitize; + MethodHandle staticMethod_Unsafe_NeverZero2_sanitize; + MethodHandle staticMethod_Unsafe_NeverZeroBranchfree_sanitize; + MethodHandle staticMethod_Unsafe_SimpleIncrement_sanitize; + + public static MethodHandle handleForTargetMethod(ClassLoader classLoader) + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { + Class<?> targetClass = classLoader.loadClass(TARGET_CLASSNAME); + return MethodHandles.lookup().findStatic(targetClass, TARGET_METHOD, TARGET_TYPE); + } + + public static MethodHandle instrumentWithStrategy( + EdgeCoverageStrategy strategy, Class<?> coverageMapClass) + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { + if (strategy == null) { + // Do not instrument the code by using the benchmark class' ClassLoader. + return handleForTargetMethod(CoverageInstrumentationBenchmark.class.getClassLoader()); + } + // It's fine to reuse a single instrumentor here as we don't want to know which class received + // how many counters. + Instrumentor instrumentor = new EdgeCoverageInstrumentor(strategy, coverageMapClass, 0); + return handleForTargetMethod(new InstrumentingClassLoader(instrumentor, TARGET_PACKAGE)); + } + + @Setup + public void instrumentWithStrategies() + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { + uninstrumented_sanitize = instrumentWithStrategy(null, null); + local_DirectByteBuffer_NeverZero_sanitize = instrumentWithStrategy( + DirectByteBufferStrategy.INSTANCE, DirectByteBufferCoverageMap.class); + staticMethod_DirectByteBuffer_NeverZero_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBufferCoverageMap.class); + staticMethod_DirectByteBuffer2_NeverZero_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBuffer2CoverageMap.class); + staticMethod_Unsafe_NeverZero_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), UnsafeCoverageMap.class); + staticMethod_Unsafe_NeverZero2_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), Unsafe2CoverageMap.class); + staticMethod_Unsafe_SimpleIncrement_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), UnsafeSimpleIncrementCoverageMap.class); + staticMethod_Unsafe_NeverZeroBranchfree_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), UnsafeBranchfreeCoverageMap.class); + } + + @Benchmark + public String uninstrumented() throws Throwable { + return (String) uninstrumented_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String local_DirectByteBuffer_NeverZero() throws Throwable { + return (String) local_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_DirectByteBuffer_NeverZero() throws Throwable { + return (String) staticMethod_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_DirectByteBuffer2_NeverZero() throws Throwable { + return (String) staticMethod_DirectByteBuffer2_NeverZero_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_Unsafe_NeverZero() throws Throwable { + return (String) staticMethod_Unsafe_NeverZero_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_Unsafe_NeverZero2() throws Throwable { + return (String) staticMethod_Unsafe_NeverZero2_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_Unsafe_SimpleIncrement() throws Throwable { + return (String) staticMethod_Unsafe_SimpleIncrement_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_Unsafe_NeverZeroBranchfree() throws Throwable { + return (String) staticMethod_Unsafe_NeverZeroBranchfree_sanitize.invokeExact(TARGET_ARG); + } +} + +class InstrumentingClassLoader extends ClassLoader { + private final Instrumentor instrumentor; + private final String classNamePrefix; + + InstrumentingClassLoader(Instrumentor instrumentor, String packageToInstrument) { + super(InstrumentingClassLoader.class.getClassLoader()); + this.instrumentor = instrumentor; + this.classNamePrefix = packageToInstrument + "."; + } + + @Override + public Class<?> loadClass(String name) throws ClassNotFoundException { + if (!name.startsWith(classNamePrefix)) { + return super.loadClass(name); + } + try (InputStream stream = super.getResourceAsStream(name.replace('.', '/') + ".class")) { + if (stream == null) { + throw new ClassNotFoundException(String.format("Failed to find class file for %s", name)); + } + byte[] bytecode = readAllBytes(stream); + byte[] instrumentedBytecode = instrumentor.instrument(name.replace('.', '/'), bytecode); + return defineClass(name, instrumentedBytecode, 0, instrumentedBytecode.length); + } catch (IOException e) { + throw new ClassNotFoundException(String.format("Failed to read class file for %s", name), e); + } + } + + private static byte[] readAllBytes(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[64 * 104 * 1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + return out.toByteArray(); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java new file mode 100644 index 00000000..c57babb5 --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java @@ -0,0 +1,32 @@ +// 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.instrumentor; + +import java.nio.ByteBuffer; + +public final class DirectByteBuffer2CoverageMap { + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final int NUM_COUNTERS = 4096; + public static final ByteBuffer counters = ByteBuffer.allocateDirect(NUM_COUNTERS); + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final byte counter = counters.get(id); + counters.put(id, (byte) (counter == -1 ? 1 : counter + 1)); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java new file mode 100644 index 00000000..e5e66abb --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java @@ -0,0 +1,36 @@ +// 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.instrumentor; + +import java.nio.ByteBuffer; + +public final class DirectByteBufferCoverageMap { + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final int NUM_COUNTERS = 4096; + public static final ByteBuffer counters = ByteBuffer.allocateDirect(NUM_COUNTERS); + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final byte counter = counters.get(id); + if (counter == -1) { + counters.put(id, (byte) 1); + } else { + counters.put(id, (byte) (counter + 1)); + } + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt new file mode 100644 index 00000000..14f5041f --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt @@ -0,0 +1,81 @@ +// 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.instrumentor + +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +object DirectByteBufferStrategy : EdgeCoverageStrategy { + + override fun instrumentControlFlowEdge( + mv: MethodVisitor, + edgeId: Int, + variable: Int, + coverageMapInternalClassName: String, + ) { + mv.apply { + visitVarInsn(Opcodes.ALOAD, variable) + // Stack: counters + push(edgeId) + // Stack: counters | edgeId + visitInsn(Opcodes.DUP2) + // Stack: counters | edgeId | counters | edgeId + visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false) + // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in + // that case. + // This approach performs better than saturating the counter at 255 (see Section 3.3 of + // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf) + // Stack: counters | edgeId | counter (sign-extended to int) + push(0xff) + // Stack: counters | edgeId | counter (sign-extended to int) | 0x000000ff + visitInsn(Opcodes.IAND) + // Stack: counters | edgeId | counter (zero-extended to int) + push(1) + // Stack: counters | edgeId | counter | 1 + visitInsn(Opcodes.IADD) + // Stack: counters | edgeId | counter + 1 + visitInsn(Opcodes.DUP) + // Stack: counters | edgeId | counter + 1 | counter + 1 + push(8) + // Stack: counters | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5) + visitInsn(Opcodes.ISHR) + // Stack: counters | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise + visitInsn(Opcodes.IADD) + // Stack: counters | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise + visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false) + // Stack: counters + visitInsn(Opcodes.POP) + } + } + + override val instrumentControlFlowEdgeStackSize = 5 + + override val localVariableType get() = "java/nio/ByteBuffer" + + override fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) { + mv.apply { + visitFieldInsn( + Opcodes.GETSTATIC, + coverageMapInternalClassName, + "counters", + "Ljava/nio/ByteBuffer;", + ) + // Stack: counters (maxStack: 1) + visitVarInsn(Opcodes.ASTORE, variable) + } + } + + override val loadLocalVariableStackSize = 1 +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java new file mode 100644 index 00000000..97f6b43b --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java @@ -0,0 +1,66 @@ +// 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.instrumentor; + +import static com.code_intelligence.jazzer.instrumentor.PatchTestUtils.*; +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodType.methodType; + +import com.code_intelligence.jazzer.runtime.CoverageMap; +import java.lang.invoke.*; +import java.nio.file.Files; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.*; + +@Warmup(iterations = 10, time = 3) +@Measurement(iterations = 10, time = 3) +@Fork(value = 3) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Benchmark) +@SuppressWarnings("unused") +public class EdgeCoverageInstrumentation { + private MethodHandle exampleMethod; + + @Setup + public void setupInstrumentation() throws Throwable { + String outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + if (outDir == null || outDir.isEmpty()) { + outDir = + Files.createTempDirectory(EdgeCoverageInstrumentation.class.getSimpleName()).toString(); + } + + byte[] originalBytecode = classToBytecode(EdgeCoverageTarget.class); + dumpBytecode(outDir, EdgeCoverageTarget.class.getName(), originalBytecode); + + byte[] patchedBytecode = applyInstrumentation(originalBytecode); + dumpBytecode(outDir, EdgeCoverageTarget.class.getName() + ".patched", patchedBytecode); + + Class<?> patchedClass = bytecodeToClass(EdgeCoverageTarget.class.getName(), patchedBytecode); + Object obj = lookup().findConstructor(patchedClass, methodType(void.class)).invoke(); + exampleMethod = lookup().bind(obj, "exampleMethod", methodType(List.class)); + } + + private byte[] applyInstrumentation(byte[] bytecode) { + return new EdgeCoverageInstrumentor(new StaticMethodStrategy(), CoverageMap.class, 0) + .instrument(EdgeCoverageTarget.class.getName().replace('.', '/'), bytecode); + } + + @Benchmark + public Object benchmarkInstrumentedMethodCall() throws Throwable { + return exampleMethod.invoke(); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java new file mode 100644 index 00000000..57eb8807 --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java @@ -0,0 +1,44 @@ +/* + * 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.instrumentor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +public class EdgeCoverageTarget { + private final Random rnd = new Random(); + + @SuppressWarnings("unused") + public List<Integer> exampleMethod() { + ArrayList<Integer> rnds = new ArrayList<>(); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + int i = rnd.nextInt() + rnd.nextInt(); + if (i > 0 && i < Integer.MAX_VALUE / 2) { + i--; + } else { + i++; + } + rnds.add(i); + return rnds.stream().map(n -> n + 1).collect(Collectors.toList()); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java new file mode 100644 index 00000000..030d9a95 --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java @@ -0,0 +1,55 @@ +// 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class Unsafe2CoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + final byte counter = UNSAFE.getByte(address); + UNSAFE.putByte(address, (byte) (counter == -1 ? 1 : counter + 1)); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java new file mode 100644 index 00000000..3694b95f --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java @@ -0,0 +1,55 @@ +// 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class UnsafeBranchfreeCoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + final int incrementedCounter = UNSAFE.getByte(address) + 1; + UNSAFE.putByte(address, (byte) (incrementedCounter ^ (incrementedCounter >>> 8))); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java new file mode 100644 index 00000000..cf73928d --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java @@ -0,0 +1,59 @@ +// 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class UnsafeCoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + final byte counter = UNSAFE.getByte(address); + if (counter == -1) { + UNSAFE.putByte(address, (byte) 1); + } else { + UNSAFE.putByte(address, (byte) (counter + 1)); + } + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java new file mode 100644 index 00000000..60fb8c8d --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java @@ -0,0 +1,54 @@ +// 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class UnsafeSimpleIncrementCoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + UNSAFE.putByte(address, (byte) (UNSAFE.getByte(address) + 1)); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl b/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl new file mode 100644 index 00000000..5391a46b --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl @@ -0,0 +1,24 @@ +# 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. + +JMH_TEST_ARGS = [ + # Fail fast on any exceptions produced by benchmarks. + "-foe true", + "-wf 0", + "-f 1", + "-wi 0", + "-i 1", + "-r 1s", + "-w 1s", +] diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel new file mode 100644 index 00000000..7595d660 --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -0,0 +1,55 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") +load("//src/jmh/java/com/code_intelligence/jazzer:jmh.bzl", "JMH_TEST_ARGS") + +java_binary( + name = "FuzzerCallbacksBenchmark", + main_class = "org.openjdk.jmh.Main", + runtime_deps = [ + ":fuzzer_callbacks_benchmark", + ], +) + +java_test( + name = "FuzzerCallbacksBenchmarkTest", + args = JMH_TEST_ARGS, + main_class = "org.openjdk.jmh.Main", + # CriticalJNINatives have been removed in Java 18. + tags = [ + "exclusive-if-local", + "no-linux-jdk19", + ], + # Directly invoke JMH's main without using a testrunner. + use_testrunner = False, + runtime_deps = [ + ":fuzzer_callbacks_benchmark", + ], +) + +java_library( + name = "fuzzer_callbacks_benchmark", + srcs = ["FuzzerCallbacksBenchmark.java"], + plugins = ["//src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"], + deps = [ + ":fuzzer_callbacks", + "@maven//:org_openjdk_jmh_jmh_core", + ], +) + +java_jni_library( + name = "fuzzer_callbacks", + srcs = [ + "FuzzerCallbacks.java", + "FuzzerCallbacksOptimizedCritical.java", + "FuzzerCallbacksOptimizedNonCritical.java", + # Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + # "FuzzerCallbacksPanama.java", + "FuzzerCallbacksWithPc.java", + ], + javacopts = [ + # Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + # "--add-modules", + # "jdk.incubator.foreign", + ], + native_libs = ["//src/jmh/native/com/code_intelligence/jazzer/runtime:fuzzer_callbacks"], + visibility = ["//src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__"], +) diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java new file mode 100644 index 00000000..6e8343ce --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java @@ -0,0 +1,29 @@ +// 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; + +public final class FuzzerCallbacks { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacks.class); + } + + static native void traceCmpInt(int arg1, int arg2, int pc); + static native void traceSwitch(long val, long[] cases, int pc); + + static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + static native void traceStrstr(String s1, String s2, int pc); +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java new file mode 100644 index 00000000..81a8f56e --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java @@ -0,0 +1,219 @@ +// 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 java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(value = 3) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class FuzzerCallbacksBenchmark { + @State(Scope.Benchmark) + public static class TraceCmpIntState { + int arg1 = 0xCAFECAFE; + int arg2 = 0xFEEDFEED; + int pc = 0x12345678; + } + + @Benchmark + public void traceCmpInt(TraceCmpIntState state) { + FuzzerCallbacks.traceCmpInt(state.arg1, state.arg2, state.pc); + } + + @Benchmark + public void traceCmpIntWithPc(TraceCmpIntState state) { + FuzzerCallbacksWithPc.traceCmpInt(state.arg1, state.arg2, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"}) + public void traceCmpIntOptimizedCritical(TraceCmpIntState state) { + FuzzerCallbacksOptimizedCritical.traceCmpInt(state.arg1, state.arg2, state.pc); + } + + // Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + // @Benchmark + // @Fork(jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules", + // "jdk.incubator.foreign"}) + // public void + // traceCmpIntPanama(TraceCmpIntState state) throws Throwable { + // FuzzerCallbacksPanama.traceCmpInt(state.arg1, state.arg2, state.pc); + // } + + @State(Scope.Benchmark) + public static class TraceSwitchState { + @Param({"5", "10"}) int numCases; + + long val; + long[] cases; + int pc = 0x12345678; + + @Setup + public void setup() { + cases = new long[2 + numCases]; + Random random = ThreadLocalRandom.current(); + Arrays.setAll(cases, i -> { + switch (i) { + case 0: + return numCases; + case 1: + return 32; + default: + return random.nextInt(); + } + }); + Arrays.sort(cases, 2, cases.length); + val = random.nextInt(); + } + } + + @Benchmark + public void traceSwitch(TraceSwitchState state) { + FuzzerCallbacks.traceSwitch(state.val, state.cases, state.pc); + } + + @Benchmark + public void traceSwitchWithPc(TraceSwitchState state) { + FuzzerCallbacksWithPc.traceSwitch(state.val, state.cases, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"}) + public void traceSwitchOptimizedCritical(TraceSwitchState state) { + FuzzerCallbacksOptimizedCritical.traceSwitch(state.val, state.cases, state.pc); + } + + @Benchmark + public void traceSwitchOptimizedNonCritical(TraceSwitchState state) { + FuzzerCallbacksOptimizedNonCritical.traceSwitch(state.val, state.cases, state.pc); + } + + // Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + // @Benchmark + // @Fork(jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules", + // "jdk.incubator.foreign"}) + // public void + // traceCmpSwitchPanama(TraceSwitchState state) throws Throwable { + // FuzzerCallbacksPanama.traceCmpSwitch(state.val, state.cases, state.pc); + // } + + @State(Scope.Benchmark) + public static class TraceMemcmpState { + @Param({"10", "100", "1000"}) int length; + + byte[] array1; + byte[] array2; + int pc = 0x12345678; + + @Setup + public void setup() { + array1 = new byte[length]; + array2 = new byte[length]; + + Random random = ThreadLocalRandom.current(); + random.nextBytes(array1); + random.nextBytes(array2); + // Make the arrays agree unil the midpoint to benchmark the "average" + // case of an interesting memcmp. + System.arraycopy(array1, 0, array2, 0, length / 2); + } + } + + @Benchmark + public void traceMemcmp(TraceMemcmpState state) { + FuzzerCallbacks.traceMemcmp(state.array1, state.array2, 1, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"}) + public void traceMemcmpOptimizedCritical(TraceMemcmpState state) { + FuzzerCallbacksOptimizedCritical.traceMemcmp(state.array1, state.array2, 1, state.pc); + } + + @Benchmark + public void traceMemcmpOptimizedNonCritical(TraceMemcmpState state) { + FuzzerCallbacksOptimizedNonCritical.traceMemcmp(state.array1, state.array2, 1, state.pc); + } + + @State(Scope.Benchmark) + public static class TraceStrstrState { + @Param({"10", "100", "1000"}) int length; + @Param({"true", "false"}) boolean asciiOnly; + + String haystack; + String needle; + int pc = 0x12345678; + + @Setup + public void setup() { + haystack = randomString(length, asciiOnly); + needle = randomString(length, asciiOnly); + } + + private String randomString(int length, boolean asciiOnly) { + String asciiString = + ThreadLocalRandom.current() + .ints('a', 'z' + 1) + .limit(length) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + if (asciiOnly) { + return asciiString; + } + // Force String to be non-Latin-1 to preclude compact string optimization. + return "\uFFFD" + asciiString.substring(1); + } + } + + @Benchmark + public void traceStrstr(TraceStrstrState state) { + FuzzerCallbacks.traceStrstr(state.haystack, state.needle, state.pc); + } + + @Benchmark + public void traceStrstrOptimizedNonCritical(TraceStrstrState state) { + FuzzerCallbacksOptimizedNonCritical.traceStrstr(state.haystack, state.needle, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+IgnoreUnrecognizedVMOptions", "-XX:+CriticalJNINatives"}) + public void traceStrstrOptimizedJavaCritical(TraceStrstrState state) + throws UnsupportedEncodingException { + FuzzerCallbacksOptimizedCritical.traceStrstrJava(state.haystack, state.needle, state.pc); + } + + @Benchmark + public void traceStrstrOptimizedJavaNonCritical(TraceStrstrState state) + throws UnsupportedEncodingException { + FuzzerCallbacksOptimizedNonCritical.traceStrstrJava(state.haystack, state.needle, state.pc); + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java new file mode 100644 index 00000000..1c09e9ad --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java @@ -0,0 +1,46 @@ +// 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; +import java.io.UnsupportedEncodingException; + +/** + * Optimized implementations of the libFuzzer callbacks that do rely on the deprecated + * CriticalJNINatives feature. Methods with `Java` in their name implement some parts in Java. + */ +public final class FuzzerCallbacksOptimizedCritical { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksOptimizedCritical.class); + } + + static native void traceCmpInt(int arg1, int arg2, int pc); + + static native void traceSwitch(long val, long[] cases, int pc); + + static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + + static void traceStrstrJava(String haystack, String needle, int pc) + throws UnsupportedEncodingException { + // 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. + traceStrstrInternal(needle.substring(0, Math.min(needle.length(), 64)).getBytes("CESU8"), pc); + } + + private static native void traceStrstrInternal(byte[] needle, int pc); +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java new file mode 100644 index 00000000..25fad3bf --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java @@ -0,0 +1,46 @@ +// 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; +import java.io.UnsupportedEncodingException; + +/** + * Optimized implementations of the libFuzzer callbacks that do not rely on the deprecated + * CriticalJNINatives feature. Methods with `Java` in their name implement some parts in Java. + */ +public final class FuzzerCallbacksOptimizedNonCritical { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksOptimizedNonCritical.class); + } + + static native void traceSwitch(long val, long[] cases, int pc); + + static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + + static native void traceStrstr(String s1, String s2, int pc); + + static void traceStrstrJava(String haystack, String needle, int pc) + throws UnsupportedEncodingException { + // 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. + traceStrstrInternal(needle.substring(0, Math.min(needle.length(), 64)).getBytes("CESU8"), pc); + } + + private static native void traceStrstrInternal(byte[] needle, int pc); +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java new file mode 100644 index 00000000..ce3d6290 --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java @@ -0,0 +1,59 @@ +// 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; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import jdk.incubator.foreign.CLinker; +import jdk.incubator.foreign.FunctionDescriptor; +import jdk.incubator.foreign.MemoryAddress; +import jdk.incubator.foreign.MemoryLayout; +import jdk.incubator.foreign.MemorySegment; +import jdk.incubator.foreign.ResourceScope; +import jdk.incubator.foreign.SymbolLookup; + +/** + * Pure-Java implementation of the fuzzer callbacks backed by Project Panama (requires JDK 16+). + * To include the implementation in the benchmark on a supported JDK, uncomment the relevant lines + * in BUILD.bazel. + */ +public class FuzzerCallbacksPanama { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacks.class); + } + + private static final MethodHandle traceCmp4 = CLinker.getInstance().downcallHandle( + SymbolLookup.loaderLookup().lookup("__sanitizer_cov_trace_cmp4").get(), + MethodType.methodType(void.class, int.class, int.class), + FunctionDescriptor.ofVoid(CLinker.C_INT, CLinker.C_INT)); + private static final MethodHandle traceSwitch = CLinker.getInstance().downcallHandle( + SymbolLookup.loaderLookup().lookup("__sanitizer_cov_trace_switch").get(), + MethodType.methodType(void.class, long.class, MemoryAddress.class), + FunctionDescriptor.ofVoid(CLinker.C_LONG, CLinker.C_POINTER)); + + static void traceCmpInt(int arg1, int arg2, int pc) throws Throwable { + traceCmp4.invokeExact(arg1, arg2); + } + + static void traceCmpSwitch(long val, long[] cases, int pc) throws Throwable { + try (ResourceScope scope = ResourceScope.newConfinedScope()) { + MemorySegment nativeCopy = MemorySegment.allocateNative( + MemoryLayout.sequenceLayout(cases.length, CLinker.C_LONG), scope); + nativeCopy.copyFrom(MemorySegment.ofArray(cases)); + traceSwitch.invokeExact(val, nativeCopy.address()); + } + } +} diff --git a/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java new file mode 100644 index 00000000..21f416cf --- /dev/null +++ b/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java @@ -0,0 +1,31 @@ +// 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; + +/** + * Unoptimized implementation of the libFuzzer callbacks that use the trampoline construction to + * inject fake PCs. + */ +public final class FuzzerCallbacksWithPc { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksWithPc.class); + } + + static native void traceCmpInt(int arg1, int arg2, int pc); + + static native void traceSwitch(long val, long[] cases, int pc); +} |