From b8e7faaa96d025cfc9b74f79de11c4face3dc276 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 17 Jan 2025 23:05:03 +0100 Subject: [PATCH 1/4] Update rules_jvm_external to fix POM Also update the direct deps that are updated as transitive deps of r_j_e to resolve warnings. --- MODULE.bazel | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 2bd6ee89e..d3581686b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,7 +6,7 @@ module(name = "jazzer") # Kept up-to-date by Renovate ################################################################################ -bazel_dep(name = "abseil-cpp", version = "20230802.0.bcr.1") +bazel_dep(name = "abseil-cpp", version = "20230802.1") bazel_dep(name = "apple_support", version = "1.11.1") bazel_dep(name = "bazel_jar_jar", version = "0.1.0") bazel_dep(name = "bazel_skylib", version = "1.7.1") @@ -18,11 +18,20 @@ bazel_dep(name = "protobuf") bazel_dep(name = "rules_android", version = "0.1.1") bazel_dep(name = "rules_android_ndk", version = "0.1.2") bazel_dep(name = "rules_foreign_cc", version = "0.11.1") -bazel_dep(name = "rules_java", version = "7.7.0") +bazel_dep(name = "rules_java", version = "7.12.2") bazel_dep(name = "rules_jni", version = "0.9.1") -bazel_dep(name = "rules_jvm_external", version = "6.2") -bazel_dep(name = "rules_kotlin", version = "1.9.5") -bazel_dep(name = "rules_license", version = "0.0.8") +bazel_dep(name = "rules_jvm_external") + +# TODO: Remove after the next release. +archive_override( + module_name = "rules_jvm_external", + integrity = "sha256-7AerLOLhQ+oIDH2id7OE8WJmbH01MqBWV4CbqJ6Nh68=", + strip_prefix = "rules_jvm_external-a1d4e4f4267c1797b686719aa385e707b732c541", + urls = ["https://github.com/bazelbuild/rules_jvm_external/archive/a1d4e4f4267c1797b686719aa385e707b732c541.tar.gz"], +) + +bazel_dep(name = "rules_kotlin", version = "1.9.6") +bazel_dep(name = "rules_license", version = "1.0.0") bazel_dep(name = "rules_pkg", version = "0.9.1") bazel_dep(name = "toolchains_llvm", version = "0.10.3") From 69bb11943fca6643f1f720558eb9b2ab6f1b901a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 17 Jan 2025 23:05:03 +0100 Subject: [PATCH 2/4] Fix format script to actually format `.kt` files --- format.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/format.sh b/format.sh index 81d27eb5d..ceeca2ef3 100755 --- a/format.sh +++ b/format.sh @@ -35,9 +35,9 @@ if [[ "${CI:-0}" == 0 ]]; then # Check which ktlint_tests failed and run the corresponding fix targets. This is much faster than # running all ktlint_fix targets when e.g. only a few or no .kt files changed. # shellcheck disable=SC2046 - TARGETS_TO_RUN=$(bazel test --config=quiet $(bazel query --config=quiet 'kind(ktlint_test, //...)') | { grep FAILED || true; } | cut -f1 -d' ' | sed -e 's/:ktlint_test/:ktlint_fix/g') + TARGETS_TO_RUN=$(bazel test --config=quiet $(bazel query --config=quiet 'kind(ktlint_test, //...)') | { grep FAILED || true; } | cut -f1 -d' ' | sed -e 's/:ktlint_test/:ktlint_fix/g' || true) if [[ -n "${TARGETS_TO_RUN}" ]]; then - echo "$TARGETS_TO_RUN" | xargs -n 1 bazel run --config=quiet + echo "$TARGETS_TO_RUN" | xargs -I '{}' -n 1 bazel run --config=quiet {} -- --format fi # BUILD files From 15867525b5d8196ae3d36175cd74bae54db7dd5a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sat, 18 Jan 2025 11:49:39 +0100 Subject: [PATCH 3/4] Run `./format.sh` --- .../java/com/example/ExampleKotlinFuzzer.kt | 7 +- .../ExampleKotlinValueProfileFuzzer.kt | 5 +- .../src/main/java/com/example/KlaxonFuzzer.kt | 1 - .../jazzer/sanitizers/Deserialization.kt | 19 +- .../sanitizers/ExpressionLanguageInjection.kt | 9 +- .../jazzer/sanitizers/LdapInjection.kt | 9 +- .../jazzer/sanitizers/NamingContextLookup.kt | 8 +- .../jazzer/sanitizers/OsCommandInjection.kt | 12 +- .../jazzer/sanitizers/ReflectiveCall.kt | 68 ++++- .../jazzer/sanitizers/RegexInjection.kt | 25 +- .../jazzer/sanitizers/Utils.kt | 16 +- .../jazzer/sanitizers/XPathInjection.kt | 12 +- .../instrumentor/DirectByteBufferStrategy.kt | 7 +- .../code_intelligence/jazzer/agent/Agent.kt | 166 ++++++------ .../jazzer/agent/CoverageIdStrategy.kt | 69 +++-- .../jazzer/agent/RuntimeInstrumentor.kt | 103 ++++--- .../jazzer/driver/ExceptionUtils.kt | 116 ++++---- .../jazzer/instrumentor/ClassInstrumentor.kt | 38 +-- .../jazzer/instrumentor/CoverageRecorder.kt | 126 +++++---- .../jazzer/instrumentor/DescriptorUtils.kt | 45 ++-- .../instrumentor/DeterministicRandom.kt | 22 +- .../instrumentor/EdgeCoverageInstrumentor.kt | 68 +++-- .../jazzer/instrumentor/Hook.kt | 82 +++--- .../jazzer/instrumentor/HookInstrumentor.kt | 55 ++-- .../jazzer/instrumentor/HookMethodVisitor.kt | 156 ++++++----- .../jazzer/instrumentor/Hooks.kt | 87 +++--- .../jazzer/instrumentor/Instrumentor.kt | 15 +- .../instrumentor/TraceDataFlowInstrumentor.kt | 251 ++++++++++-------- .../jazzer/utils/ClassNameGlobber.kt | 87 +++--- .../jazzer/utils/ManifestUtils.kt | 15 +- .../jazzer/utils/SimpleGlobMatcher.kt | 4 +- .../code_intelligence/jazzer/utils/Utils.kt | 42 +-- .../instrumentor/AfterHooksPatchTest.kt | 25 +- .../instrumentor/BeforeHooksPatchTest.kt | 25 +- .../CoverageInstrumentationTest.kt | 75 +++--- .../instrumentor/DescriptorUtilsTest.kt | 69 +++-- .../jazzer/instrumentor/PatchTestUtils.kt | 29 +- .../instrumentor/ReplaceHooksPatchTest.kt | 25 +- .../TraceDataFlowInstrumentationTest.kt | 23 +- .../com/example/KotlinStringCompareFuzzer.kt | 3 +- .../src/test/java/com/example/KotlinVararg.kt | 4 +- 41 files changed, 1169 insertions(+), 854 deletions(-) diff --git a/examples/src/main/java/com/example/ExampleKotlinFuzzer.kt b/examples/src/main/java/com/example/ExampleKotlinFuzzer.kt index 556bbb38e..624df04af 100644 --- a/examples/src/main/java/com/example/ExampleKotlinFuzzer.kt +++ b/examples/src/main/java/com/example/ExampleKotlinFuzzer.kt @@ -20,13 +20,16 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium object ExampleKotlinFuzzer { - @JvmStatic fun fuzzerTestOneInput(data: FuzzedDataProvider) { exploreMe(data.consumeString(8), data.consumeInt(), data.consumeRemainingAsString()) } - private fun exploreMe(prefix: String, n: Int, suffix: String) { + private fun exploreMe( + prefix: String, + n: Int, + suffix: String, + ) { if (prefix.findAnyOf(arrayListOf("Fuzz", "Test")) != null) { if (n >= 2000000) { if (suffix.startsWith("@")) { diff --git a/examples/src/main/java/com/example/ExampleKotlinValueProfileFuzzer.kt b/examples/src/main/java/com/example/ExampleKotlinValueProfileFuzzer.kt index 536c8096e..f6d337784 100644 --- a/examples/src/main/java/com/example/ExampleKotlinValueProfileFuzzer.kt +++ b/examples/src/main/java/com/example/ExampleKotlinValueProfileFuzzer.kt @@ -20,7 +20,6 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium object ExampleKotlinValueProfileFuzzer { - @JvmStatic fun fuzzerTestOneInput(data: FuzzedDataProvider) { if (data.consumeInt().compareTo(0x11223344) != 0) { @@ -33,7 +32,5 @@ object ExampleKotlinValueProfileFuzzer { } } - private fun encrypt(n: Long): Long { - return n.xor(0x1122334455667788) - } + private fun encrypt(n: Long): Long = n.xor(0x1122334455667788) } diff --git a/examples/src/main/java/com/example/KlaxonFuzzer.kt b/examples/src/main/java/com/example/KlaxonFuzzer.kt index eb4633171..5f15be3dc 100644 --- a/examples/src/main/java/com/example/KlaxonFuzzer.kt +++ b/examples/src/main/java/com/example/KlaxonFuzzer.kt @@ -22,7 +22,6 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider // Reproduces https://github.com/cbeust/klaxon/pull/330 object KlaxonFuzzer { - @JvmStatic fun fuzzerTestOneInput(data: FuzzedDataProvider) { try { diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt index bd35ba3ef..d864a164d 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt @@ -33,7 +33,6 @@ import java.util.WeakHashMap */ @Suppress("unused_parameter", "unused") object Deserialization { - private val OBJECT_INPUT_STREAM_HEADER = ObjectStreamConstants.STREAM_MAGIC.toBytes() + ObjectStreamConstants.STREAM_VERSION.toBytes() @@ -88,13 +87,19 @@ object Deserialization { targetMethodDescriptor = "(Ljava/io/InputStream;)V", ) @JvmStatic - fun objectInputStreamInitBeforeHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + fun objectInputStreamInitBeforeHook( + method: MethodHandle?, + alwaysNull: Any?, + args: Array, + hookId: Int, + ) { val originalInputStream = args[0] as? InputStream ?: return - val fixedInputStream = if (originalInputStream.markSupported()) { - originalInputStream - } else { - BufferedInputStream(originalInputStream) - } + val fixedInputStream = + if (originalInputStream.markSupported()) { + originalInputStream + } else { + BufferedInputStream(originalInputStream) + } args[0] = fixedInputStream guideMarkableInputStreamTowardsEquality(fixedInputStream, OBJECT_INPUT_STREAM_HEADER, hookId) } diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt index 3de0dc299..0dbb16794 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt @@ -28,7 +28,6 @@ import java.lang.invoke.MethodHandle */ @Suppress("unused_parameter", "unused") object ExpressionLanguageInjection { - /** * Try to call the default constructor of the honeypot class. */ @@ -71,7 +70,9 @@ object ExpressionLanguageInjection { hookId: Int, ) { // The overloads taking a second string argument have either three or four arguments - if (arguments.size < 3) { return } + if (arguments.size < 3) { + return + } val expression = arguments[1] as? String ?: return Jazzer.guideTowardsContainment(expression, EXPRESSION_LANGUAGE_ATTACK, hookId) } @@ -95,7 +96,9 @@ object ExpressionLanguageInjection { arguments: Array, hookId: Int, ) { - if (arguments.size != 1) { return } + if (arguments.size != 1) { + return + } val message = arguments[0] as String Jazzer.guideTowardsContainment(message, EXPRESSION_LANGUAGE_ATTACK, hookId) } diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt index 54db3d868..087c93014 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt @@ -44,7 +44,6 @@ import javax.naming.directory.InvalidSearchFilterException */ @Suppress("unused_parameter", "unused") object LdapInjection { - // Characters to escape in DNs private const val NAME_CHARACTERS = "\\+<>,;\"=" @@ -67,7 +66,6 @@ object LdapInjection { targetMethodDescriptor = "(Ljava/lang/String;Ljavax/naming.directory/Attributes;[Ljava/lang/Sting;)Ljavax/naming/NamingEnumeration;", additionalClassesToHook = ["javax.naming.directory.InitialDirContext"], ), - // Object search, possible DN and search filter injection MethodHook( type = HookType.REPLACE, @@ -92,7 +90,12 @@ object LdapInjection { ), ) @JvmStatic - fun searchLdapContext(method: MethodHandle, thisObject: Any?, args: Array, hookId: Int): Any? { + fun searchLdapContext( + method: MethodHandle, + thisObject: Any?, + args: Array, + hookId: Int, + ): Any? { try { return method.invokeWithArguments(thisObject, *args).also { (args[0] as? String)?.let { name -> diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt index 586acb426..b297607ab 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt @@ -26,7 +26,6 @@ import javax.naming.CommunicationException @Suppress("unused") object NamingContextLookup { - // The particular URL g.co is used here since it is: // - short, which makes it easier for the fuzzer to incorporate into the input; // - valid, which means that a `lookup` call on it could actually result in RCE; @@ -50,7 +49,12 @@ object NamingContextLookup { ), ) @JvmStatic - fun lookupHook(method: MethodHandle?, thisObject: Any?, args: Array, hookId: Int): Any { + fun lookupHook( + method: MethodHandle?, + thisObject: Any?, + args: Array, + hookId: Int, + ): Any { val name = args[0] as? String ?: throw CommunicationException() if (name.startsWith(RMI_MARKER) || name.startsWith(LDAP_MARKER)) { Jazzer.reportFindingFromHook( diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt index 5ecb08f0f..1c798fbbc 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt @@ -33,7 +33,6 @@ import java.lang.invoke.MethodHandle */ @Suppress("unused_parameter", "unused") object OsCommandInjection { - // Short and probably non-existing command name private const val COMMAND = "jazze" @@ -44,8 +43,15 @@ object OsCommandInjection { additionalClassesToHook = ["java.lang.ProcessBuilder"], ) @JvmStatic - fun processImplStartHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { - if (args.isEmpty()) { return } + fun processImplStartHook( + method: MethodHandle?, + alwaysNull: Any?, + args: Array, + hookId: Int, + ) { + if (args.isEmpty()) { + return + } // Calling ProcessBuilder already checks if command array is empty @Suppress("UNCHECKED_CAST") (args[0] as? Array)?.first().let { cmd -> diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt index 2c1db911c..91a5fbb01 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt @@ -30,25 +30,64 @@ import java.lang.invoke.MethodHandle */ @Suppress("unused_parameter", "unused") object ReflectiveCall { - @MethodHooks( - MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;"), - MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"), - MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;"), - MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/String;Z)Ljava/lang/Class;"), + MethodHook( + type = HookType.BEFORE, + targetClassName = "java.lang.Class", + targetMethod = "forName", + targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "java.lang.Class", + targetMethod = "forName", + targetMethodDescriptor = "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "java.lang.ClassLoader", + targetMethod = "loadClass", + targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "java.lang.ClassLoader", + targetMethod = "loadClass", + targetMethodDescriptor = "(Ljava/lang/String;Z)Ljava/lang/Class;", + ), ) @JvmStatic - fun loadClassHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + fun loadClassHook( + method: MethodHandle?, + alwaysNull: Any?, + args: Array, + hookId: Int, + ) { val className = args[0] as? String ?: return Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId) } @MethodHooks( - MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;"), - MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;"), + MethodHook( + type = HookType.BEFORE, + targetClassName = "java.lang.Class", + targetMethod = "forName", + targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "java.lang.ClassLoader", + targetMethod = "loadClass", + targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;", + ), ) @JvmStatic - fun loadClassWithModuleHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + fun loadClassWithModuleHook( + method: MethodHandle?, + alwaysNull: Any?, + args: Array, + hookId: Int, + ) { val className = args[1] as? String ?: return Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId) } @@ -62,8 +101,15 @@ object ReflectiveCall { MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "findLibrary"), ) @JvmStatic - fun loadLibraryHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { - if (args.isEmpty()) { return } + fun loadLibraryHook( + method: MethodHandle?, + alwaysNull: Any?, + args: Array, + hookId: Int, + ) { + if (args.isEmpty()) { + return + } val libraryName = args[0] as? String ?: return if (libraryName == HONEYPOT_LIBRARY_NAME) { Jazzer.reportFindingFromHook( diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt index d2faa11ca..19a4f5d21 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt @@ -51,7 +51,12 @@ object RegexInjection { targetMethodDescriptor = "(Ljava/lang/String;I)Ljava/util/regex/Pattern;", ) @JvmStatic - fun compileWithFlagsHook(method: MethodHandle, alwaysNull: Any?, args: Array, hookId: Int): Any? { + fun compileWithFlagsHook( + method: MethodHandle, + alwaysNull: Any?, + args: Array, + hookId: Int, + ): Any? { val pattern = args[0] as String? val hasCanonEqFlag = ((args[1] as Int) and Pattern.CANON_EQ) != 0 return hookInternal(method, pattern, hasCanonEqFlag, hookId, *args) @@ -72,9 +77,12 @@ object RegexInjection { ), ) @JvmStatic - fun patternHook(method: MethodHandle, alwaysNull: Any?, args: Array, hookId: Int): Any? { - return hookInternal(method, args[0] as String?, false, hookId, *args) - } + fun patternHook( + method: MethodHandle, + alwaysNull: Any?, + args: Array, + hookId: Int, + ): Any? = hookInternal(method, args[0] as String?, false, hookId, *args) @MethodHooks( MethodHook( @@ -109,9 +117,12 @@ object RegexInjection { ), ) @JvmStatic - fun stringHook(method: MethodHandle, thisObject: Any?, args: Array, hookId: Int): Any? { - return hookInternal(method, args[0] as String?, false, hookId, thisObject, *args) - } + fun stringHook( + method: MethodHandle, + thisObject: Any?, + args: Array, + hookId: Int, + ): Any? = hookInternal(method, args[0] as String?, false, hookId, thisObject, *args) private fun hookInternal( method: MethodHandle, diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt index 9e1beef51..592b3120c 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt @@ -25,12 +25,11 @@ import java.io.InputStream const val HONEYPOT_CLASS_NAME = "jaz.Zer" const val HONEYPOT_LIBRARY_NAME = "jazzer_honeypot" -internal fun Short.toBytes(): ByteArray { - return byteArrayOf( +internal fun Short.toBytes(): ByteArray = + byteArrayOf( ((toInt() shr 8) and 0xFF).toByte(), (toInt() and 0xFF).toByte(), ) -} // Runtime is only O(size * needle.size), only use for small arrays. internal fun ByteArray.indexOf(needle: ByteArray): Int { @@ -45,8 +44,15 @@ internal fun ByteArray.indexOf(needle: ByteArray): Int { return -1 } -internal fun guideMarkableInputStreamTowardsEquality(stream: InputStream, target: ByteArray, id: Int) { - fun readBytes(stream: InputStream, size: Int): ByteArray { +internal fun guideMarkableInputStreamTowardsEquality( + stream: InputStream, + target: ByteArray, + id: Int, +) { + fun readBytes( + stream: InputStream, + size: Int, + ): ByteArray { val current = ByteArray(size) var n = 0 while (n < size) { diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XPathInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XPathInjection.kt index cc5095cc3..af6308c75 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XPathInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XPathInjection.kt @@ -36,7 +36,6 @@ import javax.xml.xpath.XPathExpressionException */ @Suppress("unused_parameter", "unused") object XPathInjection { - // Characters that should be escaped in user input. // https://owasp.org/www-community/attacks/XPATH_Injection private const val CHARACTERS_TO_ESCAPE = "'\"" @@ -49,7 +48,12 @@ object XPathInjection { MethodHook(type = HookType.REPLACE, targetClassName = "javax.xml.xpath.XPath", targetMethod = "evaluateExpression"), ) @JvmStatic - fun checkXpathExecute(method: MethodHandle, thisObject: Any?, arguments: Array, hookId: Int): Any { + fun checkXpathExecute( + method: MethodHandle, + thisObject: Any?, + arguments: Array, + hookId: Int, + ): Any { if (arguments.isNotEmpty() && arguments[0] is String) { val query = arguments[0] as String Jazzer.guideTowardsContainment(query, CHARACTERS_TO_ESCAPE, hookId) @@ -67,8 +71,8 @@ object XPathInjection { Jazzer.reportFindingFromHook( FuzzerSecurityIssueHigh( """ - XPath Injection - Injected query: ${arguments[0]} + XPath Injection + Injected query: ${arguments[0]} """.trimIndent(), exception, ), diff --git a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt index fdb79d24d..9ef3f0abd 100644 --- a/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt +++ b/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt @@ -20,7 +20,6 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes object DirectByteBufferStrategy : EdgeCoverageStrategy { - override fun instrumentControlFlowEdge( mv: MethodVisitor, edgeId: Int, @@ -66,7 +65,11 @@ object DirectByteBufferStrategy : EdgeCoverageStrategy { override val localVariableType get() = "java/nio/ByteBuffer" - override fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) { + override fun loadLocalVariable( + mv: MethodVisitor, + variable: Int, + coverageMapInternalClassName: String, + ) { mv.apply { visitFieldInsn( Opcodes.GETSTATIC, diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 2ed7e814b..2463ff1db 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -63,55 +63,61 @@ fun installInternal( val customHookClassNameGlobber = ClassNameGlobber(customHookIncludes, customHookExcludes + customHookNames) // FIXME: Setting trace to the empty string explicitly results in all rather than no trace types // being applied - this is unintuitive. - val instrumentationTypes = (trace.takeIf { it.isNotEmpty() } ?: listOf("all")).flatMap { - when (it) { - "cmp" -> setOf(InstrumentationType.CMP) - "cov" -> setOf(InstrumentationType.COV) - "div" -> setOf(InstrumentationType.DIV) - "gep" -> setOf(InstrumentationType.GEP) - "indir" -> setOf(InstrumentationType.INDIR) - "native" -> setOf(InstrumentationType.NATIVE) - // Disable GEP instrumentation by default as it appears to negatively affect fuzzing - // performance. Our current GEP instrumentation only reports constant indices, but even - // when we instead reported non-constant indices, they tended to completely fill up the - // table of recent compares and value profile map. - "all" -> InstrumentationType.values().toSet() - InstrumentationType.GEP - else -> { - println("WARN: Skipping unknown instrumentation type $it") - emptySet() - } - } - }.toSet() + val instrumentationTypes = + (trace.takeIf { it.isNotEmpty() } ?: listOf("all")) + .flatMap { + when (it) { + "cmp" -> setOf(InstrumentationType.CMP) + "cov" -> setOf(InstrumentationType.COV) + "div" -> setOf(InstrumentationType.DIV) + "gep" -> setOf(InstrumentationType.GEP) + "indir" -> setOf(InstrumentationType.INDIR) + "native" -> setOf(InstrumentationType.NATIVE) + // Disable GEP instrumentation by default as it appears to negatively affect fuzzing + // performance. Our current GEP instrumentation only reports constant indices, but even + // when we instead reported non-constant indices, they tended to completely fill up the + // table of recent compares and value profile map. + "all" -> InstrumentationType.values().toSet() - InstrumentationType.GEP + else -> { + println("WARN: Skipping unknown instrumentation type $it") + emptySet() + } + } + }.toSet() - val idSyncFilePath = idSyncFile.takeUnless { it.isEmpty() }?.let { - Paths.get(it).also { path -> - Log.info("Synchronizing coverage IDs in ${path.toAbsolutePath()}") + val idSyncFilePath = + idSyncFile.takeUnless { it.isEmpty() }?.let { + Paths.get(it).also { path -> + Log.info("Synchronizing coverage IDs in ${path.toAbsolutePath()}") + } } - } - val dumpClassesDirPath = dumpClassesDir.takeUnless { it.isEmpty() }?.let { - Paths.get(it).toAbsolutePath().also { path -> - if (path.exists() && path.isDirectory()) { - Log.info("Dumping instrumented classes into $path") - } else { - Log.error("Cannot dump instrumented classes into $path; does not exist or not a directory") + val dumpClassesDirPath = + dumpClassesDir.takeUnless { it.isEmpty() }?.let { + Paths.get(it).toAbsolutePath().also { path -> + if (path.exists() && path.isDirectory()) { + Log.info("Dumping instrumented classes into $path") + } else { + Log.error("Cannot dump instrumented classes into $path; does not exist or not a directory") + } } } - } - val includedHookNames = instrumentationTypes - .mapNotNull { type -> - when (type) { - InstrumentationType.CMP -> "com.code_intelligence.jazzer.runtime.TraceCmpHooks" - InstrumentationType.DIV -> "com.code_intelligence.jazzer.runtime.TraceDivHooks" - InstrumentationType.INDIR -> "com.code_intelligence.jazzer.runtime.TraceIndirHooks" - InstrumentationType.NATIVE -> "com.code_intelligence.jazzer.runtime.NativeLibHooks" - else -> null + val includedHookNames = + instrumentationTypes + .mapNotNull { type -> + when (type) { + InstrumentationType.CMP -> "com.code_intelligence.jazzer.runtime.TraceCmpHooks" + InstrumentationType.DIV -> "com.code_intelligence.jazzer.runtime.TraceDivHooks" + InstrumentationType.INDIR -> "com.code_intelligence.jazzer.runtime.TraceIndirHooks" + InstrumentationType.NATIVE -> "com.code_intelligence.jazzer.runtime.NativeLibHooks" + else -> null + } } + val coverageIdSynchronizer = + if (idSyncFilePath != null) { + FileSyncCoverageIdStrategy(idSyncFilePath) + } else { + MemSyncCoverageIdStrategy() } - val coverageIdSynchronizer = if (idSyncFilePath != null) { - FileSyncCoverageIdStrategy(idSyncFilePath) - } else { - MemSyncCoverageIdStrategy() - } // If we don't append the JARs containing the custom hooks to the bootstrap class loader, // third-party hooks not contained in the agent JAR will not be able to instrument Java standard @@ -121,39 +127,39 @@ fun installInternal( Hooks.appendHooksToBootstrapClassLoaderSearch(instrumentation, customHookNames.toSet()) val (includedHooks, customHooks) = Hooks.loadHooks(additionalClassesExcludes, includedHookNames.toSet(), customHookNames.toSet()) - val runtimeInstrumentor = RuntimeInstrumentor( - instrumentation, - classNameGlobber, - customHookClassNameGlobber, - instrumentOnly.isNotEmpty(), - instrumentationTypes, - includedHooks.hooks, - customHooks.hooks, - conditionalHooks, - customHooks.additionalHookClassNameGlobber, - coverageIdSynchronizer, - dumpClassesDirPath, - ) + val runtimeInstrumentor = + RuntimeInstrumentor( + instrumentation, + classNameGlobber, + customHookClassNameGlobber, + instrumentOnly.isNotEmpty(), + instrumentationTypes, + includedHooks.hooks, + customHooks.hooks, + conditionalHooks, + customHooks.additionalHookClassNameGlobber, + coverageIdSynchronizer, + dumpClassesDirPath, + ) // These classes are e.g. dependencies of the RuntimeInstrumentor or hooks and thus were loaded // before the instrumentor was ready. Since we haven't enabled it yet, they can safely be // "retransformed": They haven't been transformed yet. - val classesToRetransform = instrumentation.allLoadedClasses - .filter { - // Always exclude internal Jazzer classes from retransformation, as even attempting to - // retransform those caused broken class definitions in older JVM versions. This points - // to a JDK bug that was not backported. - !it.name.startsWith("com.code_intelligence.jazzer.") && - ( - classNameGlobber.includes(it.name) || - customHookClassNameGlobber.includes(it.name) || - customHooks.additionalHookClassNameGlobber.includes(it.name) + val classesToRetransform = + instrumentation.allLoadedClasses + .filter { + // Always exclude internal Jazzer classes from retransformation, as even attempting to + // retransform those caused broken class definitions in older JVM versions. This points + // to a JDK bug that was not backported. + !it.name.startsWith("com.code_intelligence.jazzer.") && + ( + classNameGlobber.includes(it.name) || + customHookClassNameGlobber.includes(it.name) || + customHooks.additionalHookClassNameGlobber.includes(it.name) ) - } - .filter { - instrumentation.isModifiableClass(it) - } - .toTypedArray() + }.filter { + instrumentation.isModifiableClass(it) + }.toTypedArray() instrumentation.addTransformer(runtimeInstrumentor, true) @@ -164,7 +170,10 @@ fun installInternal( } } -private fun retransformClassesWithRetry(instrumentation: Instrumentation, classesToRetransform: Array>) { +private fun retransformClassesWithRetry( + instrumentation: Instrumentation, + classesToRetransform: Array>, +) { try { instrumentation.retransformClasses(*classesToRetransform) } catch (e: Throwable) { @@ -174,11 +183,16 @@ private fun retransformClassesWithRetry(instrumentation: Instrumentation, classe // The docs state that no transformation was performed if an exception is thrown. // Try again in a binary search fashion, until the not transformable classes have been isolated and reported. retransformClassesWithRetry(instrumentation, classesToRetransform.copyOfRange(0, classesToRetransform.size / 2)) - retransformClassesWithRetry(instrumentation, classesToRetransform.copyOfRange(classesToRetransform.size / 2, classesToRetransform.size)) + retransformClassesWithRetry( + instrumentation, + classesToRetransform.copyOfRange(classesToRetransform.size / 2, classesToRetransform.size), + ) } } } -private fun findManifestCustomHookNames() = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES) - .flatMap { it.split(':') } - .filter { it.isNotBlank() } +private fun findManifestCustomHookNames() = + ManifestUtils + .combineManifestValues(ManifestUtils.HOOK_CLASSES) + .flatMap { it.split(':') } + .filter { it.isNotBlank() } diff --git a/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt b/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt index cf5c8291e..bd7b50538 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt @@ -27,8 +27,9 @@ import java.util.UUID /** * Indicates a fatal failure to generate synchronized coverage IDs. */ -class CoverageIdException(cause: Throwable? = null) : - RuntimeException("Failed to synchronize coverage IDs", cause) +class CoverageIdException( + cause: Throwable? = null, +) : RuntimeException("Failed to synchronize coverage IDs", cause) /** * [CoverageIdStrategy] provides an abstraction to switch between context specific coverage ID generation. @@ -38,13 +39,15 @@ class CoverageIdException(cause: Throwable? = null) : * This precludes us from generating them simply as hashes of class names. */ interface CoverageIdStrategy { - /** * [withIdForClass] provides the initial coverage ID of the given [className] as parameter to the * [block] to execute. [block] has to return the number of additionally used IDs. */ @Throws(CoverageIdException::class) - fun withIdForClass(className: String, block: (Int) -> Int) + fun withIdForClass( + className: String, + block: (Int) -> Int, + ) } /** @@ -60,7 +63,10 @@ class MemSyncCoverageIdStrategy : CoverageIdStrategy { private var nextEdgeId = 0 @Synchronized - override fun withIdForClass(className: String, block: (Int) -> Int) { + override fun withIdForClass( + className: String, + block: (Int) -> Int, + ) { nextEdgeId += block(nextEdgeId) } } @@ -71,7 +77,9 @@ class MemSyncCoverageIdStrategy : CoverageIdStrategy { * This class takes care of synchronizing the access to the file between multiple processes as long as the general * contract of [CoverageIdStrategy] is followed. */ -class FileSyncCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrategy { +class FileSyncCoverageIdStrategy( + private val idSyncFile: Path, +) : CoverageIdStrategy { private val uuid: UUID = UUID.randomUUID() private var idFileLock: FileLock? = null @@ -85,7 +93,10 @@ class FileSyncCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrat * is always committed back again to the sync file by [commitIdCount]. */ @Synchronized - override fun withIdForClass(className: String, block: (Int) -> Int) { + override fun withIdForClass( + className: String, + block: (Int) -> Int, + ) { var actualNumEdgeIds = 0 try { val firstId = obtainFirstId(className) @@ -108,32 +119,36 @@ class FileSyncCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrat private fun obtainFirstId(className: String): Int { try { check(idFileLock == null) { "Already holding a lock on the ID file" } - val localIdFile = FileChannel.open( - idSyncFile, - StandardOpenOption.WRITE, - StandardOpenOption.READ, - ) + val localIdFile = + FileChannel.open( + idSyncFile, + StandardOpenOption.WRITE, + StandardOpenOption.READ, + ) // Wait until we have obtained the lock on the sync file. We hold the lock from this point until we have // finished reading and writing (if necessary) to the file. val localIdFileLock = localIdFile.lock() check(localIdFileLock.isValid && !localIdFileLock.isShared) // Parse the sync file, which consists of lines of the form // :: - val idInfo = localIdFileLock.channel().readFully() - .lineSequence() - .filterNot { it.isBlank() } - .map { line -> - val parts = line.split(':') - check(parts.size == 4) { - "Expected ID file line to be of the form ':::', got '$line'" - } - val lineClassName = parts[0] - val lineFirstId = parts[1].toInt() - check(lineFirstId >= 0) { "Negative first ID in line: $line" } - val lineIdCount = parts[2].toInt() - check(lineIdCount >= 0) { "Negative ID count in line: $line" } - Triple(lineClassName, lineFirstId, lineIdCount) - }.toList() + val idInfo = + localIdFileLock + .channel() + .readFully() + .lineSequence() + .filterNot { it.isBlank() } + .map { line -> + val parts = line.split(':') + check(parts.size == 4) { + "Expected ID file line to be of the form ':::', got '$line'" + } + val lineClassName = parts[0] + val lineFirstId = parts[1].toInt() + check(lineFirstId >= 0) { "Negative first ID in line: $line" } + val lineIdCount = parts[2].toInt() + check(lineIdCount >= 0) { "Negative ID count in line: $line" } + Triple(lineClassName, lineFirstId, lineIdCount) + }.toList() cachedClassName = className val idInfoForClass = idInfo.filter { it.first == className } return when (idInfoForClass.size) { diff --git a/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index 34ec4fd5b..3a44ed1e5 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -52,7 +52,6 @@ class RuntimeInstrumentor( private val coverageIdSynchronizer: CoverageIdStrategy, private val dumpClassesDir: Path?, ) : ClassFileTransformer { - @kotlin.time.ExperimentalTime override fun transform( loader: ClassLoader?, @@ -67,10 +66,16 @@ class RuntimeInstrumentor( // https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/ClassFileTransformer.html return try { if (instrumentOnly && protectionDomain != null) { - var outputPathPrefix = protectionDomain.getCodeSource().getLocation().getFile().toString() + var outputPathPrefix = + protectionDomain + .getCodeSource() + .getLocation() + .getFile() + .toString() if (outputPathPrefix.isNotEmpty()) { if (outputPathPrefix.contains(File.separator)) { - outputPathPrefix = outputPathPrefix.substring(outputPathPrefix.lastIndexOf(File.separator) + 1, outputPathPrefix.length) + outputPathPrefix = + outputPathPrefix.substring(outputPathPrefix.lastIndexOf(File.separator) + 1, outputPathPrefix.length) } if (outputPathPrefix.endsWith(".jar")) { @@ -116,7 +121,12 @@ class RuntimeInstrumentor( } } - private fun dumpToClassFile(internalClassName: String, bytecode: ByteArray, basenameSuffix: String = "", pathPrefix: String = "") { + private fun dumpToClassFile( + internalClassName: String, + bytecode: ByteArray, + basenameSuffix: String = "", + pathPrefix: String = "", + ) { val relativePath = "$pathPrefix$internalClassName$basenameSuffix.class" val absolutePath = dumpClassesDir!!.resolve(relativePath) val dumpFile = absolutePath.toFile() @@ -167,38 +177,44 @@ class RuntimeInstrumentor( } @kotlin.time.ExperimentalTime - fun transformInternal(internalClassName: String, maybeClassfileBuffer: ByteArray?): ByteArray? { - val (fullInstrumentation, printInfo) = when { - classesToFullyInstrument.includes(internalClassName) -> Pair(true, true) - classesToHookInstrument.includes(internalClassName) -> Pair(false, true) - // The classes to hook specified by hooks are more of an implementation detail of the hook. The list is - // always the same unless the set of hooks changes and doesn't help the user judge whether their classes are - // being instrumented, so we don't print info for them. - additionalClassesToHookInstrument.includes(internalClassName) -> Pair(false, false) - else -> return null - } + fun transformInternal( + internalClassName: String, + maybeClassfileBuffer: ByteArray?, + ): ByteArray? { + val (fullInstrumentation, printInfo) = + when { + classesToFullyInstrument.includes(internalClassName) -> Pair(true, true) + classesToHookInstrument.includes(internalClassName) -> Pair(false, true) + // The classes to hook specified by hooks are more of an implementation detail of the hook. The list is + // always the same unless the set of hooks changes and doesn't help the user judge whether their classes are + // being instrumented, so we don't print info for them. + additionalClassesToHookInstrument.includes(internalClassName) -> Pair(false, false) + else -> return null + } val className = internalClassName.replace('/', '.') - val classfileBuffer = maybeClassfileBuffer ?: ClassGraph() - .enableSystemJarsAndModules() - .acceptLibOrExtJars() - .ignoreClassVisibility() - .acceptClasses(className) - .scan() - .use { - it.getClassInfo(className)?.resource?.load() ?: run { - Log.warn("Failed to load bytecode of class $className") - return null + val classfileBuffer = + maybeClassfileBuffer ?: ClassGraph() + .enableSystemJarsAndModules() + .acceptLibOrExtJars() + .ignoreClassVisibility() + .acceptClasses(className) + .scan() + .use { + it.getClassInfo(className)?.resource?.load() ?: run { + Log.warn("Failed to load bytecode of class $className") + return null + } + } + val (instrumentedBytecode, duration) = + measureTimedValue { + try { + instrument(internalClassName, classfileBuffer, fullInstrumentation) + } catch (e: CoverageIdException) { + Log.error("Coverage IDs are out of sync") + e.printStackTrace() + exitProcess(1) } } - val (instrumentedBytecode, duration) = measureTimedValue { - try { - instrument(internalClassName, classfileBuffer, fullInstrumentation) - } catch (e: CoverageIdException) { - Log.error("Coverage IDs are out of sync") - e.printStackTrace() - exitProcess(1) - } - } val durationInMs = duration.inWholeMilliseconds val sizeIncrease = ((100.0 * (instrumentedBytecode.size - classfileBuffer.size)) / classfileBuffer.size).roundToInt() if (printInfo) { @@ -211,14 +227,19 @@ class RuntimeInstrumentor( return instrumentedBytecode } - private fun instrument(internalClassName: String, bytecode: ByteArray, fullInstrumentation: Boolean): ByteArray { - val classWithHooksEnabledField = if (conditionalHooks) { - // Let the hook instrumentation emit additional logic that checks the value of the - // hooksEnabled field on this class and skips the hook if it is false. - "com/code_intelligence/jazzer/runtime/JazzerInternal" - } else { - null - } + private fun instrument( + internalClassName: String, + bytecode: ByteArray, + fullInstrumentation: Boolean, + ): ByteArray { + val classWithHooksEnabledField = + if (conditionalHooks) { + // Let the hook instrumentation emit additional logic that checks the value of the + // hooksEnabled field on this class and skips the hook if it is false. + "com/code_intelligence/jazzer/runtime/JazzerInternal" + } else { + null + } return ClassInstrumentor(internalClassName, bytecode).run { if (fullInstrumentation) { // Coverage instrumentation must be performed before any other code updates diff --git a/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt b/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt index ba7ca359e..36c6cea3a 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt +++ b/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt @@ -29,15 +29,19 @@ private val JAZZER_PACKAGE_PREFIX = "com.code_intelligence.jazzer." private val PUBLIC_JAZZER_PACKAGES = setOf("api", "replay", "sanitizers") private val StackTraceElement.isInternalFrame: Boolean - get() = if (!className.startsWith(JAZZER_PACKAGE_PREFIX)) { - false - } else { - val jazzerSubPackage = - className.substring(JAZZER_PACKAGE_PREFIX.length).split(".", limit = 2)[0] - jazzerSubPackage !in PUBLIC_JAZZER_PACKAGES - } + get() = + if (!className.startsWith(JAZZER_PACKAGE_PREFIX)) { + false + } else { + val jazzerSubPackage = + className.substring(JAZZER_PACKAGE_PREFIX.length).split(".", limit = 2)[0] + jazzerSubPackage !in PUBLIC_JAZZER_PACKAGES + } -private fun hash(throwable: Throwable, passToRootCause: Boolean): ByteArray = +private fun hash( + throwable: Throwable, + passToRootCause: Boolean, +): ByteArray = MessageDigest.getInstance("SHA-256").run { // It suffices to hash the stack trace of the deepest cause as the higher-level causes only // contain part of the stack trace (plus possibly a different exception type). @@ -55,8 +59,7 @@ private fun hash(throwable: Throwable, passToRootCause: Boolean): ByteArray = it.className.startsWith("java.lang.reflect.") || it.className.startsWith("sun.reflect.") || it.className.startsWith("java.lang.invoke.") - } - .forEach { update(it.toString().toByteArray()) } + }.forEach { update(it.toString().toByteArray()) } if (throwable.suppressed.isNotEmpty()) { update("suppressed".toByteArray()) for (suppressed in throwable.suppressed) { @@ -87,36 +90,38 @@ fun computeDedupToken(throwable: Throwable): Long { * Annotates [throwable] with a severity and additional information if it represents a bug type * that has security content. */ -fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) { - is StackOverflowError -> { - // StackOverflowErrors are hard to deduplicate as the top-most stack frames vary wildly, - // whereas the information that is most useful for deduplication detection is hidden in the - // rest of the (truncated) stack frame. - // We heuristically clean up the stack trace by taking the elements from the bottom and - // stopping at the first repetition of a frame. The original error is returned as the cause - // unchanged. - val observedFrames = mutableSetOf() - val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame -> - (frame !in observedFrames).also { observedFrames.add(frame) } - } - var securityIssueMessage = "Stack overflow" - if (!IS_ANDROID) { - securityIssueMessage = "$securityIssueMessage (use '${getReproducingXssArg()}' to reproduce)" - } - FuzzerSecurityIssueLow(securityIssueMessage, throwable).apply { - stackTrace = bottomFramesWithoutRepetition.toTypedArray() +fun preprocessThrowable(throwable: Throwable): Throwable = + when (throwable) { + is StackOverflowError -> { + // StackOverflowErrors are hard to deduplicate as the top-most stack frames vary wildly, + // whereas the information that is most useful for deduplication detection is hidden in the + // rest of the (truncated) stack frame. + // We heuristically clean up the stack trace by taking the elements from the bottom and + // stopping at the first repetition of a frame. The original error is returned as the cause + // unchanged. + val observedFrames = mutableSetOf() + val bottomFramesWithoutRepetition = + throwable.stackTrace.takeLastWhile { frame -> + (frame !in observedFrames).also { observedFrames.add(frame) } + } + var securityIssueMessage = "Stack overflow" + if (!IS_ANDROID) { + securityIssueMessage = "$securityIssueMessage (use '${getReproducingXssArg()}' to reproduce)" + } + FuzzerSecurityIssueLow(securityIssueMessage, throwable).apply { + stackTrace = bottomFramesWithoutRepetition.toTypedArray() + } } - } - is OutOfMemoryError -> { - var securityIssueMessage = "Out of memory" - if (!IS_ANDROID) { - securityIssueMessage = "$securityIssueMessage (use '${getReproducingXmxArg()}' to reproduce)" + is OutOfMemoryError -> { + var securityIssueMessage = "Out of memory" + if (!IS_ANDROID) { + securityIssueMessage = "$securityIssueMessage (use '${getReproducingXmxArg()}' to reproduce)" + } + stripOwnStackTrace(FuzzerSecurityIssueLow(securityIssueMessage, throwable)) } - stripOwnStackTrace(FuzzerSecurityIssueLow(securityIssueMessage, throwable)) - } - is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable)) - else -> throwable -}.also { dropInternalFrames(it) } + is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable)) + else -> throwable + }.also { dropInternalFrames(it) } /** * Recursively strips all Jazzer-internal stack frames from the given [Throwable] and its causes. @@ -133,9 +138,10 @@ private fun dropInternalFrames(throwable: Throwable?) { * Strips the stack trace of [throwable] (e.g. because it was created in a utility method), but not * the stack traces of its causes. */ -private fun stripOwnStackTrace(throwable: Throwable) = throwable.apply { - stackTrace = emptyArray() -} +private fun stripOwnStackTrace(throwable: Throwable) = + throwable.apply { + stackTrace = emptyArray() + } /** * Returns a valid `-Xmx` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can @@ -159,7 +165,11 @@ private fun getReproducingXssArg(): String? { private fun getNumericFinalFlagValue(arg: String): Long? { val argPattern = "$arg\\D*(\\d*)".toRegex() - return argPattern.find(javaFullFinalFlags ?: return null)?.groupValues?.get(1)?.toLongOrNull() + return argPattern + .find(javaFullFinalFlags ?: return null) + ?.groupValues + ?.get(1) + ?.toLongOrNull() } private val javaFullFinalFlags by lazy { @@ -170,12 +180,14 @@ private fun readJavaFullFinalFlags(): String? { val javaHome = System.getProperty("java.home") ?: return null val javaBinary = "$javaHome/bin/java" val currentJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments - val javaPrintFlagsProcess = ProcessBuilder( - listOf(javaBinary) + currentJvmArgs + listOf( - "-XX:+PrintFlagsFinal", - "-version", - ), - ).start() + val javaPrintFlagsProcess = + ProcessBuilder( + listOf(javaBinary) + currentJvmArgs + + listOf( + "-XX:+PrintFlagsFinal", + "-version", + ), + ).start() return javaPrintFlagsProcess.inputStream.bufferedReader().useLines { lineSequence -> lineSequence .filter { it.contains("ThreadStackSize") || it.contains("MaxHeapSize") } @@ -188,15 +200,15 @@ fun dumpAllStackTraces() { for ((thread, stack) in Thread.getAllStackTraces()) { Log.println(thread.toString()) // Remove traces of this method and the methods it calls. - stack.asList() + stack + .asList() .asReversed() .takeWhile { !( it.className == "com.code_intelligence.jazzer.driver.ExceptionUtils" && it.methodName == "dumpAllStackTraces" - ) - } - .asReversed() + ) + }.asReversed() .forEach { frame -> Log.println("\tat $frame") } diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt index f024a7f63..51617ab0f 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt @@ -18,21 +18,23 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.runtime.CoverageMap -fun extractClassFileMajorVersion(classfileBuffer: ByteArray): Int { - return ((classfileBuffer[6].toInt() and 0xff) shl 8) or (classfileBuffer[7].toInt() and 0xff) -} - -class ClassInstrumentor(private val internalClassName: String, bytecode: ByteArray) { +fun extractClassFileMajorVersion(classfileBuffer: ByteArray): Int = + ((classfileBuffer[6].toInt() and 0xff) shl 8) or (classfileBuffer[7].toInt() and 0xff) +class ClassInstrumentor( + private val internalClassName: String, + bytecode: ByteArray, +) { var instrumentedBytecode = bytecode private set fun coverage(initialEdgeId: Int): Int { - val edgeCoverageInstrumentor = EdgeCoverageInstrumentor( - defaultEdgeCoverageStrategy, - defaultCoverageMap, - initialEdgeId, - ) + val edgeCoverageInstrumentor = + EdgeCoverageInstrumentor( + defaultEdgeCoverageStrategy, + defaultCoverageMap, + initialEdgeId, + ) instrumentedBytecode = edgeCoverageInstrumentor.instrument(internalClassName, instrumentedBytecode) return edgeCoverageInstrumentor.numEdges } @@ -42,12 +44,16 @@ class ClassInstrumentor(private val internalClassName: String, bytecode: ByteArr TraceDataFlowInstrumentor(instrumentations).instrument(internalClassName, instrumentedBytecode) } - fun hooks(hooks: Iterable, classWithHooksEnabledField: String?) { - instrumentedBytecode = HookInstrumentor( - hooks, - java6Mode = extractClassFileMajorVersion(instrumentedBytecode) < 51, - classWithHooksEnabledField = classWithHooksEnabledField, - ).instrument(internalClassName, instrumentedBytecode) + fun hooks( + hooks: Iterable, + classWithHooksEnabledField: String?, + ) { + instrumentedBytecode = + HookInstrumentor( + hooks, + java6Mode = extractClassFileMajorVersion(instrumentedBytecode) < 51, + classWithHooksEnabledField = classWithHooksEnabledField, + ).instrument(internalClassName, instrumentedBytecode) } companion object { diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt index 2e6299d82..0aab6ca85 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt @@ -44,16 +44,22 @@ object CoverageRecorder { private var startTimestamp: Instant? = null private val additionalCoverage = mutableSetOf() - fun recordInstrumentedClass(internalClassName: String, bytecode: ByteArray, firstId: Int, numIds: Int) { + fun recordInstrumentedClass( + internalClassName: String, + bytecode: ByteArray, + firstId: Int, + numIds: Int, + ) { if (startTimestamp == null) { startTimestamp = Instant.now() } - instrumentedClassInfo[internalClassName] = InstrumentedClassInfo( - CRC64.classId(bytecode), - firstId, - firstId + numIds, - bytecode, - ) + instrumentedClassInfo[internalClassName] = + InstrumentedClassInfo( + CRC64.classId(bytecode), + firstId, + firstId + numIds, + bytecode, + ) } /** @@ -70,7 +76,10 @@ object CoverageRecorder { */ @JvmStatic @JvmOverloads - fun dumpCoverageReport(dumpFileName: String, coveredIds: IntArray = CoverageMap.getEverCoveredIds()) { + fun dumpCoverageReport( + dumpFileName: String, + coveredIds: IntArray = CoverageMap.getEverCoveredIds(), + ) { File(dumpFileName).bufferedWriter().use { writer -> writer.write(computeFileCoverage(coveredIds)) } @@ -87,32 +96,39 @@ object CoverageRecorder { val counter = fileCoverage.branchCounter val percentage = 100 * counter.coveredRatio "${fileCoverage.name}: ${counter.coveredCount}/${counter.totalCount} (${percentage.format(2)}%)" - } + coverage.sourceFiles.joinToString( - "\n", - prefix = "Line coverage:\n", - postfix = "\n\n", - ) { fileCoverage -> - val counter = fileCoverage.lineCounter - val percentage = 100 * counter.coveredRatio - "${fileCoverage.name}: ${counter.coveredCount}/${counter.totalCount} (${percentage.format(2)}%)" - } + coverage.sourceFiles.joinToString( - "\n", - prefix = "Incompletely covered lines:\n", - postfix = "\n\n", - ) { fileCoverage -> - "${fileCoverage.name}: " + (fileCoverage.firstLine..fileCoverage.lastLine).filter { - val instructions = fileCoverage.getLine(it).instructionCounter - instructions.coveredCount in 1 until instructions.totalCount - }.toString() - } + coverage.sourceFiles.joinToString( - "\n", - prefix = "Missed lines:\n", - ) { fileCoverage -> - "${fileCoverage.name}: " + (fileCoverage.firstLine..fileCoverage.lastLine).filter { - val instructions = fileCoverage.getLine(it).instructionCounter - instructions.coveredCount == 0 && instructions.totalCount > 0 - }.toString() - } + } + + coverage.sourceFiles.joinToString( + "\n", + prefix = "Line coverage:\n", + postfix = "\n\n", + ) { fileCoverage -> + val counter = fileCoverage.lineCounter + val percentage = 100 * counter.coveredRatio + "${fileCoverage.name}: ${counter.coveredCount}/${counter.totalCount} (${percentage.format(2)}%)" + } + + coverage.sourceFiles.joinToString( + "\n", + prefix = "Incompletely covered lines:\n", + postfix = "\n\n", + ) { fileCoverage -> + "${fileCoverage.name}: " + + (fileCoverage.firstLine..fileCoverage.lastLine) + .filter { + val instructions = fileCoverage.getLine(it).instructionCounter + instructions.coveredCount in 1 until instructions.totalCount + }.toString() + } + + coverage.sourceFiles.joinToString( + "\n", + prefix = "Missed lines:\n", + ) { fileCoverage -> + "${fileCoverage.name}: " + + (fileCoverage.firstLine..fileCoverage.lastLine) + .filter { + val instructions = fileCoverage.getLine(it).instructionCounter + instructions.coveredCount == 0 && instructions.totalCount > 0 + }.toString() + } } /** @@ -122,7 +138,10 @@ object CoverageRecorder { */ @JvmStatic @JvmOverloads - fun dumpJacocoCoverage(dumpFileName: String, coveredIds: IntArray = CoverageMap.getEverCoveredIds()) { + fun dumpJacocoCoverage( + dumpFileName: String, + coveredIds: IntArray = CoverageMap.getEverCoveredIds(), + ) { FileOutputStream(dumpFileName).use { outStream -> dumpJacocoCoverage(outStream, coveredIds) } @@ -132,7 +151,10 @@ object CoverageRecorder { * [dumpJacocoCoverage] dumps the JaCoCo coverage of files using any [coveredIds] to [outStream]. */ @JvmStatic - fun dumpJacocoCoverage(outStream: OutputStream, coveredIds: IntArray) { + fun dumpJacocoCoverage( + outStream: OutputStream, + coveredIds: IntArray, + ) { // Return if no class has been instrumented. val startTimestamp = startTimestamp ?: return @@ -173,12 +195,12 @@ object CoverageRecorder { } // Generate a probes array for the current class only, i.e., mapping info.initialEdgeId to 0. val probes = BooleanArray(info.nextEdgeId - info.initialEdgeId) - (coveredIdsStart until coveredIdsEnd).asSequence() + (coveredIdsStart until coveredIdsEnd) + .asSequence() .map { val globalEdgeId = sortedCoveredIds[it] globalEdgeId - info.initialEdgeId - } - .forEach { classLocalEdgeId -> + }.forEach { classLocalEdgeId -> probes[classLocalEdgeId] = true } executionDataStore.visitClassExecution(ExecutionData(info.classId, internalClassName, probes)) @@ -189,8 +211,8 @@ object CoverageRecorder { /** * Create a [CoverageBuilder] containing all classes matching the include/exclude pattern and their coverage statistics. */ - fun analyzeCoverage(coveredIds: Set): CoverageBuilder? { - return try { + fun analyzeCoverage(coveredIds: Set): CoverageBuilder? = + try { val coverage = CoverageBuilder() analyzeAllUncoveredClasses(coverage) val executionDataStore = analyzeJacocoCoverage(coveredIds) @@ -208,7 +230,6 @@ object CoverageRecorder { e.printStackTrace() null } - } /** * Traverses the entire classpath and analyzes all uncovered classes that match the include/exclude pattern. @@ -216,11 +237,12 @@ object CoverageRecorder { * those that were loaded while the fuzzer ran. */ private fun analyzeAllUncoveredClasses(coverage: CoverageBuilder): CoverageBuilder { - val coveredClassNames = instrumentedClassInfo - .keys - .asSequence() - .map { it.replace('/', '.') } - .toSet() + val coveredClassNames = + instrumentedClassInfo + .keys + .asSequence() + .map { it.replace('/', '.') } + .toSet() ClassGraph() .enableClassInfo() .ignoreClassVisibility() @@ -229,8 +251,8 @@ object CoverageRecorder { // from the Java standard library are never traversed. "com.code_intelligence.jazzer.*", "jaz", - ) - .scan().use { result -> + ).scan() + .use { result -> // ExecutionDataStore is used to look up existing coverage during analysis of the class files, // no entries are added during that. Passing in an empty store is fine for uncovered files. val emptyExecutionDataStore = ExecutionDataStore() @@ -240,7 +262,11 @@ object CoverageRecorder { .filterNot { classInfo -> classInfo.name in coveredClassNames } .forEach { classInfo -> classInfo.resource.use { resource -> - EdgeCoverageInstrumentor(ClassInstrumentor.defaultEdgeCoverageStrategy, ClassInstrumentor.defaultCoverageMap, 0).analyze( + EdgeCoverageInstrumentor( + ClassInstrumentor.defaultEdgeCoverageStrategy, + ClassInstrumentor.defaultCoverageMap, + 0, + ).analyze( emptyExecutionDataStore, coverage, resource.load(), diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt index ab4df625c..7ec5431b2 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt @@ -25,40 +25,39 @@ val Class<*>.descriptor: String get() = Type.getDescriptor(this) val Executable.descriptor: String - get() = if (this is Method) { - Type.getMethodDescriptor(this) - } else { - Type.getConstructorDescriptor(this as Constructor<*>?) - } + get() = + if (this is Method) { + Type.getMethodDescriptor(this) + } else { + Type.getConstructorDescriptor(this as Constructor<*>?) + } -internal fun isPrimitiveType(typeDescriptor: String): Boolean { - return typeDescriptor in arrayOf("B", "C", "D", "F", "I", "J", "S", "V", "Z") -} +internal fun isPrimitiveType(typeDescriptor: String): Boolean = typeDescriptor in arrayOf("B", "C", "D", "F", "I", "J", "S", "V", "Z") private fun isPrimitiveType(typeDescriptor: Char) = isPrimitiveType(typeDescriptor.toString()) -internal fun getWrapperTypeDescriptor(typeDescriptor: String): String = when (typeDescriptor) { - "B" -> "Ljava/lang/Byte;" - "C" -> "Ljava/lang/Character;" - "D" -> "Ljava/lang/Double;" - "F" -> "Ljava/lang/Float;" - "I" -> "Ljava/lang/Integer;" - "J" -> "Ljava/lang/Long;" - "S" -> "Ljava/lang/Short;" - "V" -> "Ljava/lang/Void;" - "Z" -> "Ljava/lang/Boolean;" - else -> typeDescriptor -} +internal fun getWrapperTypeDescriptor(typeDescriptor: String): String = + when (typeDescriptor) { + "B" -> "Ljava/lang/Byte;" + "C" -> "Ljava/lang/Character;" + "D" -> "Ljava/lang/Double;" + "F" -> "Ljava/lang/Float;" + "I" -> "Ljava/lang/Integer;" + "J" -> "Ljava/lang/Long;" + "S" -> "Ljava/lang/Short;" + "V" -> "Ljava/lang/Void;" + "Z" -> "Ljava/lang/Boolean;" + else -> typeDescriptor + } // Removes the 'L' and ';' prefix/suffix from signatures to get the full class name. // Note that array signatures '[Ljava/lang/String;' already have the correct form. -internal fun extractInternalClassName(typeDescriptor: String): String { - return if (typeDescriptor.startsWith("L") && typeDescriptor.endsWith(";")) { +internal fun extractInternalClassName(typeDescriptor: String): String = + if (typeDescriptor.startsWith("L") && typeDescriptor.endsWith(";")) { typeDescriptor.substring(1, typeDescriptor.length - 1) } else { typeDescriptor } -} internal fun extractParameterTypeDescriptors(methodDescriptor: String): List { require(methodDescriptor.startsWith('(')) { "Method descriptor must start with '('" } diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt index 90a98dfb2..daef16429 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt @@ -20,16 +20,20 @@ import java.security.MessageDigest import java.security.SecureRandom // This RNG is resistant to collisions (even under XOR) but fully deterministic. -internal class DeterministicRandom(vararg contexts: String) { - private val random = SecureRandom.getInstance("SHA1PRNG").apply { - val contextHash = MessageDigest.getInstance("SHA-256").run { - for (context in contexts) { - update(context.toByteArray()) - } - digest() +internal class DeterministicRandom( + vararg contexts: String, +) { + private val random = + SecureRandom.getInstance("SHA1PRNG").apply { + val contextHash = + MessageDigest.getInstance("SHA-256").run { + for (context in contexts) { + update(context.toByteArray()) + } + digest() + } + setSeed(contextHash) } - setSeed(contextHash) - } fun nextInt(bound: Int) = random.nextInt(bound) diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt index 6d9d1469b..eb7067947 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt @@ -41,7 +41,6 @@ import kotlin.math.max * hold the collected coverage data at runtime. */ interface EdgeCoverageStrategy { - /** * Inject bytecode instrumentation on a control flow edge with ID [edgeId], with access to the * local variable [variable] that is populated at the beginning of each method by the @@ -71,7 +70,11 @@ interface EdgeCoverageStrategy { * Inject bytecode that loads the coverage counters of the coverage map class described by * [coverageMapInternalClassName] into the local variable [variable]. */ - fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) + fun loadLocalVariable( + mv: MethodVisitor, + variable: Int, + coverageMapInternalClassName: String, + ) /** * The maximal number of stack elements used by [loadLocalVariable]. @@ -103,19 +106,28 @@ class EdgeCoverageInstrumentor( ), ) - override fun instrument(internalClassName: String, bytecode: ByteArray): ByteArray { + override fun instrument( + internalClassName: String, + bytecode: ByteArray, + ): ByteArray { val reader = InstrSupport.classReaderFor(bytecode) val writer = ClassWriter(reader, 0) val version = InstrSupport.getMajorVersion(reader) - val visitor = EdgeCoverageClassProbesAdapter( - ClassInstrumenter(edgeCoverageProbeArrayStrategy, edgeCoverageProbeInserterFactory, writer), - InstrSupport.needsFrames(version), - ) + val visitor = + EdgeCoverageClassProbesAdapter( + ClassInstrumenter(edgeCoverageProbeArrayStrategy, edgeCoverageProbeInserterFactory, writer), + InstrSupport.needsFrames(version), + ) reader.accept(visitor, ClassReader.EXPAND_FRAMES) return writer.toByteArray() } - fun analyze(executionData: ExecutionDataStore, coverageVisitor: ICoverageVisitor, bytecode: ByteArray, internalClassName: String) { + fun analyze( + executionData: ExecutionDataStore, + coverageVisitor: ICoverageVisitor, + bytecode: ByteArray, + internalClassName: String, + ) { Analyzer(executionData, coverageVisitor, edgeCoverageClassProbesAdapterFactory).run { analyzeClass(bytecode, internalClassName) } @@ -144,7 +156,10 @@ class EdgeCoverageInstrumentor( strategy.instrumentControlFlowEdge(mv, id, variable, coverageMapInternalClassName) } - override fun visitMaxs(maxStack: Int, maxLocals: Int) { + override fun visitMaxs( + maxStack: Int, + maxLocals: Int, + ) { val newMaxStack = max(maxStack + strategy.instrumentControlFlowEdgeStackSize, strategy.loadLocalVariableStackSize) val newMaxLocals = maxLocals + if (strategy.localVariableType != null) 1 else 0 mv.visitMaxs(newMaxStack, newMaxLocals) @@ -158,8 +173,10 @@ class EdgeCoverageInstrumentor( EdgeCoverageProbeInserter(access, name, desc, mv, arrayStrategy) } - private inner class EdgeCoverageClassProbesAdapter(private val cpv: ClassProbesVisitor, trackFrames: Boolean) : - ClassProbesAdapter(cpv, trackFrames) { + private inner class EdgeCoverageClassProbesAdapter( + private val cpv: ClassProbesVisitor, + trackFrames: Boolean, + ) : ClassProbesAdapter(cpv, trackFrames) { override fun nextId(): Int = nextEdgeId() override fun visitEnd() { @@ -170,18 +187,27 @@ class EdgeCoverageInstrumentor( } } - private val edgeCoverageClassProbesAdapterFactory = IClassProbesAdapterFactory { probesVisitor, trackFrames -> - EdgeCoverageClassProbesAdapter(probesVisitor, trackFrames) - } - - private val edgeCoverageProbeArrayStrategy = object : IProbeArrayStrategy { - override fun storeInstance(mv: MethodVisitor, clinit: Boolean, variable: Int): Int { - strategy.loadLocalVariable(mv, variable, coverageMapInternalClassName) - return strategy.loadLocalVariableStackSize + private val edgeCoverageClassProbesAdapterFactory = + IClassProbesAdapterFactory { probesVisitor, trackFrames -> + EdgeCoverageClassProbesAdapter(probesVisitor, trackFrames) } - override fun addMembers(cv: ClassVisitor, probeCount: Int) {} - } + private val edgeCoverageProbeArrayStrategy = + object : IProbeArrayStrategy { + override fun storeInstance( + mv: MethodVisitor, + clinit: Boolean, + variable: Int, + ): Int { + strategy.loadLocalVariable(mv, variable, coverageMapInternalClassName) + return strategy.loadLocalVariableStackSize + } + + override fun addMembers( + cv: ClassVisitor, + probeCount: Int, + ) {} + } } fun MethodVisitor.push(value: Int) { diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt index 1e1cb4877..55e4c6f26 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt @@ -38,21 +38,28 @@ class Hook private constructor( val hookMethodName: String, val hookMethodDescriptor: String, ) { - - override fun toString(): String { - return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName $additionalClassesToHook" - } + override fun toString(): String = + "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName $additionalClassesToHook" companion object { - fun createAndVerifyHook(hookMethod: Method, hookData: MethodHook, className: String): Hook { - return createHook(hookMethod, hookData, className).also { + fun createAndVerifyHook( + hookMethod: Method, + hookData: MethodHook, + className: String, + ): Hook = + createHook(hookMethod, hookData, className).also { verify(hookMethod, it) } - } - private fun createHook(hookMethod: Method, annotation: MethodHook, targetClassName: String): Hook { - val targetReturnTypeDescriptor = annotation.targetMethodDescriptor - .takeIf { it.isNotBlank() }?.let { extractReturnTypeDescriptor(it) } + private fun createHook( + hookMethod: Method, + annotation: MethodHook, + targetClassName: String, + ): Hook { + val targetReturnTypeDescriptor = + annotation.targetMethodDescriptor + .takeIf { it.isNotBlank() } + ?.let { extractReturnTypeDescriptor(it) } val hookClassName: String = hookMethod.declaringClass.name return Hook( targetClassName = targetClassName, @@ -70,7 +77,10 @@ class Hook private constructor( ) } - private fun verify(hookMethod: Method, potentialHook: Hook) { + private fun verify( + hookMethod: Method, + potentialHook: Hook, + ) { // Verify the hook method's modifiers (public static). require(Modifier.isPublic(hookMethod.modifiers)) { "$potentialHook: hook method must be public" } require(Modifier.isStatic(hookMethod.modifiers)) { "$potentialHook: hook method must be static" } @@ -78,39 +88,49 @@ class Hook private constructor( // Verify the hook method's parameter count. val numParameters = hookMethod.parameters.size when (potentialHook.hookType) { - HookType.BEFORE, HookType.REPLACE -> require(numParameters == 4) { "$potentialHook: incorrect number of parameters (expected 4)" } + HookType.BEFORE, HookType.REPLACE -> + require( + numParameters == 4, + ) { "$potentialHook: incorrect number of parameters (expected 4)" } HookType.AFTER -> require(numParameters == 5) { "$potentialHook: incorrect number of parameters (expected 5)" } } // Verify the hook method's parameter types. val parameterTypes = hookMethod.parameterTypes require(parameterTypes[0] == MethodHandle::class.java) { "$potentialHook: first parameter must have type MethodHandle" } - require(parameterTypes[1] == Object::class.java || parameterTypes[1].name == potentialHook.targetClassName) { "$potentialHook: second parameter must have type Object or ${potentialHook.targetClassName}" } + require(parameterTypes[1] == Object::class.java || parameterTypes[1].name == potentialHook.targetClassName) { + "$potentialHook: second parameter must have type Object or ${potentialHook.targetClassName}" + } require(parameterTypes[2] == Array::class.java) { "$potentialHook: third parameter must have type Object[]" } require(parameterTypes[3] == Int::class.javaPrimitiveType) { "$potentialHook: fourth parameter must have type int" } // Verify the hook method's return type if possible. when (potentialHook.hookType) { - HookType.BEFORE, HookType.AFTER -> require(hookMethod.returnType == Void.TYPE) { - "$potentialHook: return type must be void" - } - HookType.REPLACE -> if (potentialHook.targetReturnTypeDescriptor != null) { - if (potentialHook.targetMethodName == "") { - require(hookMethod.returnType.name == potentialHook.targetClassName) { "$potentialHook: return type must be ${potentialHook.targetClassName} to match target constructor" } - } else if (potentialHook.targetReturnTypeDescriptor == "V") { - require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void" } - } else { - require( - hookMethod.returnType.descriptor in listOf( - java.lang.Object::class.java.descriptor, - potentialHook.targetReturnTypeDescriptor, - potentialHook.targetWrappedReturnTypeDescriptor, - ), - ) { - "$potentialHook: return type must have type Object or match the descriptors ${potentialHook.targetReturnTypeDescriptor} or ${potentialHook.targetWrappedReturnTypeDescriptor}" + HookType.BEFORE, HookType.AFTER -> + require(hookMethod.returnType == Void.TYPE) { + "$potentialHook: return type must be void" + } + HookType.REPLACE -> + if (potentialHook.targetReturnTypeDescriptor != null) { + if (potentialHook.targetMethodName == "") { + require(hookMethod.returnType.name == potentialHook.targetClassName) { + "$potentialHook: return type must be ${potentialHook.targetClassName} to match target constructor" + } + } else if (potentialHook.targetReturnTypeDescriptor == "V") { + require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void" } + } else { + require( + hookMethod.returnType.descriptor in + listOf( + java.lang.Object::class.java.descriptor, + potentialHook.targetReturnTypeDescriptor, + potentialHook.targetWrappedReturnTypeDescriptor, + ), + ) { + "$potentialHook: return type must have type Object or match the descriptors ${potentialHook.targetReturnTypeDescriptor} or ${potentialHook.targetWrappedReturnTypeDescriptor}" + } } } - } } // AfterMethodHook only: Verify the type of the last parameter if known. Even if not diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt index 56a65ea0d..735deab5d 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt @@ -26,39 +26,42 @@ internal class HookInstrumentor( private val java6Mode: Boolean, private val classWithHooksEnabledField: String?, ) : Instrumentor { - private lateinit var random: DeterministicRandom - override fun instrument(internalClassName: String, bytecode: ByteArray): ByteArray { + override fun instrument( + internalClassName: String, + bytecode: ByteArray, + ): ByteArray { val reader = ClassReader(bytecode) val writer = ClassWriter(reader, ClassWriter.COMPUTE_MAXS) random = DeterministicRandom("hook", reader.className) - val interceptor = object : ClassVisitor(Instrumentor.ASM_API_VERSION, writer) { - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array?, - ): MethodVisitor? { - val mv = cv.visitMethod(access, name, descriptor, signature, exceptions) ?: return null - return if (shouldInstrument(access)) { - makeHookMethodVisitor( - internalClassName, - access, - name, - descriptor, - mv, - hooks, - java6Mode, - random, - classWithHooksEnabledField, - ) - } else { - mv + val interceptor = + object : ClassVisitor(Instrumentor.ASM_API_VERSION, writer) { + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor? { + val mv = cv.visitMethod(access, name, descriptor, signature, exceptions) ?: return null + return if (shouldInstrument(access)) { + makeHookMethodVisitor( + internalClassName, + access, + name, + descriptor, + mv, + hooks, + java6Mode, + random, + classWithHooksEnabledField, + ) + } else { + mv + } } } - } reader.accept(interceptor, ClassReader.EXPAND_FRAMES) return writer.toByteArray() } diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt index 5b85fb4cc..17c88011a 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt @@ -37,8 +37,8 @@ internal fun makeHookMethodVisitor( java6Mode: Boolean, random: DeterministicRandom, classWithHooksEnabledField: String?, -): MethodVisitor { - return HookMethodVisitor( +): MethodVisitor = + HookMethodVisitor( owner, access, name, @@ -49,7 +49,6 @@ internal fun makeHookMethodVisitor( random, classWithHooksEnabledField, ).lvs -} private class HookMethodVisitor( owner: String, @@ -62,50 +61,51 @@ private class HookMethodVisitor( private val random: DeterministicRandom, private val classWithHooksEnabledField: String?, ) : MethodVisitor( - Instrumentor.ASM_API_VERSION, - // AnalyzerAdapter computes stack map frames at every instruction, which is needed for the - // conditional hook logic as it adds a conditional jump. Before Java 7, stack map frames were - // neither included nor required in class files. - // - // Note: Delegating to AnalyzerAdapter rather than having AnalyzerAdapter delegate to our - // MethodVisitor is unusual. We do this since we insert conditional jumps around method calls, - // which requires knowing the stack map both before and after the call. If AnalyzerAdapter - // delegated to this MethodVisitor, we would only be able to access the stack map before the - // method call in visitMethodInsn. - if (classWithHooksEnabledField != null && !java6Mode) { - AnalyzerAdapter( - owner, - access, - name, - descriptor, - methodVisitor, - ) - } else { - methodVisitor - }, -) { - + Instrumentor.ASM_API_VERSION, + // AnalyzerAdapter computes stack map frames at every instruction, which is needed for the + // conditional hook logic as it adds a conditional jump. Before Java 7, stack map frames were + // neither included nor required in class files. + // + // Note: Delegating to AnalyzerAdapter rather than having AnalyzerAdapter delegate to our + // MethodVisitor is unusual. We do this since we insert conditional jumps around method calls, + // which requires knowing the stack map both before and after the call. If AnalyzerAdapter + // delegated to this MethodVisitor, we would only be able to access the stack map before the + // method call in visitMethodInsn. + if (classWithHooksEnabledField != null && !java6Mode) { + AnalyzerAdapter( + owner, + access, + name, + descriptor, + methodVisitor, + ) + } else { + methodVisitor + }, + ) { companion object { private val showUnsupportedHookWarning = AtomicBoolean(true) } - val lvs = object : LocalVariablesSorter(Instrumentor.ASM_API_VERSION, access, descriptor, this) { - override fun updateNewLocals(newLocals: Array) { - // The local variables involved in calling hooks do not need to outlive the current - // basic block and should thus not appear in stack map frames. By requesting the - // LocalVariableSorter to fill their entries in stack map frames with TOP, they will - // be treated like an unused local variable slot. - newLocals.fill(Opcodes.TOP) + val lvs = + object : LocalVariablesSorter(Instrumentor.ASM_API_VERSION, access, descriptor, this) { + override fun updateNewLocals(newLocals: Array) { + // The local variables involved in calling hooks do not need to outlive the current + // basic block and should thus not appear in stack map frames. By requesting the + // LocalVariableSorter to fill their entries in stack map frames with TOP, they will + // be treated like an unused local variable slot. + newLocals.fill(Opcodes.TOP) + } } - } - private val hooks = hooks.groupBy { hook -> - var hookKey = "${hook.hookType}#${hook.targetInternalClassName}#${hook.targetMethodName}" - if (hook.targetMethodDescriptor != null) { - hookKey += "#${hook.targetMethodDescriptor}" + private val hooks = + hooks.groupBy { hook -> + var hookKey = "${hook.hookType}#${hook.targetInternalClassName}#${hook.targetMethodName}" + if (hook.targetMethodDescriptor != null) { + hookKey += "#${hook.targetMethodDescriptor}" + } + hookKey } - hookKey - } override fun visitMethodInsn( opcode: Int, @@ -233,13 +233,14 @@ private class HookMethodVisitor( } } else { // Push a MethodHandle representing the hooked method. - val handleOpcode = when (opcode) { - Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL - Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE - Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC - Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL - else -> -1 - } + val handleOpcode = + when (opcode) { + Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL + Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE + Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC + Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL + else -> -1 + } if (java6Mode) { // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50). mv.visitInsn(Opcodes.ACONST_NULL) // push nullref @@ -381,19 +382,28 @@ private class HookMethodVisitor( } } - private fun isMethodInvocationOp(opcode: Int) = opcode in listOf( - Opcodes.INVOKEVIRTUAL, - Opcodes.INVOKEINTERFACE, - Opcodes.INVOKESTATIC, - Opcodes.INVOKESPECIAL, - ) + private fun isMethodInvocationOp(opcode: Int) = + opcode in + listOf( + Opcodes.INVOKEVIRTUAL, + Opcodes.INVOKEINTERFACE, + Opcodes.INVOKESTATIC, + Opcodes.INVOKESPECIAL, + ) - private fun findMatchingHooks(owner: String, name: String, descriptor: String): List { - val result = HookType.values().flatMap { hookType -> - val withoutDescriptorKey = "$hookType#$owner#$name" - val withDescriptorKey = "$withoutDescriptorKey#$descriptor" - hooks[withDescriptorKey].orEmpty() + hooks[withoutDescriptorKey].orEmpty() - }.sortedBy { it.hookType } + private fun findMatchingHooks( + owner: String, + name: String, + descriptor: String, + ): List { + val result = + HookType + .values() + .flatMap { hookType -> + val withoutDescriptorKey = "$hookType#$owner#$name" + val withDescriptorKey = "$withoutDescriptorKey#$descriptor" + hooks[withDescriptorKey].orEmpty() + hooks[withoutDescriptorKey].orEmpty() + }.sortedBy { it.hookType } val replaceHookCount = result.count { it.hookType == HookType.REPLACE } check( replaceHookCount == 0 || @@ -457,7 +467,10 @@ private class HookMethodVisitor( // Loads all arguments for a method call from a local object array. // argTypeSigs: The type signatures for all method arguments // localObjArr: Index of a local variable containing an object array where the arguments will be loaded from - private fun loadMethodArguments(paramDescriptors: List, localObjArr: Int) { + private fun loadMethodArguments( + paramDescriptors: List, + localObjArr: Int, + ) { // Loop over all arguments for ((argIdx, argDescriptor) in paramDescriptors.withIndex()) { // Push a reference to the object array on the stack @@ -494,17 +507,18 @@ private class HookMethodVisitor( // and pushes the primitive value it contains (e.g. removes Integer, pushes int). // This is done by calling .intValue(...) / .charValue(...) / ... on the wrapper object. private fun unwrapTypeIfPrimitive(primitiveTypeDescriptor: String) { - val (methodName, wrappedTypeDescriptor) = when (primitiveTypeDescriptor) { - "B" -> Pair("byteValue", "java/lang/Byte") - "C" -> Pair("charValue", "java/lang/Character") - "D" -> Pair("doubleValue", "java/lang/Double") - "F" -> Pair("floatValue", "java/lang/Float") - "I" -> Pair("intValue", "java/lang/Integer") - "J" -> Pair("longValue", "java/lang/Long") - "S" -> Pair("shortValue", "java/lang/Short") - "Z" -> Pair("booleanValue", "java/lang/Boolean") - else -> return - } + val (methodName, wrappedTypeDescriptor) = + when (primitiveTypeDescriptor) { + "B" -> Pair("byteValue", "java/lang/Byte") + "C" -> Pair("charValue", "java/lang/Character") + "D" -> Pair("doubleValue", "java/lang/Double") + "F" -> Pair("floatValue", "java/lang/Float") + "I" -> Pair("intValue", "java/lang/Integer") + "J" -> Pair("longValue", "java/lang/Long") + "S" -> Pair("shortValue", "java/lang/Short") + "Z" -> Pair("booleanValue", "java/lang/Boolean") + else -> return + } mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, wrappedTypeDescriptor, diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt index 658bf233e..9d857524c 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt @@ -31,27 +31,31 @@ data class Hooks( val hookClasses: Set>, val additionalHookClassNameGlobber: ClassNameGlobber, ) { - companion object { - - fun appendHooksToBootstrapClassLoaderSearch(instrumentation: Instrumentation, hookClassNames: Set) { - hookClassNames.mapNotNull { hook -> - val hookClassFilePath = "/${hook.replace('.', '/')}.class" - val hookClassFile = Companion::class.java.getResource(hookClassFilePath) ?: return@mapNotNull null - if ("jar" != hookClassFile.protocol) { - return@mapNotNull null - } - // hookClassFile.file looks as follows: - // file:/tmp/ExampleFuzzerHooks_deploy.jar!/com/example/ExampleFuzzerHooks.class - hookClassFile.file.removePrefix("file:").takeWhile { it != '!' } - } - .toSet() + fun appendHooksToBootstrapClassLoaderSearch( + instrumentation: Instrumentation, + hookClassNames: Set, + ) { + hookClassNames + .mapNotNull { hook -> + val hookClassFilePath = "/${hook.replace('.', '/')}.class" + val hookClassFile = Companion::class.java.getResource(hookClassFilePath) ?: return@mapNotNull null + if ("jar" != hookClassFile.protocol) { + return@mapNotNull null + } + // hookClassFile.file looks as follows: + // file:/tmp/ExampleFuzzerHooks_deploy.jar!/com/example/ExampleFuzzerHooks.class + hookClassFile.file.removePrefix("file:").takeWhile { it != '!' } + }.toSet() .map { JarFile(it) } .forEach { instrumentation.appendToBootstrapClassLoaderSearch(it) } } - fun loadHooks(excludeHookClassNames: List, vararg hookClassNames: Set): List { - return ClassGraph() + fun loadHooks( + excludeHookClassNames: List, + vararg hookClassNames: Set, + ): List = + ClassGraph() .enableClassInfo() .enableSystemJarsAndModules() .acceptLibOrExtJars() @@ -63,38 +67,40 @@ data class Hooks( val loader = HooksLoader(scanResult, excludeHookClassNames) hookClassNames.map(loader::load) } - } - - private class HooksLoader(private val scanResult: ScanResult, val excludeHookClassNames: List) { + private class HooksLoader( + private val scanResult: ScanResult, + val excludeHookClassNames: List, + ) { fun load(hookClassNames: Set): Hooks { val hooksWithHookClasses = hookClassNames.flatMap(::loadHooks) val hooks = hooksWithHookClasses.map { it.first } val hookClasses = hooksWithHookClasses.map { it.second }.toSet() - val additionalHookClassNameGlobber = ClassNameGlobber( - hooks.flatMap(Hook::additionalClassesToHook), - excludeHookClassNames, - ) + val additionalHookClassNameGlobber = + ClassNameGlobber( + hooks.flatMap(Hook::additionalClassesToHook), + excludeHookClassNames, + ) return Hooks(hooks, hookClasses, additionalHookClassNameGlobber) } - private fun loadHooks(hookClassName: String): List>> { - return try { + private fun loadHooks(hookClassName: String): List>> = + try { // We let the static initializers of hook classes execute so that hooks can run // code before the fuzz target class has been loaded (e.g., register themselves // for the onFuzzTargetReady callback). val hookClass = Class.forName(hookClassName, true, Companion::class.java.classLoader) - loadHooks(hookClass).also { - Log.info("Loaded ${it.size} hooks from $hookClassName") - }.map { - it to hookClass - } + loadHooks(hookClass) + .also { + Log.info("Loaded ${it.size} hooks from $hookClassName") + }.map { + it to hookClass + } } catch (e: ClassNotFoundException) { Log.warn("Failed to load hooks from $hookClassName", e) emptyList() } - } private fun loadHooks(hookClass: Class<*>): List { val hooks = mutableListOf() @@ -111,23 +117,26 @@ data class Hooks( return hooks } - private fun verifyAndGetHooks(hookMethod: Method, hookData: MethodHook): List { - return lookupClassesToHook(hookData.targetClassName) + private fun verifyAndGetHooks( + hookMethod: Method, + hookData: MethodHook, + ): List = + lookupClassesToHook(hookData.targetClassName) .map { className -> Hook.createAndVerifyHook(hookMethod, hookData, className) } - } private fun lookupClassesToHook(annotationTargetClassName: String): List { // Allowing arbitrary exterior whitespace in the target class name allows for an easy workaround // for mangled hooks due to shading applied to hooks. val targetClassName = annotationTargetClassName.trim() val targetClassInfo = scanResult.getClassInfo(targetClassName) ?: return listOf(targetClassName) - val additionalTargetClasses = when { - targetClassInfo.isInterface -> scanResult.getClassesImplementing(targetClassName) - targetClassInfo.isAbstract -> scanResult.getSubclasses(targetClassName) - else -> emptyList() - } + val additionalTargetClasses = + when { + targetClassInfo.isInterface -> scanResult.getClassesImplementing(targetClassName) + targetClassInfo.isAbstract -> scanResult.getSubclasses(targetClassName) + else -> emptyList() + } return (listOf(targetClassName) + additionalTargetClasses.map { it.name }).sorted() } } diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt index 2fb5ae55f..fa32f51c9 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt @@ -29,17 +29,18 @@ enum class InstrumentationType { } internal interface Instrumentor { - fun instrument(internalClassName: String, bytecode: ByteArray): ByteArray + fun instrument( + internalClassName: String, + bytecode: ByteArray, + ): ByteArray - fun shouldInstrument(access: Int): Boolean { - return (access and Opcodes.ACC_ABSTRACT == 0) && + fun shouldInstrument(access: Int): Boolean = + (access and Opcodes.ACC_ABSTRACT == 0) && (access and Opcodes.ACC_NATIVE == 0) - } - fun shouldInstrument(method: MethodNode): Boolean { - return shouldInstrument(method.access) && + fun shouldInstrument(method: MethodNode): Boolean = + shouldInstrument(method.access) && method.instructions.size() > 0 - } companion object { const val ASM_API_VERSION = Opcodes.ASM9 diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt b/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt index 686ca6dc0..e637e9515 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt @@ -36,10 +36,12 @@ internal class TraceDataFlowInstrumentor( private val types: Set, private val callbackInternalClassName: String = "com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks", ) : Instrumentor { - private lateinit var random: DeterministicRandom - override fun instrument(internalClassName: String, bytecode: ByteArray): ByteArray { + override fun instrument( + internalClassName: String, + bytecode: ByteArray, + ): ByteArray { val node = ClassNode() val reader = ClassReader(bytecode) reader.accept(node, 0) @@ -93,31 +95,33 @@ internal class TraceDataFlowInstrumentor( // https://github.com/llvm-mirror/compiler-rt/blob/69445f095c22aac2388f939bedebf224a6efcdaf/lib/fuzzer/FuzzerTracePC.cpp#L520 // Case values are reported to libFuzzer via an array of unsigned long values and thus need to be // sorted by unsigned value. - val caseValues = when (inst) { - is LookupSwitchInsnNode -> { - // If the switch is over String values, find out the actual values and not the hashes, and - // report them to libFuzzer in the switch's default case. - if (instrumentSwitchOverStrings(method, inst)) { - continue@loop - } - if (inst.keys.isEmpty() || (0 <= inst.keys.first() && inst.keys.last() < 256)) { - continue@loop + val caseValues = + when (inst) { + is LookupSwitchInsnNode -> { + // If the switch is over String values, find out the actual values and not the hashes, and + // report them to libFuzzer in the switch's default case. + if (instrumentSwitchOverStrings(method, inst)) { + continue@loop + } + if (inst.keys.isEmpty() || (0 <= inst.keys.first() && inst.keys.last() < 256)) { + continue@loop + } + inst.keys } - inst.keys - } - is TableSwitchInsnNode -> { - if (0 <= inst.min && inst.max < 256) { - continue@loop + is TableSwitchInsnNode -> { + if (0 <= inst.min && inst.max < 256) { + continue@loop + } + (inst.min..inst.max) + .filter { caseValue -> + val index = caseValue - inst.min + // Filter out "gap cases". + inst.labels[index].label != inst.dflt.label + }.toList() } - (inst.min..inst.max).filter { caseValue -> - val index = caseValue - inst.min - // Filter out "gap cases". - inst.labels[index].label != inst.dflt.label - }.toList() - } - // Not reached. - else -> continue@loop - }.sortedBy { it.toUInt() }.map { it.toLong() }.toLongArray() + // Not reached. + else -> continue@loop + }.sortedBy { it.toUInt() }.map { it.toLong() }.toLongArray() method.instructions.insertBefore(inst, switchInstrumentation(caseValues)) } Opcodes.IDIV -> { @@ -244,69 +248,75 @@ internal class TraceDataFlowInstrumentor( add(LdcInsnNode(random.nextInt(512))) } - private fun longCmpInstrumentation() = InsnList().apply { - pushFakePc() - // traceCmpLong returns the result of the comparison as duplicating two longs on the stack - // is not possible without local variables. - add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceCmpLongWrapper", "(JJI)I", false)) - } + private fun longCmpInstrumentation() = + InsnList().apply { + pushFakePc() + // traceCmpLong returns the result of the comparison as duplicating two longs on the stack + // is not possible without local variables. + add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceCmpLongWrapper", "(JJI)I", false)) + } - private fun intCmpInstrumentation() = InsnList().apply { - add(InsnNode(Opcodes.DUP2)) - pushFakePc() - add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceCmpInt", "(III)V", false)) - } + private fun intCmpInstrumentation() = + InsnList().apply { + add(InsnNode(Opcodes.DUP2)) + pushFakePc() + add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceCmpInt", "(III)V", false)) + } - private fun ifInstrumentation() = InsnList().apply { - add(InsnNode(Opcodes.DUP)) - // All if* instructions are compares to the constant 0. - add(InsnNode(Opcodes.ICONST_0)) - add(InsnNode(Opcodes.SWAP)) - pushFakePc() - add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceConstCmpInt", "(III)V", false)) - } + private fun ifInstrumentation() = + InsnList().apply { + add(InsnNode(Opcodes.DUP)) + // All if* instructions are compares to the constant 0. + add(InsnNode(Opcodes.ICONST_0)) + add(InsnNode(Opcodes.SWAP)) + pushFakePc() + add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceConstCmpInt", "(III)V", false)) + } - private fun intDivInstrumentation() = InsnList().apply { - add(InsnNode(Opcodes.DUP)) - pushFakePc() - add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceDivInt", "(II)V", false)) - } + private fun intDivInstrumentation() = + InsnList().apply { + add(InsnNode(Opcodes.DUP)) + pushFakePc() + add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceDivInt", "(II)V", false)) + } - private fun longDivInstrumentation() = InsnList().apply { - add(InsnNode(Opcodes.DUP2)) - pushFakePc() - add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceDivLong", "(JI)V", false)) - } + private fun longDivInstrumentation() = + InsnList().apply { + add(InsnNode(Opcodes.DUP2)) + pushFakePc() + add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceDivLong", "(JI)V", false)) + } - private fun switchInstrumentation(caseValues: LongArray) = InsnList().apply { - // duplicate {lookup,table}switch key for use as first function argument - add(InsnNode(Opcodes.DUP)) - add(InsnNode(Opcodes.I2L)) - // Set up array with switch case values. The format libfuzzer expects is created here directly, i.e., the first - // two entries are the number of cases and the bit size of values (always 32). - add(IntInsnNode(Opcodes.SIPUSH, caseValues.size + 2)) - add(IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_LONG)) - // Store number of cases - add(InsnNode(Opcodes.DUP)) - add(IntInsnNode(Opcodes.SIPUSH, 0)) - add(LdcInsnNode(caseValues.size.toLong())) - add(InsnNode(Opcodes.LASTORE)) - // Store bit size of keys - add(InsnNode(Opcodes.DUP)) - add(IntInsnNode(Opcodes.SIPUSH, 1)) - add(LdcInsnNode(32.toLong())) - add(InsnNode(Opcodes.LASTORE)) - // Store {lookup,table}switch case values - for ((i, caseValue) in caseValues.withIndex()) { + private fun switchInstrumentation(caseValues: LongArray) = + InsnList().apply { + // duplicate {lookup,table}switch key for use as first function argument add(InsnNode(Opcodes.DUP)) - add(IntInsnNode(Opcodes.SIPUSH, 2 + i)) - add(LdcInsnNode(caseValue)) + add(InsnNode(Opcodes.I2L)) + // Set up array with switch case values. The format libfuzzer expects is created here directly, i.e., the first + // two entries are the number of cases and the bit size of values (always 32). + add(IntInsnNode(Opcodes.SIPUSH, caseValues.size + 2)) + add(IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_LONG)) + // Store number of cases + add(InsnNode(Opcodes.DUP)) + add(IntInsnNode(Opcodes.SIPUSH, 0)) + add(LdcInsnNode(caseValues.size.toLong())) add(InsnNode(Opcodes.LASTORE)) + // Store bit size of keys + add(InsnNode(Opcodes.DUP)) + add(IntInsnNode(Opcodes.SIPUSH, 1)) + add(LdcInsnNode(32.toLong())) + add(InsnNode(Opcodes.LASTORE)) + // Store {lookup,table}switch case values + for ((i, caseValue) in caseValues.withIndex()) { + add(InsnNode(Opcodes.DUP)) + add(IntInsnNode(Opcodes.SIPUSH, 2 + i)) + add(LdcInsnNode(caseValue)) + add(InsnNode(Opcodes.LASTORE)) + } + pushFakePc() + // call the native callback function + add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceSwitch", "(J[JI)V", false)) } - pushFakePc() - // call the native callback function - add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceSwitch", "(J[JI)V", false)) - } /** * Returns true if [node] represents an instruction that possibly pushes a valid, non-zero, constant array index @@ -324,47 +334,54 @@ internal class TraceDataFlowInstrumentor( return MethodInfo(node.owner, node.name, returnType) in GEP_LOAD_METHODS } - private fun gepLoadInstrumentation() = InsnList().apply { - // Duplicate the index and convert to long. - add(InsnNode(Opcodes.DUP)) - add(InsnNode(Opcodes.I2L)) - pushFakePc() - add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceGep", "(JI)V", false)) - } + private fun gepLoadInstrumentation() = + InsnList().apply { + // Duplicate the index and convert to long. + add(InsnNode(Opcodes.DUP)) + add(InsnNode(Opcodes.I2L)) + pushFakePc() + add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceGep", "(JI)V", false)) + } companion object { // Low constants (0, 1) are omitted as they create a lot of noise. - val CONSTANT_INTEGER_PUSH_OPCODES = listOf( - Opcodes.BIPUSH, - Opcodes.SIPUSH, - Opcodes.LDC, - Opcodes.ICONST_2, - Opcodes.ICONST_3, - Opcodes.ICONST_4, - Opcodes.ICONST_5, - ) - - data class MethodInfo(val internalClassName: String, val name: String, val returnType: String) + val CONSTANT_INTEGER_PUSH_OPCODES = + listOf( + Opcodes.BIPUSH, + Opcodes.SIPUSH, + Opcodes.LDC, + Opcodes.ICONST_2, + Opcodes.ICONST_3, + Opcodes.ICONST_4, + Opcodes.ICONST_5, + ) - val GEP_LOAD_METHODS = setOf( - MethodInfo("java/util/AbstractList", "get", "Ljava/lang/Object;"), - MethodInfo("java/util/ArrayList", "get", "Ljava/lang/Object;"), - MethodInfo("java/util/List", "get", "Ljava/lang/Object;"), - MethodInfo("java/util/Stack", "get", "Ljava/lang/Object;"), - MethodInfo("java/util/Vector", "get", "Ljava/lang/Object;"), - MethodInfo("java/lang/CharSequence", "charAt", "C"), - MethodInfo("java/lang/String", "charAt", "C"), - MethodInfo("java/lang/StringBuffer", "charAt", "C"), - MethodInfo("java/lang/StringBuilder", "charAt", "C"), - MethodInfo("java/lang/String", "codePointAt", "I"), - MethodInfo("java/lang/String", "codePointBefore", "I"), - MethodInfo("java/nio/ByteBuffer", "get", "B"), - MethodInfo("java/nio/ByteBuffer", "getChar", "C"), - MethodInfo("java/nio/ByteBuffer", "getDouble", "D"), - MethodInfo("java/nio/ByteBuffer", "getFloat", "F"), - MethodInfo("java/nio/ByteBuffer", "getInt", "I"), - MethodInfo("java/nio/ByteBuffer", "getLong", "J"), - MethodInfo("java/nio/ByteBuffer", "getShort", "S"), + data class MethodInfo( + val internalClassName: String, + val name: String, + val returnType: String, ) + + val GEP_LOAD_METHODS = + setOf( + MethodInfo("java/util/AbstractList", "get", "Ljava/lang/Object;"), + MethodInfo("java/util/ArrayList", "get", "Ljava/lang/Object;"), + MethodInfo("java/util/List", "get", "Ljava/lang/Object;"), + MethodInfo("java/util/Stack", "get", "Ljava/lang/Object;"), + MethodInfo("java/util/Vector", "get", "Ljava/lang/Object;"), + MethodInfo("java/lang/CharSequence", "charAt", "C"), + MethodInfo("java/lang/String", "charAt", "C"), + MethodInfo("java/lang/StringBuffer", "charAt", "C"), + MethodInfo("java/lang/StringBuilder", "charAt", "C"), + MethodInfo("java/lang/String", "codePointAt", "I"), + MethodInfo("java/lang/String", "codePointBefore", "I"), + MethodInfo("java/nio/ByteBuffer", "get", "B"), + MethodInfo("java/nio/ByteBuffer", "getChar", "C"), + MethodInfo("java/nio/ByteBuffer", "getDouble", "D"), + MethodInfo("java/nio/ByteBuffer", "getFloat", "F"), + MethodInfo("java/nio/ByteBuffer", "getInt", "I"), + MethodInfo("java/nio/ByteBuffer", "getLong", "J"), + MethodInfo("java/nio/ByteBuffer", "getShort", "S"), + ) } } diff --git a/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt b/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt index 0a7346538..4f4846f62 100644 --- a/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt +++ b/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt @@ -16,53 +16,62 @@ package com.code_intelligence.jazzer.utils -private val BASE_INCLUDED_CLASS_NAME_GLOBS = listOf( - "**", // everything -) +private val BASE_INCLUDED_CLASS_NAME_GLOBS = + listOf( + "**", // everything + ) // We use both a strong indicator for running as a Bazel test together with an indicator for a // Bazel coverage run to rule out false positives. -private val IS_BAZEL_COVERAGE_RUN = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") != null && - System.getenv("COVERAGE_DIR") != null +private val IS_BAZEL_COVERAGE_RUN = + System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") != null && + System.getenv("COVERAGE_DIR") != null -private val ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE = listOf( - "com.google.testing.coverage.**", - "org.jacoco.**", -) +private val ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE = + listOf( + "com.google.testing.coverage.**", + "org.jacoco.**", + ) -private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf( - // JDK internals - "\\[**", // array types - "java.**", - "javax.**", - "jdk.**", - "sun.**", - "com.sun.**", // package for Proxy objects - // Azul JDK internals - "com.azul.tooling.**", - // Kotlin internals - "kotlin.**", - // Jazzer internals - "com.code_intelligence.jazzer.**", - "jaz.Ter", // safe companion of the honeypot class used by sanitizers - "jaz.Zer", // honeypot class used by sanitizers - // Test and instrumentation tools - "org.junit.**", // dependency of @FuzzTest - "org.mockito.**", // can cause instrumentation cycles - "net.bytebuddy.**", // ignore Byte Buddy, though it's probably shaded - "org.jetbrains.**", // ignore JetBrains products (coverage agent) -) + if (IS_BAZEL_COVERAGE_RUN) ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE else listOf() +private val BASE_EXCLUDED_CLASS_NAME_GLOBS = + listOf( + // JDK internals + "\\[**", // array types + "java.**", + "javax.**", + "jdk.**", + "sun.**", + "com.sun.**", // package for Proxy objects + // Azul JDK internals + "com.azul.tooling.**", + // Kotlin internals + "kotlin.**", + // Jazzer internals + "com.code_intelligence.jazzer.**", + "jaz.Ter", // safe companion of the honeypot class used by sanitizers + "jaz.Zer", // honeypot class used by sanitizers + // Test and instrumentation tools + "org.junit.**", // dependency of @FuzzTest + "org.mockito.**", // can cause instrumentation cycles + "net.bytebuddy.**", // ignore Byte Buddy, though it's probably shaded + "org.jetbrains.**", // ignore JetBrains products (coverage agent) + ) + if (IS_BAZEL_COVERAGE_RUN) ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE else listOf() -class ClassNameGlobber(includes: List, excludes: List) { +class ClassNameGlobber( + includes: List, + excludes: List, +) { // If no include globs are provided, start with all classes. - private val includeMatchers = includes.ifEmpty { BASE_INCLUDED_CLASS_NAME_GLOBS } - .map(::SimpleGlobMatcher) + private val includeMatchers = + includes + .ifEmpty { BASE_INCLUDED_CLASS_NAME_GLOBS } + .map(::SimpleGlobMatcher) // If no include globs are provided, additionally exclude stdlib classes as well as our own classes. - private val excludeMatchers = (if (includes.isEmpty()) BASE_EXCLUDED_CLASS_NAME_GLOBS + excludes else excludes) - .map(::SimpleGlobMatcher) + private val excludeMatchers = + (if (includes.isEmpty()) BASE_EXCLUDED_CLASS_NAME_GLOBS + excludes else excludes) + .map(::SimpleGlobMatcher) - fun includes(className: String): Boolean { - return includeMatchers.any { it.matches(className) } && excludeMatchers.none { it.matches(className) } - } + fun includes(className: String): Boolean = + includeMatchers.any { it.matches(className) } && excludeMatchers.none { it.matches(className) } } diff --git a/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt b/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt index c448c17ef..96bb0300d 100644 --- a/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt +++ b/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt @@ -19,18 +19,19 @@ package com.code_intelligence.jazzer.utils import java.util.jar.Manifest object ManifestUtils { - private const val FUZZ_TARGET_CLASS = "Jazzer-Fuzz-Target-Class" const val HOOK_CLASSES = "Jazzer-Hook-Classes" fun combineManifestValues(attribute: String): List { val manifests = ManifestUtils::class.java.classLoader.getResources("META-INF/MANIFEST.MF") - return manifests.asSequence().mapNotNull { url -> - url.openStream().use { inputStream -> - val manifest = Manifest(inputStream) - manifest.mainAttributes.getValue(attribute) - } - }.toList() + return manifests + .asSequence() + .mapNotNull { url -> + url.openStream().use { inputStream -> + val manifest = Manifest(inputStream) + manifest.mainAttributes.getValue(attribute) + } + }.toList() } /** diff --git a/src/main/java/com/code_intelligence/jazzer/utils/SimpleGlobMatcher.kt b/src/main/java/com/code_intelligence/jazzer/utils/SimpleGlobMatcher.kt index 47a1e09a0..232b3a64b 100644 --- a/src/main/java/com/code_intelligence/jazzer/utils/SimpleGlobMatcher.kt +++ b/src/main/java/com/code_intelligence/jazzer/utils/SimpleGlobMatcher.kt @@ -16,7 +16,9 @@ package com.code_intelligence.jazzer.utils -class SimpleGlobMatcher(val glob: String) { +class SimpleGlobMatcher( + val glob: String, +) { private enum class Type { // foo.bar (matches foo.bar only) FULL_MATCH, diff --git a/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt b/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt index 92076813b..b9ec1a974 100644 --- a/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt +++ b/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt @@ -20,28 +20,30 @@ package com.code_intelligence.jazzer.utils import java.lang.reflect.Executable val Class<*>.readableDescriptor: String - get() = when { - isPrimitive -> { - when (this) { - Boolean::class.javaPrimitiveType -> "boolean" - Byte::class.javaPrimitiveType -> "byte" - Char::class.javaPrimitiveType -> "char" - Short::class.javaPrimitiveType -> "short" - Int::class.javaPrimitiveType -> "int" - Long::class.javaPrimitiveType -> "long" - Float::class.javaPrimitiveType -> "float" - Double::class.javaPrimitiveType -> "double" - java.lang.Void::class.javaPrimitiveType -> "void" - else -> throw IllegalStateException("Unknown primitive type: $name") + get() = + when { + isPrimitive -> { + when (this) { + Boolean::class.javaPrimitiveType -> "boolean" + Byte::class.javaPrimitiveType -> "byte" + Char::class.javaPrimitiveType -> "char" + Short::class.javaPrimitiveType -> "short" + Int::class.javaPrimitiveType -> "int" + Long::class.javaPrimitiveType -> "long" + Float::class.javaPrimitiveType -> "float" + Double::class.javaPrimitiveType -> "double" + java.lang.Void::class.javaPrimitiveType -> "void" + else -> throw IllegalStateException("Unknown primitive type: $name") + } } + isArray -> "${componentType.readableDescriptor}[]" + java.lang.Object::class.java.isAssignableFrom(this) -> name + else -> throw IllegalArgumentException("Unknown class type: $name") } - isArray -> "${componentType.readableDescriptor}[]" - java.lang.Object::class.java.isAssignableFrom(this) -> name - else -> throw IllegalArgumentException("Unknown class type: $name") - } // This does not include the return type as the parameter descriptors already uniquely identify the executable. val Executable.readableDescriptor: String - get() = parameterTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { parameterType -> - parameterType.readableDescriptor - } + get() = + parameterTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { parameterType -> + parameterType.readableDescriptor + } diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt index d122e844a..4273e64b1 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt @@ -21,17 +21,16 @@ import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File -private fun getOriginalAfterHooksTargetInstance(): AfterHooksTargetContract { - return AfterHooksTarget() -} +private fun getOriginalAfterHooksTargetInstance(): AfterHooksTargetContract = AfterHooksTarget() private fun getNoHooksAfterHooksTargetInstance(): AfterHooksTargetContract { val originalBytecode = classToBytecode(AfterHooksTarget::class.java) // Let the bytecode pass through the hooking logic, but don't apply any hooks. - val patchedBytecode = HookInstrumentor(emptyList(), false, null).instrument( - AfterHooksTarget::class.java.name.replace('.', '/'), - originalBytecode, - ) + val patchedBytecode = + HookInstrumentor(emptyList(), false, null).instrument( + AfterHooksTarget::class.java.name.replace('.', '/'), + originalBytecode, + ) val patchedClass = bytecodeToClass(AfterHooksTarget::class.java.name, patchedBytecode) return patchedClass.getDeclaredConstructor().newInstance() as AfterHooksTargetContract } @@ -39,11 +38,12 @@ private fun getNoHooksAfterHooksTargetInstance(): AfterHooksTargetContract { private fun getPatchedAfterHooksTargetInstance(classWithHooksEnabledField: Class<*>?): AfterHooksTargetContract { val originalBytecode = classToBytecode(AfterHooksTarget::class.java) val hooks = Hooks.loadHooks(emptyList(), setOf(AfterHooks::class.java.name)).first().hooks - val patchedBytecode = HookInstrumentor( - hooks, - false, - classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'), - ).instrument(AfterHooksTarget::class.java.name.replace('.', '/'), originalBytecode) + val patchedBytecode = + HookInstrumentor( + hooks, + false, + classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'), + ).instrument(AfterHooksTarget::class.java.name.replace('.', '/'), originalBytecode) // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection. val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") File("$outDir/${AfterHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode) @@ -53,7 +53,6 @@ private fun getPatchedAfterHooksTargetInstance(classWithHooksEnabledField: Class } class AfterHooksPatchTest { - @Test fun testOriginal() { assertSelfCheck(getOriginalAfterHooksTargetInstance(), false) diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt index 971455ffb..60d22aaf4 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt @@ -21,17 +21,16 @@ import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File -private fun getOriginalBeforeHooksTargetInstance(): BeforeHooksTargetContract { - return BeforeHooksTarget() -} +private fun getOriginalBeforeHooksTargetInstance(): BeforeHooksTargetContract = BeforeHooksTarget() private fun getNoHooksBeforeHooksTargetInstance(): BeforeHooksTargetContract { val originalBytecode = classToBytecode(BeforeHooksTarget::class.java) // Let the bytecode pass through the hooking logic, but don't apply any hooks. - val patchedBytecode = HookInstrumentor(emptyList(), false, null).instrument( - BeforeHooksTarget::class.java.name.replace('.', '/'), - originalBytecode, - ) + val patchedBytecode = + HookInstrumentor(emptyList(), false, null).instrument( + BeforeHooksTarget::class.java.name.replace('.', '/'), + originalBytecode, + ) val patchedClass = bytecodeToClass(BeforeHooksTarget::class.java.name, patchedBytecode) return patchedClass.getDeclaredConstructor().newInstance() as BeforeHooksTargetContract } @@ -39,11 +38,12 @@ private fun getNoHooksBeforeHooksTargetInstance(): BeforeHooksTargetContract { private fun getPatchedBeforeHooksTargetInstance(classWithHooksEnabledField: Class<*>?): BeforeHooksTargetContract { val originalBytecode = classToBytecode(BeforeHooksTarget::class.java) val hooks = Hooks.loadHooks(emptyList(), setOf(BeforeHooks::class.java.name)).first().hooks - val patchedBytecode = HookInstrumentor( - hooks, - false, - classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'), - ).instrument(BeforeHooksTarget::class.java.name.replace('.', '/'), originalBytecode) + val patchedBytecode = + HookInstrumentor( + hooks, + false, + classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'), + ).instrument(BeforeHooksTarget::class.java.name.replace('.', '/'), originalBytecode) // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection. val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") File("$outDir/${BeforeHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode) @@ -53,7 +53,6 @@ private fun getPatchedBeforeHooksTargetInstance(classWithHooksEnabledField: Clas } class BeforeHooksPatchTest { - @Test fun testOriginal() { assertSelfCheck(getOriginalBeforeHooksTargetInstance(), false) diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt index 23e2dd4da..a7254febf 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -41,17 +41,16 @@ private fun makeTestable(strategy: EdgeCoverageStrategy): EdgeCoverageStrategy = } } -private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract { - return CoverageInstrumentationTarget() -} +private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract = CoverageInstrumentationTarget() private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract { val originalBytecode = classToBytecode(CoverageInstrumentationTarget::class.java) - val patchedBytecode = EdgeCoverageInstrumentor( - makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy), - MockCoverageMap::class.java, - 0, - ).instrument(CoverageInstrumentationTarget::class.java.name.replace('.', '/'), originalBytecode) + val patchedBytecode = + EdgeCoverageInstrumentor( + makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy), + MockCoverageMap::class.java, + 0, + ).instrument(CoverageInstrumentationTarget::class.java.name.replace('.', '/'), originalBytecode) // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection. val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") File("$outDir/${CoverageInstrumentationTarget::class.java.simpleName}.class").writeBytes(originalBytecode) @@ -66,7 +65,6 @@ private fun assertControlFlow(expectedLocations: List) { @Suppress("unused") class CoverageInstrumentationTest { - private val constructorReturn = 0 private val mapConstructor = 1 @@ -105,26 +103,31 @@ class CoverageInstrumentationTest { val mapControlFlow = listOf(mapConstructor, addFor0, addFor1, addFor2, addFor3, addFor4, addFoobar) val ifControlFlow = listOf(ifTrueBranch, addBlock1, ifEnd) - val forFirstRunControlFlow = mutableListOf().apply { - add(outerForCondition) - repeat(5) { - addAll(listOf(innerForCondition, innerForBodyIfFalseBranch, innerForBodyPutInvocation)) - } - add(outerForIncrementCounter) - }.toList() - val forSecondRunControlFlow = mutableListOf().apply { - add(outerForCondition) - repeat(5) { - addAll(listOf(innerForCondition, innerForBodyIfTrueBranch, innerForBodyPutInvocation)) - } - add(outerForIncrementCounter) - }.toList() + val forFirstRunControlFlow = + mutableListOf() + .apply { + add(outerForCondition) + repeat(5) { + addAll(listOf(innerForCondition, innerForBodyIfFalseBranch, innerForBodyPutInvocation)) + } + add(outerForIncrementCounter) + }.toList() + val forSecondRunControlFlow = + mutableListOf() + .apply { + add(outerForCondition) + repeat(5) { + addAll(listOf(innerForCondition, innerForBodyIfTrueBranch, innerForBodyPutInvocation)) + } + add(outerForIncrementCounter) + }.toList() val forControlFlow = forFirstRunControlFlow + forSecondRunControlFlow - val fooCallControlFlow = listOf( - barAfterPutInvocation, - fooAfterBarInvocation, - afterFooInvocation, - ) + val fooCallControlFlow = + listOf( + barAfterPutInvocation, + fooAfterBarInvocation, + afterFooInvocation, + ) assertControlFlow( listOf(constructorReturn) + mapControlFlow + @@ -149,8 +152,9 @@ class CoverageInstrumentationTest { assertSelfCheck(target) 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() + val expectedCounter = + (lastCounter + 1U).toUByte().takeUnless { it == 0.toUByte() } + ?: (lastCounter + 2U).toUByte() lastCounter = expectedCounter val actualCounter = MockCoverageMap.counters[takenOnEveryRunEdge].toUByte() assertEquals(expectedCounter, actualCounter, "After $i runs:") @@ -160,11 +164,12 @@ class CoverageInstrumentationTest { @Test fun testSpecialCases() { val originalBytecode = classToBytecode(CoverageInstrumentationSpecialCasesTarget::class.java) - val patchedBytecode = EdgeCoverageInstrumentor( - makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy), - MockCoverageMap::class.java, - 0, - ).instrument(CoverageInstrumentationSpecialCasesTarget::class.java.name.replace('.', '/'), originalBytecode) + val patchedBytecode = + EdgeCoverageInstrumentor( + makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy), + MockCoverageMap::class.java, + 0, + ).instrument(CoverageInstrumentationSpecialCasesTarget::class.java.name.replace('.', '/'), originalBytecode) // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection. val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") File("$outDir/${CoverageInstrumentationSpecialCasesTarget::class.simpleName}.class").writeBytes(originalBytecode) diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt index 4695c3600..14453afaf 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt @@ -20,7 +20,6 @@ import org.junit.Test import kotlin.test.assertEquals class DescriptorUtilsTest { - @Test fun testClassDescriptor() { assertEquals("V", java.lang.Void::class.javaPrimitiveType?.descriptor) @@ -38,33 +37,47 @@ class DescriptorUtilsTest { @Test fun testExtractTypeDescriptors() { - val testCases = listOf( - Triple( - String::class.java.getMethod("equals", Object::class.java), - listOf("Ljava/lang/Object;"), - "Z", - ), - Triple( - String::class.java.getMethod("regionMatches", Boolean::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java, Int::class.javaPrimitiveType, Integer::class.javaPrimitiveType), - listOf("Z", "I", "Ljava/lang/String;", "I", "I"), - "Z", - ), - Triple( - String::class.java.getMethod("getChars", Integer::class.javaPrimitiveType, Int::class.javaPrimitiveType, CharArray::class.java, Int::class.javaPrimitiveType), - listOf("I", "I", "[C", "I"), - "V", - ), - Triple( - String::class.java.getMethod("subSequence", Integer::class.javaPrimitiveType, Integer::class.javaPrimitiveType), - listOf("I", "I"), - "Ljava/lang/CharSequence;", - ), - Triple( - String::class.java.getConstructor(), - emptyList(), - "V", - ), - ) + val testCases = + listOf( + Triple( + String::class.java.getMethod("equals", Object::class.java), + listOf("Ljava/lang/Object;"), + "Z", + ), + Triple( + String::class.java.getMethod( + "regionMatches", + Boolean::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + String::class.java, + Int::class.javaPrimitiveType, + Integer::class.javaPrimitiveType, + ), + listOf("Z", "I", "Ljava/lang/String;", "I", "I"), + "Z", + ), + Triple( + String::class.java.getMethod( + "getChars", + Integer::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + CharArray::class.java, + Int::class.javaPrimitiveType, + ), + listOf("I", "I", "[C", "I"), + "V", + ), + Triple( + String::class.java.getMethod("subSequence", Integer::class.javaPrimitiveType, Integer::class.javaPrimitiveType), + listOf("I", "I"), + "Ljava/lang/CharSequence;", + ), + Triple( + String::class.java.getConstructor(), + emptyList(), + "V", + ), + ) for ((executable, parameterDescriptors, returnTypeDescriptor) in testCases) { val descriptor = executable.descriptor assertEquals(extractParameterTypeDescriptors(descriptor), parameterDescriptors) diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt index d4cc35cbc..e100ea744 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt @@ -20,22 +20,26 @@ import java.io.FileOutputStream object PatchTestUtils { @JvmStatic - fun classToBytecode(targetClass: Class<*>): ByteArray { - return ClassLoader + fun classToBytecode(targetClass: Class<*>): ByteArray = + ClassLoader .getSystemClassLoader() .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!! .use { it.readBytes() } - } @JvmStatic - fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> { - return BytecodeClassLoader(name, bytecode).loadClass(name) - } + fun bytecodeToClass( + name: String, + bytecode: ByteArray, + ): Class<*> = BytecodeClassLoader(name, bytecode).loadClass(name) @JvmStatic - fun dumpBytecode(outDir: String, name: String, originalBytecode: ByteArray) { + fun dumpBytecode( + outDir: String, + name: String, + originalBytecode: ByteArray, + ) { FileOutputStream("$outDir/$name.class").use { fos -> fos.write(originalBytecode) } } @@ -43,8 +47,10 @@ object PatchTestUtils { * 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) { + 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) @@ -54,7 +60,10 @@ object PatchTestUtils { } } -fun assertSelfCheck(target: DynamicTestContract, shouldPass: Boolean = true) { +fun assertSelfCheck( + target: DynamicTestContract, + shouldPass: Boolean = true, +) { val results = target.selfCheck() for ((test, passed) in results) { if (shouldPass) { diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt index b2de1fe3e..1c16e79f8 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt @@ -21,17 +21,16 @@ import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File -private fun getOriginalReplaceHooksTargetInstance(): ReplaceHooksTargetContract { - return ReplaceHooksTarget() -} +private fun getOriginalReplaceHooksTargetInstance(): ReplaceHooksTargetContract = ReplaceHooksTarget() private fun getNoHooksReplaceHooksTargetInstance(): ReplaceHooksTargetContract { val originalBytecode = classToBytecode(ReplaceHooksTarget::class.java) // Let the bytecode pass through the hooking logic, but don't apply any hooks. - val patchedBytecode = HookInstrumentor(emptyList(), false, null).instrument( - ReplaceHooksTarget::class.java.name.replace('.', '/'), - originalBytecode, - ) + val patchedBytecode = + HookInstrumentor(emptyList(), false, null).instrument( + ReplaceHooksTarget::class.java.name.replace('.', '/'), + originalBytecode, + ) val patchedClass = bytecodeToClass(ReplaceHooksTarget::class.java.name, patchedBytecode) return patchedClass.getDeclaredConstructor().newInstance() as ReplaceHooksTargetContract } @@ -39,11 +38,12 @@ private fun getNoHooksReplaceHooksTargetInstance(): ReplaceHooksTargetContract { private fun getPatchedReplaceHooksTargetInstance(classWithHooksEnabledField: Class<*>?): ReplaceHooksTargetContract { val originalBytecode = classToBytecode(ReplaceHooksTarget::class.java) val hooks = Hooks.loadHooks(emptyList(), setOf(ReplaceHooks::class.java.name)).first().hooks - val patchedBytecode = HookInstrumentor( - hooks, - false, - classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'), - ).instrument(ReplaceHooksTarget::class.java.name.replace('.', '/'), originalBytecode) + val patchedBytecode = + HookInstrumentor( + hooks, + false, + classWithHooksEnabledField = classWithHooksEnabledField?.name?.replace('.', '/'), + ).instrument(ReplaceHooksTarget::class.java.name.replace('.', '/'), originalBytecode) // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection. val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") File("$outDir/${ReplaceHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode) @@ -53,7 +53,6 @@ private fun getPatchedReplaceHooksTargetInstance(classWithHooksEnabledField: Cla } class ReplaceHooksPatchTest { - @Test fun testOriginal() { assertSelfCheck(getOriginalReplaceHooksTargetInstance(), false) diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt index 36c699911..074486270 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt @@ -21,20 +21,19 @@ import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File -private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract { - return TraceDataFlowInstrumentationTarget() -} +private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract = TraceDataFlowInstrumentationTarget() private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract { val originalBytecode = classToBytecode(TraceDataFlowInstrumentationTarget::class.java) - val patchedBytecode = TraceDataFlowInstrumentor( - setOf( - InstrumentationType.CMP, - InstrumentationType.DIV, - InstrumentationType.GEP, - ), - MockTraceDataFlowCallbacks::class.java.name.replace('.', '/'), - ).instrument(TraceDataFlowInstrumentationTarget::class.java.name.replace('.', '/'), originalBytecode) + val patchedBytecode = + TraceDataFlowInstrumentor( + setOf( + InstrumentationType.CMP, + InstrumentationType.DIV, + InstrumentationType.GEP, + ), + MockTraceDataFlowCallbacks::class.java.name.replace('.', '/'), + ).instrument(TraceDataFlowInstrumentationTarget::class.java.name.replace('.', '/'), originalBytecode) // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection. val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") File("$outDir/${TraceDataFlowInstrumentationTarget::class.simpleName}.class").writeBytes(originalBytecode) @@ -44,7 +43,6 @@ private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract } class TraceDataFlowInstrumentationTest { - @Test fun testOriginal() { MockTraceDataFlowCallbacks.init() @@ -101,7 +99,6 @@ class TraceDataFlowInstrumentationTest { // shortArray[8] == 8 "GEP: 8", "ICMP: 8, 8", - "GEP: 2", "GEP: 3", "GEP: 4", diff --git a/tests/src/test/java/com/example/KotlinStringCompareFuzzer.kt b/tests/src/test/java/com/example/KotlinStringCompareFuzzer.kt index e2a19b783..312b9ee9e 100644 --- a/tests/src/test/java/com/example/KotlinStringCompareFuzzer.kt +++ b/tests/src/test/java/com/example/KotlinStringCompareFuzzer.kt @@ -25,7 +25,8 @@ object KotlinStringCompareFuzzer { @OptIn(ExperimentalEncodingApi::class) fun fuzzerTestOneInput(data: ByteArray) { val text = Base64.encode(data) - if (text.startsWith("aGVsbG8K") && // hello + if (text.startsWith("aGVsbG8K") && + // hello text.endsWith("d29ybGQK") // world ) { throw IOException("Found the secret message!") diff --git a/tests/src/test/java/com/example/KotlinVararg.kt b/tests/src/test/java/com/example/KotlinVararg.kt index b4933c587..e7f82772c 100644 --- a/tests/src/test/java/com/example/KotlinVararg.kt +++ b/tests/src/test/java/com/example/KotlinVararg.kt @@ -16,7 +16,9 @@ package com.example -class KotlinVararg(vararg opts: String) { +class KotlinVararg( + vararg opts: String, +) { private val allOpts = opts.toList().joinToString(", ") fun doStuff() = allOpts From f53b1f74a77c1ec183c5b6f28f2c5de40a1b6fc4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sat, 18 Jan 2025 11:53:00 +0100 Subject: [PATCH 4/4] Suppress ktlint issues without automatic fixes --- .../com/code_intelligence/jazzer/sanitizers/LdapInjection.kt | 1 + .../code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt | 1 + .../jazzer/instrumentor/BeforeHooksPatchTest.kt | 1 + .../jazzer/instrumentor/ReplaceHooksPatchTest.kt | 1 + 4 files changed, 4 insertions(+) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt index 087c93014..b0c6f1de0 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt @@ -50,6 +50,7 @@ object LdapInjection { // Characters to escape in search filter queries private const val FILTER_CHARACTERS = "*()\\\u0000" + @Suppress("ktlint:standard:max-line-length") @MethodHooks( // Single object lookup, possible DN injection MethodHook( diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt index 4273e64b1..fd691f205 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt @@ -52,6 +52,7 @@ private fun getPatchedAfterHooksTargetInstance(classWithHooksEnabledField: Class return patchedClass.getDeclaredConstructor().newInstance() as AfterHooksTargetContract } +@Suppress("ktlint:standard:property-naming") class AfterHooksPatchTest { @Test fun testOriginal() { diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt index 60d22aaf4..a5fb4c68a 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt @@ -52,6 +52,7 @@ private fun getPatchedBeforeHooksTargetInstance(classWithHooksEnabledField: Clas return patchedClass.getDeclaredConstructor().newInstance() as BeforeHooksTargetContract } +@Suppress("ktlint:standard:property-naming") class BeforeHooksPatchTest { @Test fun testOriginal() { diff --git a/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt index 1c16e79f8..072571f57 100644 --- a/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt +++ b/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt @@ -52,6 +52,7 @@ private fun getPatchedReplaceHooksTargetInstance(classWithHooksEnabledField: Cla return patchedClass.getDeclaredConstructor().newInstance() as ReplaceHooksTargetContract } +@Suppress("ktlint:standard:property-naming") class ReplaceHooksPatchTest { @Test fun testOriginal() {