diff options
Diffstat (limited to 'agent/src/test')
23 files changed, 811 insertions, 89 deletions
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java index 66a85db6..59ef238d 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.fail; import java.util.Arrays; import java.util.Collections; +import org.junit.BeforeClass; import org.junit.Test; public class AutofuzzTest { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel index 9192ff77..f2537b73 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel @@ -16,6 +16,7 @@ java_test( ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", "@maven//:junit_junit", ], ) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 0615e9ae..0906d1d5 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -22,19 +22,13 @@ import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; import java.io.ByteArrayInputStream; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; +import java.util.Map; import org.junit.Test; public class MetaTest { - public static boolean isFive(int arg) { - return arg == 5; - } - - public static boolean intEquals(int arg1, int arg2) { - return arg1 == arg2; - } - public enum TestEnum { FOO, BAR, @@ -42,7 +36,7 @@ public class MetaTest { } @Test - public void testConsume() { + public void testConsume() throws NoSuchMethodException { consumeTestCase(5, "5", Collections.singletonList(5)); consumeTestCase((short) 5, "(short) 5", Collections.singletonList((short) 5)); consumeTestCase(5L, "5L", Collections.singletonList(5L)); @@ -121,6 +115,52 @@ public class MetaTest { consumeTestCase(YourAverageJavaClass.class, "com.code_intelligence.jazzer.autofuzz.YourAverageJavaClass.class", Collections.singletonList((byte) 1)); + + Type stringStringMapType = + MetaTest.class.getDeclaredMethod("returnsStringStringMap").getGenericReturnType(); + Map<String, String> expectedMap = + java.util.stream.Stream + .of(new java.util.AbstractMap.SimpleEntry<>("key0", "value0"), + new java.util.AbstractMap.SimpleEntry<>("key1", "value1"), + new java.util.AbstractMap.SimpleEntry<>("key2", (java.lang.String) null)) + .collect(java.util.HashMap::new, + (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll); + consumeTestCase(stringStringMapType, expectedMap, + "java.util.stream.Stream.<java.util.AbstractMap.SimpleEntry<java.lang.String, java.lang.String>>of(new java.util.AbstractMap.SimpleEntry<>(\"key0\", \"value0\"), new java.util.AbstractMap.SimpleEntry<>(\"key1\", \"value1\"), new java.util.AbstractMap.SimpleEntry<>(\"key2\", (java.lang.String) null)).collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)", + Arrays.asList((byte) 1, // do not return null for the map + 32, // remaining bytes + (byte) 1, // do not return null for the string + 31, // remaining bytes + "key0", + (byte) 1, // do not return null for the string + 28, // remaining bytes + "value0", + 28, // remaining bytes + 28, // consumeArrayLength + (byte) 1, // do not return null for the string + 27, // remaining bytes + "key1", + (byte) 1, // do not return null for the string + 23, // remaining bytes + "value1", + (byte) 1, // do not return null for the string + 27, // remaining bytes + "key2", + (byte) 0 // *do* return null for the string + )); + } + + private Map<String, String> returnsStringStringMap() { + throw new IllegalStateException( + "Should not be called, only exists to construct its generic return type"); + } + + public static boolean isFive(int arg) { + return arg == 5; + } + + public static boolean intEquals(int arg1, int arg2) { + return arg1 == arg2; } @Test @@ -129,7 +169,7 @@ public class MetaTest { MetaTest.class.getMethod("isFive", int.class), Collections.singletonList(5)); autofuzzTestCase(false, "com.code_intelligence.jazzer.autofuzz.MetaTest.intEquals(5, 4)", MetaTest.class.getMethod("intEquals", int.class, int.class), Arrays.asList(5, 4)); - autofuzzTestCase("foobar", "\"foo\".concat(\"bar\")", + autofuzzTestCase("foobar", "(\"foo\").concat(\"bar\")", String.class.getMethod("concat", String.class), Arrays.asList((byte) 1, 6, "foo", (byte) 1, 6, "bar")); autofuzzTestCase("jazzer", "new java.lang.String(\"jazzer\")", diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java index 52f19a74..d556beb3 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java @@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.List; class TestHelpers { @@ -57,8 +58,7 @@ class TestHelpers { } static void consumeTestCase( - Class<?> type, Object expectedResult, String expectedResultString, List<Object> cannedData) { - assertTrue(expectedResult == null || type.isAssignableFrom(expectedResult.getClass())); + Type type, Object expectedResult, String expectedResultString, List<Object> cannedData) { AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData); assertGeneralEquals(expectedResult, Meta.consume(data, type, visitor)); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt index 53efd200..c5a2e156 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt @@ -14,11 +14,14 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File private fun applyAfterHooks(bytecode: ByteArray): ByteArray { - return HookInstrumentor(loadHooks(AfterHooks::class.java), false).instrument(bytecode) + val hooks = Hooks.loadHooks(setOf(AfterHooks::class.java.name)).first().hooks + return HookInstrumentor(hooks, false).instrument(bytecode) } private fun getOriginalAfterHooksTargetInstance(): AfterHooksTargetContract { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 472d2b98..036559ec 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -7,6 +7,7 @@ kt_jvm_library( "DynamicTestContract.java", "PatchTestUtils.kt", ], + visibility = ["//visibility:public"], ) wrapped_kt_jvm_test( @@ -130,6 +131,7 @@ wrapped_kt_jvm_test( size = "small", srcs = [ "ReplaceHooks.java", + "ReplaceHooksInit.java", "ReplaceHooksPatchTest.kt", "ReplaceHooksTarget.java", "ReplaceHooksTargetContract.java", diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt index 31e9733c..4fde7ee1 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt @@ -14,11 +14,14 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File private fun applyBeforeHooks(bytecode: ByteArray): ByteArray { - return HookInstrumentor(loadHooks(BeforeHooks::class.java), false).instrument(bytecode) + val hooks = Hooks.loadHooks(setOf(BeforeHooks::class.java.name)).first().hooks + return HookInstrumentor(hooks, false).instrument(bytecode) } private fun getOriginalBeforeHooksTargetInstance(): BeforeHooksTargetContract { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt index 15c88f4c..f2cf2f08 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -14,12 +14,37 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes import java.io.File import kotlin.test.assertEquals +/** + * Amends the instrumentation performed by [strategy] to call the map's public static void method + * updated() after every update to coverage counters. + */ +private fun makeTestable(strategy: EdgeCoverageStrategy): EdgeCoverageStrategy = + object : EdgeCoverageStrategy by strategy { + override fun instrumentControlFlowEdge( + mv: MethodVisitor, + edgeId: Int, + variable: Int, + coverageMapInternalClassName: String + ) { + strategy.instrumentControlFlowEdge(mv, edgeId, variable, coverageMapInternalClassName) + mv.visitMethodInsn(Opcodes.INVOKESTATIC, coverageMapInternalClassName, "updated", "()V", false) + } + } + private fun applyInstrumentation(bytecode: ByteArray): ByteArray { - return EdgeCoverageInstrumentor(0, MockCoverageMap::class.java).instrument(bytecode) + return EdgeCoverageInstrumentor( + makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy), + MockCoverageMap::class.java, + 0 + ).instrument(bytecode) } private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract { @@ -41,26 +66,34 @@ private fun assertControlFlow(expectedLocations: List<Int>) { assertEquals(expectedLocations, MockCoverageMap.locations.toList()) } +@Suppress("unused") class CoverageInstrumentationTest { private val constructorReturn = 0 - private val ifFirstBranch = 1 - @Suppress("unused") - private val ifSecondBranch = 2 - private val ifEnd = 3 - private val outerForCondition = 4 - private val innerForBodyIfFirstRun = 6 - private val innerForBodyIfSecondRun = 5 - private val innerForIncrementCounter = 7 - private val outerForIncrementCounter = 8 - private val afterFooInvocation = 9 - private val beforeReturn = 10 - private val fooAfterBarInvocation = 11 - private val fooBeforeReturn = 12 - private val barAfterMapPutInvocation = 13 - private val barBeforeReturn = 14 - @Suppress("unused") - private val bazReturn = 15 + + private val mapConstructor = 1 + private val addFor0 = 2 + private val addFor1 = 3 + private val addFor2 = 4 + private val addFor3 = 5 + private val addFor4 = 6 + private val addFoobar = 7 + + private val ifTrueBranch = 8 + private val addBlock1 = 9 + private val ifFalseBranch = 10 + private val ifEnd = 11 + + private val outerForCondition = 12 + private val innerForCondition = 13 + private val innerForBodyIfTrueBranch = 14 + private val innerForBodyIfFalseBranch = 15 + private val innerForBodyPutInvocation = 16 + private val outerForIncrementCounter = 17 + + private val afterFooInvocation = 18 + private val fooAfterBarInvocation = 19 + private val barAfterPutInvocation = 20 @Test fun testOriginal() { @@ -72,31 +105,32 @@ class CoverageInstrumentationTest { MockCoverageMap.clear() assertSelfCheck(getInstrumentedInstrumentationTargetInstance()) - val innerForFirstRunControlFlow = mutableListOf<Int>().apply { + val mapControlFlow = listOf(mapConstructor, addFor0, addFor1, addFor2, addFor3, addFor4, addFoobar) + val ifControlFlow = listOf(ifTrueBranch, addBlock1, ifEnd) + val forFirstRunControlFlow = mutableListOf<Int>().apply { + add(outerForCondition) repeat(5) { - addAll(listOf(innerForBodyIfFirstRun, innerForIncrementCounter)) + addAll(listOf(innerForCondition, innerForBodyIfFalseBranch, innerForBodyPutInvocation)) } + add(outerForIncrementCounter) }.toList() - val innerForSecondRunControlFlow = mutableListOf<Int>().apply { + val forSecondRunControlFlow = mutableListOf<Int>().apply { + add(outerForCondition) repeat(5) { - addAll(listOf(innerForBodyIfSecondRun, innerForIncrementCounter)) + addAll(listOf(innerForCondition, innerForBodyIfTrueBranch, innerForBodyPutInvocation)) } + add(outerForIncrementCounter) }.toList() - val outerForControlFlow = - listOf(outerForCondition) + - innerForFirstRunControlFlow + - listOf(outerForIncrementCounter, outerForCondition) + - innerForSecondRunControlFlow + - listOf(outerForIncrementCounter) - + val forControlFlow = forFirstRunControlFlow + forSecondRunControlFlow + val fooCallControlFlow = listOf( + barAfterPutInvocation, fooAfterBarInvocation, afterFooInvocation + ) assertControlFlow( - listOf(constructorReturn, ifFirstBranch, ifEnd) + - outerForControlFlow + - listOf( - barAfterMapPutInvocation, barBeforeReturn, - fooAfterBarInvocation, fooBeforeReturn, - afterFooInvocation, beforeReturn - ) + listOf(constructorReturn) + + mapControlFlow + + ifControlFlow + + forControlFlow + + fooCallControlFlow ) } @@ -109,17 +143,17 @@ class CoverageInstrumentationTest { // The constructor of the target is run only once. val takenOnceEdge = constructorReturn // Control flows through the first if branch once per run. - val takenOnEveryRunEdge = ifFirstBranch + val takenOnEveryRunEdge = ifTrueBranch var lastCounter = 0.toUByte() for (i in 1..600) { assertSelfCheck(target) - assertEquals(1, MockCoverageMap.mem[takenOnceEdge]) + assertEquals(1, MockCoverageMap.counters[takenOnceEdge]) // Verify that the counter increments, but is never zero. val expectedCounter = (lastCounter + 1U).toUByte().takeUnless { it == 0.toUByte() } ?: (lastCounter + 2U).toUByte() lastCounter = expectedCounter - val actualCounter = MockCoverageMap.mem[takenOnEveryRunEdge].toUByte() + val actualCounter = MockCoverageMap.counters[takenOnEveryRunEdge].toUByte() assertEquals(expectedCounter, actualCounter, "After $i runs:") } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt index 7e7c31c9..ac263dc5 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt @@ -22,7 +22,8 @@ import kotlin.test.assertFailsWith class HookValidationTest { @Test fun testValidHooks() { - assertEquals(6, loadHooks(ValidHookMocks::class.java).size) + val hooks = Hooks.loadHooks(setOf(ValidHookMocks::class.java.name)).first().hooks + assertEquals(5, hooks.size) } @Test @@ -30,7 +31,8 @@ class HookValidationTest { for (method in InvalidHookMocks::class.java.methods) { if (method.isAnnotationPresent(MethodHook::class.java)) { assertFailsWith<IllegalArgumentException>("Expected ${method.name} to be an invalid hook") { - Hook.verifyAndGetHook(method, method.declaredAnnotations.first() as MethodHook) + val methodHook = method.declaredAnnotations.first() as MethodHook + Hook.createAndVerifyHook(method, methodHook, methodHook.targetClassName) } } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java index 2723ad6e..0df349ca 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java @@ -18,6 +18,7 @@ import com.code_intelligence.jazzer.api.HookType; import com.code_intelligence.jazzer.api.MethodHook; import java.lang.invoke.MethodHandle; +@SuppressWarnings({"unused", "RedundantThrows"}) class InvalidHookMocks { @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.String", targetMethod = "equals") public static void incorrectHookIdType( @@ -45,7 +46,14 @@ class InvalidHookMocks { return true; } - @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder", + @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.System", targetMethod = "gc", + targetMethodDescriptor = "()V") + public static Object + invalidReplaceVoidMethod(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + return null; + } + + @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.StringBuilder", targetMethod = "<init>", targetMethodDescriptor = "(Ljava/lang/String;)V") public static Object invalidReturnType(MethodHandle method, Object thisObject, Object[] arguments, int hookId) @@ -58,4 +66,22 @@ class InvalidHookMocks { public static void primitiveReturnValueMustBeWrapped(MethodHandle method, String thisObject, Object[] arguments, int hookId, boolean returnValue) {} + + @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder", + targetMethod = "<init>", targetMethodDescriptor = "(Ljava/lang/String;)V") + public static void + replaceOnInitWithoutReturnType( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) throws Throwable {} + + @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder", + targetMethod = "<init>", targetMethodDescriptor = "(Ljava/lang/String;)V") + public static Object + replaceOnInitWithIncompatibleType( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) throws Throwable { + return new Object(); + } + + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals") + public static void primitiveReturnType(MethodHandle method, String thisObject, Object[] arguments, + int hookId, boolean returnValue) {} } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java index 787ea493..3ea33d19 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java @@ -20,8 +20,7 @@ import java.util.Arrays; public class MockCoverageMap { public static final int SIZE = 65536; - public static final ByteBuffer mem = ByteBuffer.allocate(SIZE); - public static int prev_location = 0; // is used in byte code directly + public static final ByteBuffer counters = ByteBuffer.allocate(SIZE); private static final ByteBuffer previous_mem = ByteBuffer.allocate(SIZE); public static ArrayList<Integer> locations = new ArrayList<>(); @@ -29,16 +28,25 @@ public class MockCoverageMap { public static void updated() { int updated_pos = -1; for (int i = 0; i < SIZE; i++) { - if (previous_mem.get(i) != mem.get(i)) { + if (previous_mem.get(i) != counters.get(i)) { updated_pos = i; } } locations.add(updated_pos); - System.arraycopy(mem.array(), 0, previous_mem.array(), 0, SIZE); + System.arraycopy(counters.array(), 0, previous_mem.array(), 0, SIZE); + } + + public static void enlargeIfNeeded(int nextId) { + // This mock coverage map is statically sized. + } + + public static void recordCoverage(int id) { + byte counter = counters.get(id); + counters.put(id, (byte) (counter == -1 ? 1 : counter + 1)); } public static void clear() { - Arrays.fill(mem.array(), (byte) 0); + Arrays.fill(counters.array(), (byte) 0); Arrays.fill(previous_mem.array(), (byte) 0); locations.clear(); } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt index f286d03f..00279c35 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt @@ -14,30 +14,40 @@ package com.code_intelligence.jazzer.instrumentor -fun classToBytecode(targetClass: Class<*>): ByteArray { - return ClassLoader - .getSystemClassLoader() - .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!! - .use { - it.readBytes() - } -} +import java.io.FileOutputStream -fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> { - return BytecodeClassLoader(name, bytecode).loadClass(name) -} +object PatchTestUtils { + @JvmStatic + fun classToBytecode(targetClass: Class<*>): ByteArray { + return ClassLoader + .getSystemClassLoader() + .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!! + .use { + it.readBytes() + } + } -/** - * A ClassLoader that dynamically loads a single specified class from byte code and delegates all other class loads to - * its own ClassLoader. - */ -class BytecodeClassLoader(val className: String, private val classBytecode: ByteArray) : - ClassLoader(BytecodeClassLoader::class.java.classLoader) { - override fun loadClass(name: String): Class<*> { - if (name != className) - return super.loadClass(name) + @JvmStatic + fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> { + return BytecodeClassLoader(name, bytecode).loadClass(name) + } + + @JvmStatic + public fun dumpBytecode(outDir: String, name: String, originalBytecode: ByteArray) { + FileOutputStream("$outDir/$name.class").use { fos -> fos.write(originalBytecode) } + } - return defineClass(className, classBytecode, 0, classBytecode.size) + /** + * A ClassLoader that dynamically loads a single specified class from byte code and delegates all other class loads to + * its own ClassLoader. + */ + class BytecodeClassLoader(val className: String, private val classBytecode: ByteArray) : + ClassLoader(BytecodeClassLoader::class.java.classLoader) { + override fun loadClass(name: String): Class<*> { + if (name != className) + return super.loadClass(name) + return defineClass(className, classBytecode, 0, classBytecode.size) + } } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java index a71e1180..7e31b77b 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java @@ -18,6 +18,7 @@ import com.code_intelligence.jazzer.api.HookType; import com.code_intelligence.jazzer.api.MethodHook; import java.lang.invoke.MethodHandle; +@SuppressWarnings("unused") public class ReplaceHooks { @MethodHook(type = HookType.REPLACE, targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget", @@ -106,4 +107,30 @@ public class ReplaceHooks { patchAbstractListGet(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { return true; } + + @MethodHook(type = HookType.REPLACE, targetClassName = "java.util.Set", targetMethod = "contains", + targetMethodDescriptor = "(Ljava/lang/Object;)Z") + public static boolean + patchSetGet(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + return true; + } + + @MethodHook(type = HookType.REPLACE, + targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksInit", + targetMethod = "<init>", targetMethodDescriptor = "()V") + public static ReplaceHooksInit + patchInit(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + // Test with subclass + return new ReplaceHooksInit() { + { initialized = true; } + }; + } + + @MethodHook(type = HookType.REPLACE, + targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksInit", + targetMethod = "<init>", targetMethodDescriptor = "(ZLjava/lang/String;)V") + public static ReplaceHooksInit + patchInitWithParams(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + return new ReplaceHooksInit(true, ""); + } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java new file mode 100644 index 00000000..da77be81 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java @@ -0,0 +1,26 @@ +// 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.instrumentor; + +public class ReplaceHooksInit { + public boolean initialized; + + public ReplaceHooksInit() {} + + @SuppressWarnings("unused") + public ReplaceHooksInit(boolean initialized, String ignored) { + this.initialized = initialized; + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt index 76fb53e5..b6266d12 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt @@ -14,11 +14,14 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File private fun applyReplaceHooks(bytecode: ByteArray): ByteArray { - return HookInstrumentor(loadHooks(ReplaceHooks::class.java), false).instrument(bytecode) + val hooks = Hooks.loadHooks(setOf(ReplaceHooks::class.java.name)).first().hooks + return HookInstrumentor(hooks, false).instrument(bytecode) } private fun getOriginalReplaceHooksTargetInstance(): ReplaceHooksTargetContract { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java index 7a4b89f8..fadbdf80 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java @@ -15,9 +15,9 @@ package com.code_intelligence.jazzer.instrumentor; import java.security.SecureRandom; -import java.util.AbstractList; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; // selfCheck() only passes with the hooks in ReplaceHooks.java applied. @@ -56,10 +56,16 @@ public class ReplaceHooksTarget implements ReplaceHooksTargetContract { shouldCallPass(); } - AbstractList<Boolean> boolList = new ArrayList<>(); + ArrayList<Boolean> boolList = new ArrayList<>(); boolList.add(false); results.put("arrayListGet", boolList.get(0)); + HashSet<Boolean> boolSet = new HashSet<>(); + results.put("stringSetGet", boolSet.contains(Boolean.TRUE)); + + results.put("shouldInitialize", new ReplaceHooksInit().initialized); + results.put("shouldInitializeWithParams", new ReplaceHooksInit(false, "foo").initialized); + return results; } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java index 48f16e60..d8e28881 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java @@ -37,6 +37,7 @@ public class TraceDataFlowInstrumentationTarget implements DynamicTestContract { volatile int switchValue = 1200; + @SuppressWarnings("ReturnValueIgnored") @Override public Map<String, Boolean> selfCheck() { Map<String, Boolean> results = new HashMap<>(); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt index c6fd218f..4d4b0318 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt @@ -14,6 +14,8 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java index 06bed141..a919242b 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java @@ -27,10 +27,6 @@ class ValidHookMocks { public static void validAfterHook(MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {} - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals") - public static void validAfterHook2(MethodHandle method, String thisObject, Object[] arguments, - int hookId, boolean returnValue) {} - @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String", targetMethod = "equals", targetMethodDescriptor = "(Ljava/lang/Object;)Z") public static Boolean diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel new file mode 100644 index 00000000..97ac4f62 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -0,0 +1,38 @@ +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") + +java_test( + name = "FuzzedDataProviderImplTest", + srcs = ["FuzzedDataProviderImplTest.java"], + use_testrunner = False, + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", + ], +) + +java_test( + name = "RecordingFuzzedDataProviderTest", + srcs = [ + "RecordingFuzzedDataProviderTest.java", + ], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + "@maven//:junit_junit", + ], +) + +java_test( + name = "TraceCmpHooksTest", + srcs = [ + "TraceCmpHooksTest.java", + ], + target_compatible_with = SKIP_ON_WINDOWS, + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", + "@maven//:junit_junit", + ], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java new file mode 100644 index 00000000..5e922fc0 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java @@ -0,0 +1,225 @@ +// 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.FuzzedDataProvider; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class FuzzedDataProviderImplTest { + public static void main(String[] args) { + try (FuzzedDataProviderImpl fuzzedDataProvider = + FuzzedDataProviderImpl.withJavaData(INPUT_BYTES)) { + verifyFuzzedDataProvider(fuzzedDataProvider); + } + } + + private strictfp static void verifyFuzzedDataProvider(FuzzedDataProvider data) { + assertEqual(true, data.consumeBoolean()); + + assertEqual((byte) 0x7F, data.consumeByte()); + assertEqual((byte) 0x14, data.consumeByte((byte) 0x12, (byte) 0x22)); + + assertEqual(0x12345678, data.consumeInt()); + assertEqual(-0x12345600, data.consumeInt(-0x12345678, -0x12345600)); + assertEqual(0x12345679, data.consumeInt(0x12345678, 0x12345679)); + + assertEqual(true, Arrays.equals(new byte[] {0x01, 0x02}, data.consumeBytes(2))); + + assertEqual("jazzer", data.consumeString(6)); + assertEqual("ja\u0000zer", data.consumeString(6)); + assertEqual("€ß", data.consumeString(2)); + + assertEqual("jazzer", data.consumeAsciiString(6)); + assertEqual("ja\u0000zer", data.consumeAsciiString(6)); + assertEqual("\u0062\u0002\u002C\u0043\u001F", data.consumeAsciiString(5)); + + assertEqual(true, + Arrays.equals(new boolean[] {false, false, true, false, true}, data.consumeBooleans(5))); + assertEqual(true, + Arrays.equals(new long[] {0x0123456789abdcefL, 0xfedcba9876543210L}, data.consumeLongs(2))); + + assertEqual((float) 0.28969181, data.consumeProbabilityFloat()); + assertEqual(0.086814121166605432, data.consumeProbabilityDouble()); + assertEqual((float) 0.30104411, data.consumeProbabilityFloat()); + assertEqual(0.96218831486039413, data.consumeProbabilityDouble()); + + assertEqual((float) -2.8546307e+38, data.consumeRegularFloat()); + assertEqual(8.0940194040236032e+307, data.consumeRegularDouble()); + assertEqual((float) 271.49084, data.consumeRegularFloat((float) 123.0, (float) 777.0)); + assertEqual(30.859126145478349, data.consumeRegularDouble(13.37, 31.337)); + + assertEqual((float) 0.0, data.consumeFloat()); + assertEqual((float) -0.0, data.consumeFloat()); + assertEqual(Float.POSITIVE_INFINITY, data.consumeFloat()); + assertEqual(Float.NEGATIVE_INFINITY, data.consumeFloat()); + assertEqual(true, Float.isNaN(data.consumeFloat())); + assertEqual(Float.MIN_VALUE, data.consumeFloat()); + assertEqual(-Float.MIN_VALUE, data.consumeFloat()); + assertEqual(Float.MIN_NORMAL, data.consumeFloat()); + assertEqual(-Float.MIN_NORMAL, data.consumeFloat()); + assertEqual(Float.MAX_VALUE, data.consumeFloat()); + assertEqual(-Float.MAX_VALUE, data.consumeFloat()); + + assertEqual(0.0, data.consumeDouble()); + assertEqual(-0.0, data.consumeDouble()); + assertEqual(Double.POSITIVE_INFINITY, data.consumeDouble()); + assertEqual(Double.NEGATIVE_INFINITY, data.consumeDouble()); + assertEqual(true, Double.isNaN(data.consumeDouble())); + assertEqual(Double.MIN_VALUE, data.consumeDouble()); + assertEqual(-Double.MIN_VALUE, data.consumeDouble()); + assertEqual(Double.MIN_NORMAL, data.consumeDouble()); + assertEqual(-Double.MIN_NORMAL, data.consumeDouble()); + assertEqual(Double.MAX_VALUE, data.consumeDouble()); + assertEqual(-Double.MAX_VALUE, data.consumeDouble()); + + int[] array = {0, 1, 2, 3, 4}; + assertEqual(4, data.pickValue(array)); + assertEqual(2, (int) data.pickValue(Arrays.stream(array).boxed().toArray())); + assertEqual(3, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toList()))); + assertEqual(2, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toSet()))); + + // Buffer is almost depleted at this point. + assertEqual(7, data.remainingBytes()); + assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(3))); + assertEqual(7, data.remainingBytes()); + assertEqual(true, Arrays.equals(new int[] {0x12345678}, data.consumeInts(3))); + assertEqual(3, data.remainingBytes()); + assertEqual(0x123456L, data.consumeLong()); + + // Buffer has been fully consumed at this point + assertEqual(0, data.remainingBytes()); + assertEqual(0, data.consumeInt()); + assertEqual(0.0, data.consumeDouble()); + assertEqual(-13.37, data.consumeRegularDouble(-13.37, 31.337)); + assertEqual(true, Arrays.equals(new byte[0], data.consumeBytes(4))); + assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(4))); + assertEqual("", data.consumeRemainingAsAsciiString()); + assertEqual("", data.consumeRemainingAsString()); + assertEqual("", data.consumeAsciiString(100)); + assertEqual("", data.consumeString(100)); + } + + private static <T extends Comparable<T>> void assertEqual(T a, T b) { + if (a.compareTo(b) != 0) { + throw new IllegalArgumentException("Expected: " + a + ", got: " + b); + } + } + + private static final byte[] INPUT_BYTES = new byte[] { + // Bytes read from the start + 0x01, 0x02, // consumeBytes(2): {0x01, 0x02} + + 'j', 'a', 'z', 'z', 'e', 'r', // consumeString(6): "jazzer" + 'j', 'a', 0x00, 'z', 'e', 'r', // consumeString(6): "ja\u0000zer" + (byte) 0xE2, (byte) 0x82, (byte) 0xAC, (byte) 0xC3, (byte) 0x9F, // consumeString(2): "€ẞ" + + 'j', 'a', 'z', 'z', 'e', 'r', // consumeAsciiString(6): "jazzer" + 'j', 'a', 0x00, 'z', 'e', 'r', // consumeAsciiString(6): "ja\u0000zer" + (byte) 0xE2, (byte) 0x82, (byte) 0xAC, (byte) 0xC3, + (byte) 0x9F, // consumeAsciiString(5): "\u0062\u0002\u002C\u0043\u001F" + + 0, 0, 1, 0, 1, // consumeBooleans(5): { false, false, true, false, true } + (byte) 0xEF, (byte) 0xDC, (byte) 0xAB, (byte) 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, + 0x76, (byte) 0x98, (byte) 0xBA, (byte) 0xDC, (byte) 0xFE, + // consumeLongs(2): { 0x0123456789ABCDEF, 0xFEDCBA9876543210 } + + 0x78, 0x56, 0x34, 0x12, // consumeInts(3): { 0x12345678 } + 0x56, 0x34, 0x12, // consumeLong(): + + // Bytes read from the end + 0x02, 0x03, 0x02, 0x04, // 4x pickValue in array with five elements + + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 10, // -max for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 9, // max for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 8, // -min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 7, // min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 6, // -denorm_min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 5, // denorm_min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 4, // NaN for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 3, // -infinity for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 2, // infinity for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 1, // -0.0 for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 0, // 0.0 for next consumeDouble + + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 10, // -max for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 9, // max for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 8, // -min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 7, // min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 6, // -denorm_min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 5, // denorm_min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 4, // NaN for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 3, // -infinity for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 2, // infinity for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 1, // -0.0 for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 0, // 0.0 for next consumeFloat + + (byte) 0x88, (byte) 0xAB, 0x61, (byte) 0xCB, 0x32, (byte) 0xEB, 0x30, (byte) 0xF9, + // consumeDouble(13.37, 31.337): 30.859126145478349 (small range) + 0x51, (byte) 0xF6, 0x1F, 0x3A, // consumeFloat(123.0, 777.0): 271.49084 (small range) + 0x11, 0x4D, (byte) 0xFD, 0x54, (byte) 0xD6, 0x3D, 0x43, 0x73, 0x39, + // consumeRegularDouble(): 8.0940194040236032e+307 + 0x16, (byte) 0xCF, 0x3D, 0x29, 0x4A, // consumeRegularFloat(): -2.8546307e+38 + + 0x61, (byte) 0xCB, 0x32, (byte) 0xEB, 0x30, (byte) 0xF9, 0x51, (byte) 0xF6, + // consumeProbabilityDouble(): 0.96218831486039413 + 0x1F, 0x3A, 0x11, 0x4D, // consumeProbabilityFloat(): 0.30104411 + (byte) 0xFD, 0x54, (byte) 0xD6, 0x3D, 0x43, 0x73, 0x39, 0x16, + // consumeProbabilityDouble(): 0.086814121166605432 + (byte) 0xCF, 0x3D, 0x29, 0x4A, // consumeProbabilityFloat(): 0.28969181 + + 0x01, // consumeInt(0x12345678, 0x12345679): 0x12345679 + 0x78, // consumeInt(-0x12345678, -0x12345600): -0x12345600 + 0x78, 0x56, 0x34, 0x12, // consumeInt(): 0x12345678 + + 0x02, // consumeByte(0x12, 0x22): 0x14 + 0x7F, // consumeByte(): 0x7F + + 0x01, // consumeBool(): true + }; +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java new file mode 100644 index 00000000..d58a5ca9 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java @@ -0,0 +1,214 @@ +// 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.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.IOException; +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import org.junit.Assert; +import org.junit.Test; + +public class RecordingFuzzedDataProviderTest { + @Test + public void testRecordingFuzzedDataProvider() throws IOException { + FuzzedDataProvider mockData = new MockFuzzedDataProvider(); + String referenceResult = sampleFuzzTarget(mockData); + + FuzzedDataProvider recordingMockData = + RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(mockData); + Assert.assertEquals(referenceResult, sampleFuzzTarget(recordingMockData)); + + String cannedMockDataString = + RecordingFuzzedDataProvider.serializeFuzzedDataProviderProxy(recordingMockData); + FuzzedDataProvider cannedMockData = new CannedFuzzedDataProvider(cannedMockDataString); + Assert.assertEquals(referenceResult, sampleFuzzTarget(cannedMockData)); + } + + private String sampleFuzzTarget(FuzzedDataProvider data) { + StringBuilder result = new StringBuilder(); + result.append(data.consumeString(10)); + int[] ints = data.consumeInts(5); + result.append(Arrays.stream(ints).mapToObj(Integer::toString).collect(Collectors.joining(","))); + result.append(data.pickValue(ints)); + result.append(data.consumeString(20)); + result.append(data.pickValues(Arrays.stream(ints).boxed().collect(Collectors.toSet()), 5) + .stream() + .map(Integer::toHexString) + .collect(Collectors.joining(","))); + result.append(data.remainingBytes()); + return result.toString(); + } + + private static final class MockFuzzedDataProvider implements FuzzedDataProvider { + @Override + public boolean consumeBoolean() { + return true; + } + + @Override + public boolean[] consumeBooleans(int maxLength) { + return new boolean[] {false, true}; + } + + @Override + public byte consumeByte() { + return 2; + } + + @Override + public byte consumeByte(byte min, byte max) { + return max; + } + + @Override + public short consumeShort() { + return 2; + } + + @Override + public short consumeShort(short min, short max) { + return min; + } + + @Override + public short[] consumeShorts(int maxLength) { + return new short[] {2, 4, 7}; + } + + @Override + public int consumeInt() { + return 5; + } + + @Override + public int consumeInt(int min, int max) { + return max; + } + + @Override + public int[] consumeInts(int maxLength) { + return IntStream.range(0, maxLength).toArray(); + } + + @Override + public long consumeLong() { + return 42; + } + + @Override + public long consumeLong(long min, long max) { + return min; + } + + @Override + public long[] consumeLongs(int maxLength) { + return LongStream.range(0, maxLength).toArray(); + } + + @Override + public float consumeFloat() { + return Float.NaN; + } + + @Override + public float consumeRegularFloat() { + return 0.3f; + } + + @Override + public float consumeRegularFloat(float min, float max) { + return min; + } + + @Override + public float consumeProbabilityFloat() { + return 0.2f; + } + + @Override + public double consumeDouble() { + return Double.NaN; + } + + @Override + public double consumeRegularDouble(double min, double max) { + return max; + } + + @Override + public double consumeRegularDouble() { + return Math.PI; + } + + @Override + public double consumeProbabilityDouble() { + return 0.5; + } + + @Override + public char consumeChar() { + return 'C'; + } + + @Override + public char consumeChar(char min, char max) { + return min; + } + + @Override + public char consumeCharNoSurrogates() { + return 'C'; + } + + @Override + public String consumeAsciiString(int maxLength) { + return "foobar"; + } + + @Override + public String consumeString(int maxLength) { + return "foo€ä"; + } + + @Override + public String consumeRemainingAsAsciiString() { + return "foobar"; + } + + @Override + public String consumeRemainingAsString() { + return "foobar"; + } + + @Override + public byte[] consumeBytes(int maxLength) { + return new byte[maxLength]; + } + + @Override + public byte[] consumeRemainingAsBytes() { + return new byte[] {1}; + } + + @Override + public int remainingBytes() { + return 1; + } + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java new file mode 100644 index 00000000..9275ca30 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.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.runtime; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import org.junit.BeforeClass; +import org.junit.Test; + +public class TraceCmpHooksTest { + private static final ExecutorService ES = Executors.newFixedThreadPool(5); + + @Test + public void cmpHookShouldHandleConcurrentModifications() throws InterruptedException { + String arg = "test"; + Map<String, Object> map = new HashMap<>(); + map.put(arg, arg); + + // Add elements to map asynchronously + Function<Integer, Runnable> put = (final Integer num) -> () -> { + map.put(String.valueOf(num), num); + }; + for (int i = 0; i < 1_000_000; i++) { + ES.submit(put.apply(i)); + } + + // Call hook + for (int i = 0; i < 1_000; i++) { + TraceCmpHooks.mapGet(null, map, new Object[] {arg}, 1, null); + } + + ES.shutdown(); + // noinspection ResultOfMethodCallIgnored + ES.awaitTermination(5, TimeUnit.SECONDS); + } +} |