diff options
Diffstat (limited to 'agent/src/main/java/com/code_intelligence/jazzer/utils')
5 files changed, 0 insertions, 463 deletions
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel deleted file mode 100644 index 10e3477c..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel +++ /dev/null @@ -1,15 +0,0 @@ -load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") - -kt_jvm_library( - name = "utils", - srcs = [ - "ClassNameGlobber.kt", - "ExceptionUtils.kt", - "ManifestUtils.kt", - "Utils.kt", - ], - visibility = ["//visibility:public"], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - ], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt deleted file mode 100644 index 44249c81..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2021 Code Intelligence GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.code_intelligence.jazzer.utils - -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 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 -) + if (IS_BAZEL_COVERAGE_RUN) ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE else listOf() - -class ClassNameGlobber(includes: List<String>, excludes: List<String>) { - // If no include globs are provided, start with all classes. - 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) - - fun includes(className: String): Boolean { - return includeMatchers.any { it.matches(className) } && excludeMatchers.none { it.matches(className) } - } -} - -class SimpleGlobMatcher(val glob: String) { - private enum class Type { - // foo.bar (matches foo.bar only) - FULL_MATCH, - // foo.** (matches foo.bar and foo.bar.baz) - PATH_WILDCARD_SUFFIX, - // foo.* (matches foo.bar, but not foo.bar.baz) - SEGMENT_WILDCARD_SUFFIX, - } - - private val type: Type - private val prefix: String - - init { - // Remain compatible with globs such as "\\[" that use escaping. - val pattern = glob.replace("\\", "") - when { - !pattern.contains('*') -> { - type = Type.FULL_MATCH - prefix = pattern - } - // Ends with "**" and contains no other '*'. - pattern.endsWith("**") && pattern.indexOf('*') == pattern.length - 2 -> { - type = Type.PATH_WILDCARD_SUFFIX - prefix = pattern.removeSuffix("**") - } - // Ends with "*" and contains no other '*'. - pattern.endsWith('*') && pattern.indexOf('*') == pattern.length - 1 -> { - type = Type.SEGMENT_WILDCARD_SUFFIX - prefix = pattern.removeSuffix("*") - } - else -> throw IllegalArgumentException( - "Unsupported glob pattern (only foo.bar, foo.* and foo.** are supported): $pattern" - ) - } - } - - /** - * Checks whether [maybeInternalClassName], which may be internal (foo/bar) or not (foo.bar), matches [glob]. - */ - fun matches(maybeInternalClassName: String): Boolean { - val className = maybeInternalClassName.replace('/', '.') - return when (type) { - Type.FULL_MATCH -> className == prefix - Type.PATH_WILDCARD_SUFFIX -> className.startsWith(prefix) - Type.SEGMENT_WILDCARD_SUFFIX -> { - // className starts with prefix and contains no further '.'. - className.startsWith(prefix) && - className.indexOf('.', startIndex = prefix.length) == -1 - } - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt deleted file mode 100644 index 30f6fb30..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2021 Code Intelligence GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@file:JvmName("ExceptionUtils") - -package com.code_intelligence.jazzer.utils - -import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow -import java.lang.management.ManagementFactory -import java.nio.ByteBuffer -import java.security.MessageDigest - -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). - var rootCause = throwable - if (passToRootCause) { - while (true) { - rootCause = rootCause.cause ?: break - } - } - update(rootCause.javaClass.name.toByteArray()) - for (element in rootCause.stackTrace) { - update(element.toString().toByteArray()) - } - if (throwable.suppressed.isNotEmpty()) { - update("suppressed".toByteArray()) - for (suppressed in throwable.suppressed) { - update(hash(suppressed, passToRootCause)) - } - } - digest() - } - -/** - * Computes a hash of the stack trace of [throwable] without messages. - * - * The hash can be used to deduplicate stack traces obtained on crashes. By not including the - * messages, this hash should not depend on the precise crashing input. - */ -fun computeDedupToken(throwable: Throwable): Long { - var passToRootCause = true - if (throwable is FuzzerSecurityIssueLow && throwable.cause is StackOverflowError) { - // Special handling for StackOverflowErrors as processed by preprocessThrowable: - // Only consider the repeated part of the stack trace and ignore the original stack trace in - // the cause. - passToRootCause = false - } - return ByteBuffer.wrap(hash(throwable, passToRootCause)).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<StackTraceElement>() - val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame -> - (frame !in observedFrames).also { observedFrames.add(frame) } - } - FuzzerSecurityIssueLow("Stack overflow (use '${getReproducingXssArg()}' to reproduce)", throwable).apply { - stackTrace = bottomFramesWithoutRepetition.toTypedArray() - } - } - is OutOfMemoryError -> stripOwnStackTrace( - FuzzerSecurityIssueLow( - "Out of memory (use '${getReproducingXmxArg()}' to reproduce)", throwable - ) - ) - is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable)) - else -> 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() -} - -/** - * Returns a valid `-Xmx` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can - * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version). - */ -private fun getReproducingXmxArg(): String? { - val maxHeapSizeInMegaBytes = (getNumericFinalFlagValue("MaxHeapSize") ?: return null) shr 20 - val conservativeMaxHeapSizeInMegaBytes = (maxHeapSizeInMegaBytes * 0.9).toInt() - return "-Xmx${conservativeMaxHeapSizeInMegaBytes}m" -} - -/** - * Returns a valid `-Xss` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can - * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version). - */ -private fun getReproducingXssArg(): String? { - val threadStackSizeInKiloBytes = getNumericFinalFlagValue("ThreadStackSize") ?: return null - val conservativeThreadStackSizeInKiloBytes = (threadStackSizeInKiloBytes * 0.9).toInt() - return "-Xss${conservativeThreadStackSizeInKiloBytes}k" -} - -private fun getNumericFinalFlagValue(arg: String): Long? { - val argPattern = "$arg\\D*(\\d*)".toRegex() - return argPattern.find(javaFullFinalFlags ?: return null)?.groupValues?.get(1)?.toLongOrNull() -} - -private val javaFullFinalFlags by lazy { - readJavaFullFinalFlags() -} - -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() - return javaPrintFlagsProcess.inputStream.bufferedReader().useLines { lineSequence -> - lineSequence - .filter { it.contains("ThreadStackSize") || it.contains("MaxHeapSize") } - .joinToString("\n") - } -} - -fun dumpAllStackTraces() { - System.err.println("\nStack traces of all JVM threads:\n") - for ((thread, stack) in Thread.getAllStackTraces()) { - System.err.println(thread) - // Remove traces of this method and the methods it calls. - stack.asList() - .asReversed() - .takeWhile { - !( - it.className == "com.code_intelligence.jazzer.runtime.ExceptionUtils" && - it.methodName == "dumpAllStackTraces" - ) - } - .asReversed() - .forEach { frame -> - System.err.println("\tat $frame") - } - System.err.println() - } - System.err.println("Garbage collector stats:") - System.err.println( - ManagementFactory.getGarbageCollectorMXBeans().joinToString("\n", "\n", "\n") { - "${it.name}: ${it.collectionCount} collections took ${it.collectionTime}ms" - } - ) -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt deleted file mode 100644 index e7165e55..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021 Code Intelligence GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.code_intelligence.jazzer.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<String> { - val manifests = ClassLoader.getSystemResources("META-INF/MANIFEST.MF") - return manifests.asSequence().mapNotNull { url -> - url.openStream().use { inputStream -> - val manifest = Manifest(inputStream) - manifest.mainAttributes.getValue(attribute) - } - }.toList() - } - - /** - * Returns the value of the `Fuzz-Target-Class` manifest attribute if there is a unique one among all manifest - * files in the classpath. - */ - @JvmStatic - fun detectFuzzTargetClass(): String? { - val fuzzTargets = combineManifestValues(FUZZ_TARGET_CLASS) - return when (fuzzTargets.size) { - 0 -> null - 1 -> fuzzTargets.first() - else -> { - println( - """ - |WARN: More than one Jazzer-Fuzz-Target-Class manifest entry detected on the - |classpath.""".trimMargin() - ) - null - } - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt deleted file mode 100644 index 1b399baf..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2021 Code Intelligence GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -@file:JvmName("Utils") - -package com.code_intelligence.jazzer.utils - -import java.lang.reflect.Executable -import java.lang.reflect.Method -import java.nio.ByteBuffer -import java.nio.channels.FileChannel - -val Class<*>.descriptor: String - get() = when { - isPrimitive -> { - when (this) { - Boolean::class.javaPrimitiveType -> "Z" - Byte::class.javaPrimitiveType -> "B" - Char::class.javaPrimitiveType -> "C" - Short::class.javaPrimitiveType -> "S" - Int::class.javaPrimitiveType -> "I" - Long::class.javaPrimitiveType -> "J" - Float::class.javaPrimitiveType -> "F" - Double::class.javaPrimitiveType -> "D" - java.lang.Void::class.javaPrimitiveType -> "V" - else -> throw IllegalStateException("Unknown primitive type: $name") - } - } - isArray -> "[${componentType.descriptor}" - java.lang.Object::class.java.isAssignableFrom(this) -> "L${name.replace('.', '/')};" - else -> throw IllegalArgumentException("Unknown class type: $name") - } - -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") - } - } - isArray -> "${componentType.readableDescriptor}[]" - java.lang.Object::class.java.isAssignableFrom(this) -> name - else -> throw IllegalArgumentException("Unknown class type: $name") - } - -val Executable.descriptor: String - get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")") { parameterType -> - parameterType.descriptor - } + if (this is Method) returnType.descriptor else "V" - -// 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 - } - -fun simpleFastHash(vararg strings: String): Int { - var hash = 0 - for (string in strings) { - for (c in string) { - hash = hash * 11 + c.code - } - } - return hash -} - -/** - * Reads the [FileChannel] to the end as a UTF-8 string. - */ -fun FileChannel.readFully(): String { - check(size() <= Int.MAX_VALUE) - val buffer = ByteBuffer.allocate(size().toInt()) - while (buffer.hasRemaining()) { - when (read(buffer)) { - 0 -> throw IllegalStateException("No bytes read") - -1 -> break - } - } - return String(buffer.array()) -} - -/** - * Appends [string] to the end of the [FileChannel]. - */ -fun FileChannel.append(string: String) { - position(size()) - write(ByteBuffer.wrap(string.toByteArray())) -} |