aboutsummaryrefslogtreecommitdiff
path: root/agent/src/main/java/com/code_intelligence/jazzer/utils
diff options
context:
space:
mode:
Diffstat (limited to 'agent/src/main/java/com/code_intelligence/jazzer/utils')
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel15
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt115
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt172
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt54
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt107
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()))
-}