diff options
Diffstat (limited to 'agent/src/main/java/com/code_intelligence/jazzer')
66 files changed, 0 insertions, 7784 deletions
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt deleted file mode 100644 index f9b026f1..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ /dev/null @@ -1,171 +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("Agent") - -package com.code_intelligence.jazzer.agent - -import com.code_intelligence.jazzer.driver.Opt -import com.code_intelligence.jazzer.instrumentor.CoverageRecorder -import com.code_intelligence.jazzer.instrumentor.Hooks -import com.code_intelligence.jazzer.instrumentor.InstrumentationType -import com.code_intelligence.jazzer.runtime.NativeLibHooks -import com.code_intelligence.jazzer.runtime.TraceCmpHooks -import com.code_intelligence.jazzer.runtime.TraceDivHooks -import com.code_intelligence.jazzer.runtime.TraceIndirHooks -import com.code_intelligence.jazzer.utils.ClassNameGlobber -import com.code_intelligence.jazzer.utils.ManifestUtils -import java.io.File -import java.lang.instrument.Instrumentation -import java.net.URI -import java.nio.file.Paths -import java.util.jar.JarFile -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.exists -import kotlin.io.path.isDirectory - -private object AgentJarFinder { - val agentJarFile = jarUriForClass(AgentJarFinder::class.java)?.let { JarFile(File(it)) } -} - -fun jarUriForClass(clazz: Class<*>): URI? { - return clazz.protectionDomain?.codeSource?.location?.toURI() -} - -@OptIn(ExperimentalPathApi::class) -@Suppress("UNUSED_PARAMETER") -fun premain(agentArgs: String?, instrumentation: Instrumentation) { - // Add the agent jar (i.e., the jar out of which we are currently executing) to the search path of the bootstrap - // class loader to ensure that instrumented classes can find the CoverageMap class regardless of which ClassLoader - // they are using. - if (AgentJarFinder.agentJarFile != null) { - instrumentation.appendToBootstrapClassLoaderSearch(AgentJarFinder.agentJarFile) - } else { - println("WARN: Failed to add agent JAR to bootstrap class loader search path") - } - - val manifestCustomHookNames = - ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { - it.split(':') - }.filter { it.isNotBlank() } - val allCustomHookNames = (manifestCustomHookNames + Opt.customHooks).toSet() - val disabledCustomHookNames = Opt.disabledHooks.toSet() - val customHookNames = allCustomHookNames - disabledCustomHookNames - val disabledCustomHooksToPrint = allCustomHookNames - customHookNames.toSet() - if (disabledCustomHooksToPrint.isNotEmpty()) { - println("INFO: Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}") - } - - val classNameGlobber = ClassNameGlobber(Opt.instrumentationIncludes, Opt.instrumentationExcludes + customHookNames) - CoverageRecorder.classNameGlobber = classNameGlobber - val customHookClassNameGlobber = ClassNameGlobber(Opt.customHookIncludes, Opt.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 = (Opt.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 idSyncFile = Opt.idSyncFile.takeUnless { it.isEmpty() }?.let { - Paths.get(it).also { path -> - println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}") - } - } - val dumpClassesDir = Opt.dumpClassesDir.takeUnless { it.isEmpty() }?.let { - Paths.get(it).toAbsolutePath().also { path -> - if (path.exists() && path.isDirectory()) { - println("INFO: Dumping instrumented classes into $path") - } else { - println("ERROR: Cannot dump instrumented classes into $path; does not exist or not a directory") - } - } - } - val includedHookNames = instrumentationTypes - .mapNotNull { type -> - when (type) { - InstrumentationType.CMP -> TraceCmpHooks::class.java.name - InstrumentationType.DIV -> TraceDivHooks::class.java.name - InstrumentationType.INDIR -> TraceIndirHooks::class.java.name - InstrumentationType.NATIVE -> NativeLibHooks::class.java.name - else -> null - } - } - val coverageIdSynchronizer = if (idSyncFile != null) - FileSyncCoverageIdStrategy(idSyncFile) - else - MemSyncCoverageIdStrategy() - - val (includedHooks, customHooks) = Hooks.loadHooks(includedHookNames.toSet(), customHookNames.toSet()) - // 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 - // library classes. These classes are loaded by the bootstrap / system class loader and would - // not be considered when resolving references to hook methods, leading to NoClassDefFoundError - // being thrown. - customHooks.hookClasses - .mapNotNull { jarUriForClass(it) } - .toSet() - .map { JarFile(File(it)) } - .forEach { instrumentation.appendToBootstrapClassLoaderSearch(it) } - - val runtimeInstrumentor = RuntimeInstrumentor( - instrumentation, - classNameGlobber, - customHookClassNameGlobber, - instrumentationTypes, - includedHooks.hooks, - customHooks.hooks, - customHooks.additionalHookClassNameGlobber, - coverageIdSynchronizer, - dumpClassesDir, - ) - - // 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 { - classNameGlobber.includes(it.name) || - customHookClassNameGlobber.includes(it.name) || - customHooks.additionalHookClassNameGlobber.includes(it.name) - } - .filter { - instrumentation.isModifiableClass(it) - } - .toTypedArray() - - instrumentation.addTransformer(runtimeInstrumentor, true) - - if (classesToRetransform.isNotEmpty()) { - if (instrumentation.isRetransformClassesSupported) { - instrumentation.retransformClasses(*classesToRetransform) - } else { - println("WARN: Instrumentation was not applied to the following classes as they are dependencies of hooks:") - println("WARN: ${classesToRetransform.joinToString()}") - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel deleted file mode 100644 index db6ae264..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") - -kt_jvm_library( - name = "agent_lib", - srcs = [ - "Agent.kt", - "CoverageIdStrategy.kt", - "RuntimeInstrumentor.kt", - ], - visibility = ["//visibility:public"], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", - "//agent/src/main/java/com/code_intelligence/jazzer/runtime", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:opt", - ], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt deleted file mode 100644 index 5d1d28e3..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt +++ /dev/null @@ -1,200 +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.agent - -import com.code_intelligence.jazzer.utils.append -import com.code_intelligence.jazzer.utils.readFully -import java.nio.channels.FileChannel -import java.nio.channels.FileLock -import java.nio.file.Path -import java.nio.file.StandardOpenOption -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) - -/** - * [CoverageIdStrategy] provides an abstraction to switch between context specific coverage ID generation. - * - * Coverage (i.e., edge) IDs differ from other kinds of IDs, such as those generated for call sites or cmp - * instructions, in that they should be consecutive, collision-free, and lie in a known, small range. - * 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) -} - -/** - * A memory synced strategy for coverage ID generation. - * - * This strategy uses a synchronized block to guard access to a global edge ID counter. - * Even though concurrent fuzzing is not fully supported this strategy enables consistent coverage - * IDs in case of concurrent class loading. - * - * It only prevents races within one VM instance. - */ -class MemSyncCoverageIdStrategy : CoverageIdStrategy { - private var nextEdgeId = 0 - - @Synchronized - override fun withIdForClass(className: String, block: (Int) -> Int) { - nextEdgeId += block(nextEdgeId) - } -} - -/** - * A strategy for coverage ID generation that synchronizes the IDs assigned to a class with other processes via the - * specified [idSyncFile]. - * 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 { - private val uuid: UUID = UUID.randomUUID() - private var idFileLock: FileLock? = null - - private var cachedFirstId: Int? = null - private var cachedClassName: String? = null - private var cachedIdCount: Int? = null - - /** - * This method is synchronized to prevent concurrent access to the internal file lock which would result in - * [java.nio.channels.OverlappingFileLockException]. Furthermore, every coverage ID obtained by [obtainFirstId] - * is always committed back again to the sync file by [commitIdCount]. - */ - @Synchronized - override fun withIdForClass(className: String, block: (Int) -> Int) { - var actualNumEdgeIds = 0 - try { - val firstId = obtainFirstId(className) - actualNumEdgeIds = block(firstId) - } finally { - commitIdCount(actualNumEdgeIds) - } - } - - /** - * Obtains a coverage ID for [className] such that all cooperating agent processes will obtain the same ID. - * There are two cases to consider: - * - This agent process is the first to encounter [className], i.e., it does not find a record for that class in - * [idSyncFile]. In this case, a lock on the file is held until the class has been instrumented and a record with - * the required number of coverage IDs has been added. - * - Another agent process has already encountered [className], i.e., there is a record that class in [idSyncFile]. - * In this case, the lock on the file is returned immediately and the extracted first coverage ID is returned to - * the caller. The caller is still expected to call [commitIdCount] so that desynchronization can be detected. - */ - 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 - ) - // 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 - // <class name>:<first ID>:<num IDs> - 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 '<class name>:<first ID>:<num IDs>:<uuid>', 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) { - 0 -> { - // We are the first to encounter this class and thus need to hold the lock until the class has been - // instrumented and we know the required number of coverage IDs. - idFileLock = localIdFileLock - // Compute the next free ID as the maximum over the sums of first ID and ID count, starting at 0 if - // this is the first ID to be assigned. In fact, since this is the only way new lines are added to - // the file, the maximum is always attained by the last line. - val nextFreeId = idInfo.asSequence().map { it.second + it.third }.lastOrNull() ?: 0 - cachedFirstId = nextFreeId - nextFreeId - } - 1 -> { - // This class has already been instrumented elsewhere, so we just return the first ID and ID count - // reported from there and release the lock right away. The caller is still expected to call - // commitIdCount. - localIdFile.close() - cachedIdCount = idInfoForClass.single().third - idInfoForClass.single().second - } - else -> { - localIdFile.close() - System.err.println(idInfo.joinToString("\n") { "${it.first}:${it.second}:${it.third}" }) - throw IllegalStateException("Multiple entries for $className in ID file") - } - } - } catch (e: Exception) { - throw CoverageIdException(e) - } - } - - /** - * Records the number of coverage IDs used to instrument the class specified in a previous call to [obtainFirstId]. - * If instrumenting the class should fail, this function must still be called. In this case, [idCount] is set to 0. - */ - private fun commitIdCount(idCount: Int) { - val localIdFileLock = idFileLock - try { - check(cachedClassName != null) - if (localIdFileLock == null) { - // We released the lock already in obtainFirstId since the class had already been instrumented - // elsewhere. As we know the expected number of IDs for the current class in this case, check for - // deviations. - check(cachedIdCount != null) - check(idCount == cachedIdCount) { - "$cachedClassName has $idCount edges, but $cachedIdCount edges reserved in ID file" - } - } else { - // We are the first to instrument this class and should record the number of IDs in the sync file. - check(cachedFirstId != null) - localIdFileLock.channel().append("$cachedClassName:$cachedFirstId:$idCount:$uuid\n") - localIdFileLock.channel().force(true) - } - idFileLock = null - cachedFirstId = null - cachedIdCount = null - cachedClassName = null - } catch (e: Exception) { - throw CoverageIdException(e) - } finally { - localIdFileLock?.channel()?.close() - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt deleted file mode 100644 index fe2efd54..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ /dev/null @@ -1,181 +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.agent - -import com.code_intelligence.jazzer.instrumentor.ClassInstrumentor -import com.code_intelligence.jazzer.instrumentor.CoverageRecorder -import com.code_intelligence.jazzer.instrumentor.Hook -import com.code_intelligence.jazzer.instrumentor.InstrumentationType -import com.code_intelligence.jazzer.utils.ClassNameGlobber -import java.lang.instrument.ClassFileTransformer -import java.lang.instrument.Instrumentation -import java.nio.file.Path -import java.security.ProtectionDomain -import kotlin.math.roundToInt -import kotlin.system.exitProcess -import kotlin.time.measureTimedValue - -class RuntimeInstrumentor( - private val instrumentation: Instrumentation, - private val classesToFullyInstrument: ClassNameGlobber, - private val classesToHookInstrument: ClassNameGlobber, - private val instrumentationTypes: Set<InstrumentationType>, - private val includedHooks: List<Hook>, - private val customHooks: List<Hook>, - // Dedicated name globber for additional classes to hook stated in hook annotations is needed due to - // existing include and exclude pattern of classesToHookInstrument. All classes are included in hook - // instrumentation except the ones from default excludes, like JDK and Kotlin classes. But additional - // classes to hook, based on annotations, are allowed to reference normally ignored ones, like JDK - // and Kotlin internals. - // FIXME: Adding an additional class to hook will apply _all_ hooks to it and not only the one it's - // defined in. At some point we might want to track the list of classes per custom hook rather than globally. - private val additionalClassesToHookInstrument: ClassNameGlobber, - private val coverageIdSynchronizer: CoverageIdStrategy, - private val dumpClassesDir: Path?, -) : ClassFileTransformer { - - @OptIn(kotlin.time.ExperimentalTime::class) - override fun transform( - loader: ClassLoader?, - internalClassName: String, - classBeingRedefined: Class<*>?, - protectionDomain: ProtectionDomain?, - classfileBuffer: ByteArray, - ): ByteArray? { - return try { - // Bail out early if we would instrument ourselves. This prevents ClassCircularityErrors as we might need to - // load additional Jazzer classes until we reach the full exclusion logic. - if (internalClassName.startsWith("com/code_intelligence/jazzer/")) - return null - transformInternal(internalClassName, classfileBuffer) - } catch (t: Throwable) { - // Throwables raised from transform are silently dropped, making it extremely hard to detect instrumentation - // failures. The docs advise to use a top-level try-catch. - // https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/ClassFileTransformer.html - t.printStackTrace() - throw t - }.also { instrumentedByteCode -> - // Only dump classes that were instrumented. - if (instrumentedByteCode != null && dumpClassesDir != null) { - dumpToClassFile(internalClassName, instrumentedByteCode) - dumpToClassFile(internalClassName, classfileBuffer, basenameSuffix = ".original") - } - } - } - - private fun dumpToClassFile(internalClassName: String, bytecode: ByteArray, basenameSuffix: String = "") { - val relativePath = "$internalClassName$basenameSuffix.class" - val absolutePath = dumpClassesDir!!.resolve(relativePath) - val dumpFile = absolutePath.toFile() - dumpFile.parentFile.mkdirs() - dumpFile.writeBytes(bytecode) - } - - override fun transform( - module: Module?, - loader: ClassLoader?, - internalClassName: String, - classBeingRedefined: Class<*>?, - protectionDomain: ProtectionDomain?, - classfileBuffer: ByteArray - ): ByteArray? { - return try { - if (module != null && !module.canRead(RuntimeInstrumentor::class.java.module)) { - // Make all other modules read our (unnamed) module, which allows them to access the classes needed by the - // instrumentations, e.g. CoverageMap. If a module can't be modified, it should not be instrumented as the - // injected bytecode might throw NoClassDefFoundError. - // https://mail.openjdk.java.net/pipermail/jigsaw-dev/2021-May/014663.html - if (!instrumentation.isModifiableModule(module)) { - val prettyClassName = internalClassName.replace('/', '.') - println("WARN: Failed to instrument $prettyClassName in unmodifiable module ${module.name}, skipping") - return null - } - instrumentation.redefineModule( - module, - /* extraReads */ setOf(RuntimeInstrumentor::class.java.module), - emptyMap(), - emptyMap(), - emptySet(), - emptyMap() - ) - } - transform(loader, internalClassName, classBeingRedefined, protectionDomain, classfileBuffer) - } catch (t: Throwable) { - // Throwables raised from transform are silently dropped, making it extremely hard to detect instrumentation - // failures. The docs advise to use a top-level try-catch. - // https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/ClassFileTransformer.html - t.printStackTrace() - throw t - } - } - - @OptIn(kotlin.time.ExperimentalTime::class) - fun transformInternal(internalClassName: String, classfileBuffer: ByteArray): ByteArray? { - val fullInstrumentation = when { - classesToFullyInstrument.includes(internalClassName) -> true - classesToHookInstrument.includes(internalClassName) -> false - additionalClassesToHookInstrument.includes(internalClassName) -> false - else -> return null - } - val prettyClassName = internalClassName.replace('/', '.') - val (instrumentedBytecode, duration) = measureTimedValue { - try { - instrument(internalClassName, classfileBuffer, fullInstrumentation) - } catch (e: CoverageIdException) { - System.err.println("ERROR: Coverage IDs are out of sync") - e.printStackTrace() - exitProcess(1) - } catch (e: Exception) { - println("WARN: Failed to instrument $prettyClassName, skipping") - e.printStackTrace() - return null - } - } - val durationInMs = duration.inWholeMilliseconds - val sizeIncrease = ((100.0 * (instrumentedBytecode.size - classfileBuffer.size)) / classfileBuffer.size).roundToInt() - if (fullInstrumentation) { - println("INFO: Instrumented $prettyClassName (took $durationInMs ms, size +$sizeIncrease%)") - } else { - println("INFO: Instrumented $prettyClassName with custom hooks only (took $durationInMs ms, size +$sizeIncrease%)") - } - return instrumentedBytecode - } - - private fun instrument(internalClassName: String, bytecode: ByteArray, fullInstrumentation: Boolean): ByteArray { - return ClassInstrumentor(bytecode).run { - if (fullInstrumentation) { - // Hook instrumentation must be performed after data flow tracing as the injected - // bytecode would trigger the GEP callbacks for byte[]. Coverage instrumentation - // must be performed after hook instrumentation as the injected bytecode would - // trigger the GEP callbacks for ByteBuffer. - traceDataFlow(instrumentationTypes) - hooks(includedHooks + customHooks) - coverageIdSynchronizer.withIdForClass(internalClassName) { firstId -> - coverage(firstId).also { actualNumEdgeIds -> - CoverageRecorder.recordInstrumentedClass( - internalClassName, - bytecode, - firstId, - actualNumEdgeIds - ) - } - } - } else { - hooks(customHooks) - } - instrumentedBytecode - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzConstructionException.java b/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzConstructionException.java deleted file mode 100644 index 93340ee8..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzConstructionException.java +++ /dev/null @@ -1,32 +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.api; - -// An exception wrapping a Throwable thrown during the construction of parameters for, but not the -// actual invocation of an autofuzzed method. -/** - * Only used internally. - */ -public class AutofuzzConstructionException extends RuntimeException { - public AutofuzzConstructionException() { - super(); - } - public AutofuzzConstructionException(String message) { - super(message); - } - public AutofuzzConstructionException(Throwable cause) { - super(cause); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzInvocationException.java b/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzInvocationException.java deleted file mode 100644 index 7e6203ce..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzInvocationException.java +++ /dev/null @@ -1,26 +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.api; - -// An exception wrapping a {@link Throwable} thrown during the actual invocation of, but not the -// construction of parameters for an autofuzzed method. -/** - * Only used internally. - */ -public class AutofuzzInvocationException extends RuntimeException { - public AutofuzzInvocationException(Throwable cause) { - super(cause); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel deleted file mode 100644 index b26bb846..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel +++ /dev/null @@ -1,29 +0,0 @@ -java_library( - name = "api", - srcs = [ - "AutofuzzConstructionException.java", - "AutofuzzInvocationException.java", - "CannedFuzzedDataProvider.java", - "Consumer1.java", - "Consumer2.java", - "Consumer3.java", - "Consumer4.java", - "Consumer5.java", - "Function1.java", - "Function2.java", - "Function3.java", - "Function4.java", - "Function5.java", - "FuzzedDataProvider.java", - "FuzzerSecurityIssueCritical.java", - "FuzzerSecurityIssueHigh.java", - "FuzzerSecurityIssueLow.java", - "FuzzerSecurityIssueMedium.java", - "HookType.java", - "Jazzer.java", - "MethodHook.java", - "MethodHooks.java", - "//agent/src/main/java/jaz", - ], - visibility = ["//visibility:public"], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/CannedFuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/api/CannedFuzzedDataProvider.java deleted file mode 100644 index 7209a497..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/CannedFuzzedDataProvider.java +++ /dev/null @@ -1,211 +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.api; - -import java.io.*; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Iterator; -import java.util.List; - -/** - * Replays recorded FuzzedDataProvider invocations that were executed while fuzzing. - * Note: This class is only meant to be used by Jazzer's generated reproducers. - */ -final public class CannedFuzzedDataProvider implements FuzzedDataProvider { - private final Iterator<Object> nextReply; - - public CannedFuzzedDataProvider(String can) { - byte[] rawIn = Base64.getDecoder().decode(can); - ArrayList<Object> recordedReplies; - try (ByteArrayInputStream byteStream = new ByteArrayInputStream(rawIn)) { - try (ObjectInputStream objectStream = new ObjectInputStream(byteStream)) { - recordedReplies = (ArrayList<Object>) objectStream.readObject(); - } - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } - nextReply = recordedReplies.iterator(); - } - - public static CannedFuzzedDataProvider create(List<Object> objects) { - try { - try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) { - try (ObjectOutputStream out = new ObjectOutputStream(bout)) { - out.writeObject(new ArrayList<>(objects)); - String base64 = Base64.getEncoder().encodeToString(bout.toByteArray()); - return new CannedFuzzedDataProvider(base64); - } - } - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - @Override - public boolean consumeBoolean() { - return (boolean) nextReply.next(); - } - - @Override - public boolean[] consumeBooleans(int maxLength) { - return (boolean[]) nextReply.next(); - } - - @Override - public byte consumeByte() { - return (byte) nextReply.next(); - } - - @Override - public byte consumeByte(byte min, byte max) { - return (byte) nextReply.next(); - } - - @Override - public short consumeShort() { - return (short) nextReply.next(); - } - - @Override - public short consumeShort(short min, short max) { - return (short) nextReply.next(); - } - - @Override - public short[] consumeShorts(int maxLength) { - return (short[]) nextReply.next(); - } - - @Override - public int consumeInt() { - return (int) nextReply.next(); - } - - @Override - public int consumeInt(int min, int max) { - return (int) nextReply.next(); - } - - @Override - public int[] consumeInts(int maxLength) { - return (int[]) nextReply.next(); - } - - @Override - public long consumeLong() { - return (long) nextReply.next(); - } - - @Override - public long consumeLong(long min, long max) { - return (long) nextReply.next(); - } - - @Override - public long[] consumeLongs(int maxLength) { - return (long[]) nextReply.next(); - } - - @Override - public float consumeFloat() { - return (float) nextReply.next(); - } - - @Override - public float consumeRegularFloat() { - return (float) nextReply.next(); - } - - @Override - public float consumeRegularFloat(float min, float max) { - return (float) nextReply.next(); - } - - @Override - public float consumeProbabilityFloat() { - return (float) nextReply.next(); - } - - @Override - public double consumeDouble() { - return (double) nextReply.next(); - } - - @Override - public double consumeRegularDouble(double min, double max) { - return (double) nextReply.next(); - } - - @Override - public double consumeRegularDouble() { - return (double) nextReply.next(); - } - - @Override - public double consumeProbabilityDouble() { - return (double) nextReply.next(); - } - - @Override - public char consumeChar() { - return (char) nextReply.next(); - } - - @Override - public char consumeChar(char min, char max) { - return (char) nextReply.next(); - } - - @Override - public char consumeCharNoSurrogates() { - return (char) nextReply.next(); - } - - @Override - public String consumeAsciiString(int maxLength) { - return (String) nextReply.next(); - } - - @Override - public String consumeString(int maxLength) { - return (String) nextReply.next(); - } - - @Override - public String consumeRemainingAsAsciiString() { - return (String) nextReply.next(); - } - - @Override - public String consumeRemainingAsString() { - return (String) nextReply.next(); - } - - @Override - public byte[] consumeBytes(int maxLength) { - return (byte[]) nextReply.next(); - } - - @Override - public byte[] consumeRemainingAsBytes() { - return (byte[]) nextReply.next(); - } - - @Override - public int remainingBytes() { - return (int) nextReply.next(); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer1.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer1.java deleted file mode 100644 index 472c2efd..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer1.java +++ /dev/null @@ -1,22 +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.api; - -import java.util.function.Consumer; - -@FunctionalInterface -public interface Consumer1<T1> extends Consumer<T1> { - @Override void accept(T1 t1); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer2.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer2.java deleted file mode 100644 index d951ade7..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer2.java +++ /dev/null @@ -1,22 +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.api; - -import java.util.function.BiConsumer; - -@FunctionalInterface -public interface Consumer2<T1, T2> extends BiConsumer<T1, T2> { - @Override void accept(T1 t1, T2 t2); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer3.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer3.java deleted file mode 100644 index c508fe53..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer3.java +++ /dev/null @@ -1,20 +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.api; - -@FunctionalInterface -public interface Consumer3<T1, T2, T3> { - void accept(T1 t1, T2 t2, T3 t3); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer4.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer4.java deleted file mode 100644 index 6ee70141..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer4.java +++ /dev/null @@ -1,20 +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.api; - -@FunctionalInterface -public interface Consumer4<T1, T2, T3, T4> { - void accept(T1 t1, T2 t2, T3 t3, T4 t4); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer5.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer5.java deleted file mode 100644 index 523df53c..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer5.java +++ /dev/null @@ -1,20 +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.api; - -@FunctionalInterface -public interface Consumer5<T1, T2, T3, T4, T5> { - void accept(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function1.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function1.java deleted file mode 100644 index 43d68cc7..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Function1.java +++ /dev/null @@ -1,22 +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.api; - -import java.util.function.Function; - -@FunctionalInterface -public interface Function1<T1, R> extends Function<T1, R> { - @Override R apply(T1 t1); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function2.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function2.java deleted file mode 100644 index 6e733b1c..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Function2.java +++ /dev/null @@ -1,22 +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.api; - -import java.util.function.BiFunction; - -@FunctionalInterface -public interface Function2<T1, T2, R> extends BiFunction<T1, T2, R> { - @Override R apply(T1 t1, T2 t2); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function3.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function3.java deleted file mode 100644 index 07d593f9..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Function3.java +++ /dev/null @@ -1,20 +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.api; - -@FunctionalInterface -public interface Function3<T1, T2, T3, R> { - R apply(T1 t1, T2 t2, T3 t3); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function4.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function4.java deleted file mode 100644 index 0e6ec75e..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Function4.java +++ /dev/null @@ -1,20 +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.api; - -@FunctionalInterface -public interface Function4<T1, T2, T3, T4, R> { - R apply(T1 t1, T2 t2, T3 t3, T4 t4); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function5.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function5.java deleted file mode 100644 index cd833f78..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Function5.java +++ /dev/null @@ -1,20 +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.api; - -@FunctionalInterface -public interface Function5<T1, T2, T3, T4, T5, R> { - R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzedDataProvider.java deleted file mode 100644 index b1f38b50..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzedDataProvider.java +++ /dev/null @@ -1,444 +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.api; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Random; - -/** - * A convenience wrapper turning the raw fuzzer input bytes into Java primitive types. - * - * <p>The methods defined by this interface behave similarly to {@link Random#nextInt()}, with all - * returned values depending deterministically on the fuzzer input for the current run. - */ -public interface FuzzedDataProvider { - /** - * Consumes a {@code boolean} from the fuzzer input. - * - * @return a {@code boolean} - */ - boolean consumeBoolean(); - - /** - * Consumes a {@code boolean} array from the fuzzer input. - * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input - * is not sufficiently long. - * - * @param maxLength the maximum length of the array - * @return a {@code boolean} array of length at most {@code length} - */ - boolean[] consumeBooleans(int maxLength); - - /** - * Consumes a {@code byte} from the fuzzer input. - * - * @return a {@code byte} - */ - byte consumeByte(); - - /** - * Consumes a {@code byte} between {@code min} and {@code max} from the fuzzer input. - * - * @param min the inclusive lower bound on the returned value - * @param max the inclusive upper bound on the returned value - * @return a {@code byte} in the range {@code [min, max]} - */ - byte consumeByte(byte min, byte max); - - /** - * Consumes a {@code byte} array from the fuzzer input. - * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input - * is not sufficiently long. - * - * @param maxLength the maximum length of the array - * @return a {@code byte} array of length at most {@code length} - */ - byte[] consumeBytes(int maxLength); - - /** - * Consumes the remaining fuzzer input as a {@code byte} array. - * <p><b>Note:</b> After calling this method, further calls to methods of this interface will - * return fixed values only. - * - * @return a {@code byte} array - */ - byte[] consumeRemainingAsBytes(); - - /** - * Consumes a {@code short} from the fuzzer input. - * - * @return a {@code short} - */ - short consumeShort(); - - /** - * Consumes a {@code short} between {@code min} and {@code max} from the fuzzer input. - * - * @param min the inclusive lower bound on the returned value - * @param max the inclusive upper bound on the returned value - * @return a {@code short} in the range {@code [min, max]} - */ - short consumeShort(short min, short max); - - /** - * Consumes a {@code short} array from the fuzzer input. - * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input - * is not sufficiently long. - * - * @param maxLength the maximum length of the array - * @return a {@code short} array of length at most {@code length} - */ - short[] consumeShorts(int maxLength); - - /** - * Consumes an {@code int} from the fuzzer input. - * - * @return an {@code int} - */ - int consumeInt(); - - /** - * Consumes an {@code int} between {@code min} and {@code max} from the fuzzer input. - * - * @param min the inclusive lower bound on the returned value - * @param max the inclusive upper bound on the returned value - * @return an {@code int} in the range {@code [min, max]} - */ - int consumeInt(int min, int max); - - /** - * Consumes an {@code int} array from the fuzzer input. - * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input - * is not sufficiently long. - * - * @param maxLength the maximum length of the array - * @return an {@code int} array of length at most {@code length} - */ - int[] consumeInts(int maxLength); - - /** - * Consumes a {@code long} from the fuzzer input. - * - * @return a {@code long} - */ - long consumeLong(); - - /** - * Consumes a {@code long} between {@code min} and {@code max} from the fuzzer input. - * - * @param min the inclusive lower bound on the returned value - * @param max the inclusive upper bound on the returned value - * @return a {@code long} in the range @{code [min, max]} - */ - long consumeLong(long min, long max); - - /** - * Consumes a {@code long} array from the fuzzer input. - * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input - * is not sufficiently long. - * - * @param maxLength the maximum length of the array - * @return a {@code long} array of length at most {@code length} - */ - long[] consumeLongs(int maxLength); - - /** - * Consumes a {@code float} from the fuzzer input. - * - * @return a {@code float} that may have a special value (e.g. a NaN or infinity) - */ - float consumeFloat(); - - /** - * Consumes a regular {@code float} from the fuzzer input. - * - * @return a {@code float} that is not a special value (e.g. not a NaN or infinity) - */ - float consumeRegularFloat(); - - /** - * Consumes a regular {@code float} between {@code min} and {@code max} from the fuzzer input. - * - * @return a {@code float} in the range {@code [min, max]} - */ - float consumeRegularFloat(float min, float max); - - /** - * Consumes a {@code float} between 0.0 and 1.0 (inclusive) from the fuzzer input. - * - * @return a {@code float} in the range {@code [0.0, 1.0]} - */ - float consumeProbabilityFloat(); - - /** - * Consumes a {@code double} from the fuzzer input. - * - * @return a {@code double} that may have a special value (e.g. a NaN or infinity) - */ - double consumeDouble(); - - /** - * Consumes a regular {@code double} from the fuzzer input. - * - * @return a {@code double} that is not a special value (e.g. not a NaN or infinity) - */ - double consumeRegularDouble(); - - /** - * Consumes a regular {@code double} between {@code min} and {@code max} from the fuzzer input. - * - * @return a {@code double} in the range {@code [min, max]} - */ - double consumeRegularDouble(double min, double max); - - /** - * Consumes a {@code double} between 0.0 and 1.0 (inclusive) from the fuzzer input. - * - * @return a {@code double} in the range {@code [0.0, 1.0]} - */ - double consumeProbabilityDouble(); - - /** - * Consumes a {@code char} from the fuzzer input. - */ - char consumeChar(); - - /** - * Consumes a {@code char} between {@code min} and {@code max} from the fuzzer input. - * - * @param min the inclusive lower bound on the returned value - * @param max the inclusive upper bound on the returned value - * @return a {@code char} in the range {@code [min, max]} - */ - char consumeChar(char min, char max); - - /** - * Consumes a {@code char} from the fuzzer input that is never a UTF-16 surrogate character. - */ - char consumeCharNoSurrogates(); - - /** - * Consumes a {@link String} from the fuzzer input. - * <p>The returned string may be of any length between 0 and {@code maxLength}, even if there is - * more fuzzer input available. - * - * @param maxLength the maximum length of the string - * @return a {@link String} of length between 0 and {@code maxLength} (inclusive) - */ - String consumeString(int maxLength); - - /** - * Consumes the remaining fuzzer input as a {@link String}. - * <p><b>Note:</b> After calling this method, further calls to methods of this interface will - * return fixed values only. - * - * @return a {@link String} - */ - String consumeRemainingAsString(); - - /** - * Consumes an ASCII-only {@link String} from the fuzzer input. - * <p>The returned string may be of any length between 0 and {@code maxLength}, even if there is - * more fuzzer input available. - * - * @param maxLength the maximum length of the string - * @return a {@link String} of length between 0 and {@code maxLength} (inclusive) that contains - * only ASCII characters - */ - String consumeAsciiString(int maxLength); - - /** - * Consumes the remaining fuzzer input as an ASCII-only {@link String}. - * <p><b>Note:</b> After calling this method, further calls to methods of this interface will - * return fixed values only. - * - * @return a {@link String} that contains only ASCII characters - */ - String consumeRemainingAsAsciiString(); - - /** - * Returns the number of unconsumed bytes in the fuzzer input. - * - * @return the number of unconsumed bytes in the fuzzer input - */ - int remainingBytes(); - - /** - * Picks an element from {@code collection} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param collection the {@link Collection} to pick an element from. - * @param <T> the type of the collection element - * @return an element from {@code collection} chosen based on the fuzzer input - */ - @SuppressWarnings("unchecked") - default<T> T pickValue(Collection<T> collection) { - int size = collection.size(); - if (size == 0) { - throw new IllegalArgumentException("collection is empty"); - } - if (collection instanceof List<?>) { - return ((List<T>) collection).get(consumeInt(0, size - 1)); - } else { - return (T) pickValue(collection.toArray()); - } - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @param <T> the type of the array element - * @return an element from {@code array} chosen based on the fuzzer input - */ - default<T> T pickValue(T[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default boolean pickValue(boolean[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default byte pickValue(byte[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default short pickValue(short[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default int pickValue(int[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default long pickValue(long[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default double pickValue(double[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default float pickValue(float[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks an element from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @return an element from {@code array} chosen based on the fuzzer input - */ - default char pickValue(char[] array) { - return array[consumeInt(0, array.length - 1)]; - } - - /** - * Picks {@code numOfElements} elements from {@code collection} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param collection the {@link Collection} to pick an element from. - * @param numOfElements the number of elements to pick. - * @param <T> the type of the collection element - * @return an array of size {@code numOfElements} from {@code collection} chosen based on the - * fuzzer input - */ - default<T> List<T> pickValues(Collection<T> collection, int numOfElements) { - int size = collection.size(); - if (size == 0) { - throw new IllegalArgumentException("collection is empty"); - } - if (numOfElements > collection.size()) { - throw new IllegalArgumentException("numOfElements exceeds collection.size()"); - } - - List<T> remainingElements = new ArrayList<>(collection); - List<T> pickedElements = new ArrayList<>(); - for (int i = 0; i < numOfElements; i++) { - T element = pickValue(remainingElements); - pickedElements.add(element); - remainingElements.remove(element); - } - return pickedElements; - } - - /** - * Picks {@code numOfElements} elements from {@code array} based on the fuzzer input. - * <p><b>Note:</b> The distribution of picks is not perfectly uniform. - * - * @param array the array to pick an element from. - * @param numOfElements the number of elements to pick. - * @param <T> the type of the array element - * @return an array of size {@code numOfElements} from {@code array} chosen based on the fuzzer - * input - */ - default<T> List<T> pickValues(T[] array, int numOfElements) { - return pickValues(Arrays.asList(array), numOfElements); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java deleted file mode 100644 index fbde853b..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java +++ /dev/null @@ -1,39 +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.api; - -/** - * Thrown to indicate that a fuzz target has detected a critical severity security issue rather than - * a normal bug. - * <p> - * There is only a semantical but no functional difference between throwing exceptions of this type - * or any other. However, automated fuzzing platforms can use the extra information to handle the - * detected issues appropriately. - */ -public class FuzzerSecurityIssueCritical extends RuntimeException { - public FuzzerSecurityIssueCritical() {} - - public FuzzerSecurityIssueCritical(String message) { - super(message); - } - - public FuzzerSecurityIssueCritical(String message, Throwable cause) { - super(message, cause); - } - - public FuzzerSecurityIssueCritical(Throwable cause) { - super(cause); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java deleted file mode 100644 index 05837b0e..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java +++ /dev/null @@ -1,39 +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.api; - -/** - * Thrown to indicate that a fuzz target has detected a high severity security issue rather than a - * normal bug. - * <p> - * There is only a semantical but no functional difference between throwing exceptions of this type - * or any other. However, automated fuzzing platforms can use the extra information to handle the - * detected issues appropriately. - */ -public class FuzzerSecurityIssueHigh extends RuntimeException { - public FuzzerSecurityIssueHigh() {} - - public FuzzerSecurityIssueHigh(String message) { - super(message); - } - - public FuzzerSecurityIssueHigh(String message, Throwable cause) { - super(message, cause); - } - - public FuzzerSecurityIssueHigh(Throwable cause) { - super(cause); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueLow.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueLow.java deleted file mode 100644 index 364b3afb..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueLow.java +++ /dev/null @@ -1,39 +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.api; - -/** - * Thrown to indicate that a fuzz target has detected a low severity security issue rather than a - * normal bug. - * - * There is only a semantical but no functional difference between throwing exceptions of this type - * or any other. However, automated fuzzing platforms can use the extra information to handle the - * detected issues appropriately. - */ -public class FuzzerSecurityIssueLow extends RuntimeException { - public FuzzerSecurityIssueLow() {} - - public FuzzerSecurityIssueLow(String message) { - super(message); - } - - public FuzzerSecurityIssueLow(String message, Throwable cause) { - super(message, cause); - } - - public FuzzerSecurityIssueLow(Throwable cause) { - super(cause); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java deleted file mode 100644 index be7c8c8f..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java +++ /dev/null @@ -1,39 +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.api; - -/** - * Thrown to indicate that a fuzz target has detected a medium severity security issue rather than a - * normal bug. - * <p> - * There is only a semantical but no functional difference between throwing exceptions of this type - * or any other. However, automated fuzzing platforms can use the extra information to handle the - * detected issues appropriately. - */ -public class FuzzerSecurityIssueMedium extends RuntimeException { - public FuzzerSecurityIssueMedium() {} - - public FuzzerSecurityIssueMedium(String message) { - super(message); - } - - public FuzzerSecurityIssueMedium(String message, Throwable cause) { - super(message, cause); - } - - public FuzzerSecurityIssueMedium(Throwable cause) { - super(cause); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java b/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java deleted file mode 100644 index 8ed4337f..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java +++ /dev/null @@ -1,25 +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.api; - -/** - * The type of a {@link MethodHook}. - */ -// Note: The order of entries is important and is used during instrumentation. -public enum HookType { - BEFORE, - REPLACE, - AFTER, -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java deleted file mode 100644 index 97adf578..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ /dev/null @@ -1,643 +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.api; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.InvocationTargetException; -import java.security.SecureRandom; - -/** - * Helper class with static methods that interact with Jazzer at runtime. - */ -final public class Jazzer { - /** - * A 32-bit random number that hooks can use to make pseudo-random choices - * between multiple possible mutations they could guide the fuzzer towards. - * Hooks <b>must not</b> base the decision whether or not to report a finding - * on this number as this will make findings non-reproducible. - * <p> - * This is the same number that libFuzzer uses as a seed internally, which - * makes it possible to deterministically reproduce a previous fuzzing run by - * supplying the seed value printed by libFuzzer as the value of the - * {@code -seed}. - */ - public static final int SEED = getLibFuzzerSeed(); - - private static final Class<?> JAZZER_INTERNAL; - - private static final MethodHandle ON_FUZZ_TARGET_READY; - - private static final MethodHandle TRACE_STRCMP; - private static final MethodHandle TRACE_STRSTR; - private static final MethodHandle TRACE_MEMCMP; - private static final MethodHandle TRACE_PC_INDIR; - - private static final MethodHandle CONSUME; - private static final MethodHandle AUTOFUZZ_FUNCTION_1; - private static final MethodHandle AUTOFUZZ_FUNCTION_2; - private static final MethodHandle AUTOFUZZ_FUNCTION_3; - private static final MethodHandle AUTOFUZZ_FUNCTION_4; - private static final MethodHandle AUTOFUZZ_FUNCTION_5; - private static final MethodHandle AUTOFUZZ_CONSUMER_1; - private static final MethodHandle AUTOFUZZ_CONSUMER_2; - private static final MethodHandle AUTOFUZZ_CONSUMER_3; - private static final MethodHandle AUTOFUZZ_CONSUMER_4; - private static final MethodHandle AUTOFUZZ_CONSUMER_5; - - static { - Class<?> jazzerInternal = null; - MethodHandle onFuzzTargetReady = null; - MethodHandle traceStrcmp = null; - MethodHandle traceStrstr = null; - MethodHandle traceMemcmp = null; - MethodHandle tracePcIndir = null; - MethodHandle consume = null; - MethodHandle autofuzzFunction1 = null; - MethodHandle autofuzzFunction2 = null; - MethodHandle autofuzzFunction3 = null; - MethodHandle autofuzzFunction4 = null; - MethodHandle autofuzzFunction5 = null; - MethodHandle autofuzzConsumer1 = null; - MethodHandle autofuzzConsumer2 = null; - MethodHandle autofuzzConsumer3 = null; - MethodHandle autofuzzConsumer4 = null; - MethodHandle autofuzzConsumer5 = null; - try { - jazzerInternal = Class.forName("com.code_intelligence.jazzer.runtime.JazzerInternal"); - MethodType onFuzzTargetReadyType = MethodType.methodType(void.class, Runnable.class); - onFuzzTargetReady = MethodHandles.publicLookup().findStatic( - jazzerInternal, "registerOnFuzzTargetReadyCallback", onFuzzTargetReadyType); - Class<?> traceDataFlowNativeCallbacks = - Class.forName("com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks"); - - // Use method handles for hints as the calls are potentially performance critical. - MethodType traceStrcmpType = - MethodType.methodType(void.class, String.class, String.class, int.class, int.class); - traceStrcmp = MethodHandles.publicLookup().findStatic( - traceDataFlowNativeCallbacks, "traceStrcmp", traceStrcmpType); - MethodType traceStrstrType = - MethodType.methodType(void.class, String.class, String.class, int.class); - traceStrstr = MethodHandles.publicLookup().findStatic( - traceDataFlowNativeCallbacks, "traceStrstr", traceStrstrType); - MethodType traceMemcmpType = - MethodType.methodType(void.class, byte[].class, byte[].class, int.class, int.class); - traceMemcmp = MethodHandles.publicLookup().findStatic( - traceDataFlowNativeCallbacks, "traceMemcmp", traceMemcmpType); - MethodType tracePcIndirType = MethodType.methodType(void.class, int.class, int.class); - tracePcIndir = MethodHandles.publicLookup().findStatic( - traceDataFlowNativeCallbacks, "tracePcIndir", tracePcIndirType); - - Class<?> metaClass = Class.forName("com.code_intelligence.jazzer.autofuzz.Meta"); - MethodType consumeType = - MethodType.methodType(Object.class, FuzzedDataProvider.class, Class.class); - consume = MethodHandles.publicLookup().findStatic(metaClass, "consume", consumeType); - - autofuzzFunction1 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(Object.class, FuzzedDataProvider.class, Function1.class)); - autofuzzFunction2 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(Object.class, FuzzedDataProvider.class, Function2.class)); - autofuzzFunction3 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(Object.class, FuzzedDataProvider.class, Function3.class)); - autofuzzFunction4 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(Object.class, FuzzedDataProvider.class, Function4.class)); - autofuzzFunction5 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(Object.class, FuzzedDataProvider.class, Function5.class)); - autofuzzConsumer1 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer1.class)); - autofuzzConsumer2 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer2.class)); - autofuzzConsumer3 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer3.class)); - autofuzzConsumer4 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer4.class)); - autofuzzConsumer5 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz", - MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer5.class)); - } catch (ClassNotFoundException ignore) { - // Not running in the context of the agent. This is fine as long as no methods are called on - // this class. - } catch (NoSuchMethodException | IllegalAccessException e) { - // This should never happen as the Jazzer API is loaded from the agent and thus should always - // match the version of the runtime classes. - System.err.println("ERROR: Incompatible version of the Jazzer API detected, please update."); - e.printStackTrace(); - System.exit(1); - } - JAZZER_INTERNAL = jazzerInternal; - ON_FUZZ_TARGET_READY = onFuzzTargetReady; - TRACE_STRCMP = traceStrcmp; - TRACE_STRSTR = traceStrstr; - TRACE_MEMCMP = traceMemcmp; - TRACE_PC_INDIR = tracePcIndir; - CONSUME = consume; - AUTOFUZZ_FUNCTION_1 = autofuzzFunction1; - AUTOFUZZ_FUNCTION_2 = autofuzzFunction2; - AUTOFUZZ_FUNCTION_3 = autofuzzFunction3; - AUTOFUZZ_FUNCTION_4 = autofuzzFunction4; - AUTOFUZZ_FUNCTION_5 = autofuzzFunction5; - AUTOFUZZ_CONSUMER_1 = autofuzzConsumer1; - AUTOFUZZ_CONSUMER_2 = autofuzzConsumer2; - AUTOFUZZ_CONSUMER_3 = autofuzzConsumer3; - AUTOFUZZ_CONSUMER_4 = autofuzzConsumer4; - AUTOFUZZ_CONSUMER_5 = autofuzzConsumer5; - } - - private Jazzer() {} - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Function1} with (partially) specified - * type variables, e.g. {@code (Function1<String, ?>) String::new}. - * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke - * the function. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - @SuppressWarnings("unchecked") - public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) { - try { - return (R) AUTOFUZZ_FUNCTION_1.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - // Not reached. - return null; - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Function2} with (partially) specified - * type variables. - * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke - * the function. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - @SuppressWarnings("unchecked") - public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) { - try { - return (R) AUTOFUZZ_FUNCTION_2.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - // Not reached. - return null; - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Function3} with (partially) specified - * type variables. - * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke - * the function. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - @SuppressWarnings("unchecked") - public static <T1, T2, T3, R> R autofuzz(FuzzedDataProvider data, Function3<T1, T2, T3, R> func) { - try { - return (R) AUTOFUZZ_FUNCTION_3.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - // Not reached. - return null; - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Function4} with (partially) specified - * type variables. - * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke - * the function. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - @SuppressWarnings("unchecked") - public static <T1, T2, T3, T4, R> R autofuzz( - FuzzedDataProvider data, Function4<T1, T2, T3, T4, R> func) { - try { - return (R) AUTOFUZZ_FUNCTION_4.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - // Not reached. - return null; - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Function5} with (partially) specified - * type variables. - * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke - * the function. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - @SuppressWarnings("unchecked") - public static <T1, T2, T3, T4, T5, R> R autofuzz( - FuzzedDataProvider data, Function5<T1, T2, T3, T4, T5, R> func) { - try { - return (R) AUTOFUZZ_FUNCTION_5.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - // Not reached. - return null; - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Consumer1} with explicitly specified - * type variable. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) { - try { - AUTOFUZZ_CONSUMER_1.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Consumer2} with (partially) specified - * type variables. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - public static <T1, T2> void autofuzz(FuzzedDataProvider data, Consumer2<T1, T2> func) { - try { - AUTOFUZZ_CONSUMER_2.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Consumer3} with (partially) specified - * type variables. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - public static <T1, T2, T3> void autofuzz(FuzzedDataProvider data, Consumer3<T1, T2, T3> func) { - try { - AUTOFUZZ_CONSUMER_3.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Consumer4} with (partially) specified - * type variables. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - public static <T1, T2, T3, T4> void autofuzz( - FuzzedDataProvider data, Consumer4<T1, T2, T3, T4> func) { - try { - AUTOFUZZ_CONSUMER_4.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - } - - /** - * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input - * using only public methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in - * meaningful ways for a number of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param func a method reference for the function to autofuzz. If there are multiple overloads, - * resolve ambiguities by explicitly casting to {@link Consumer5} with (partially) specified - * type variables. - * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link - * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call. - * The {@link Throwable} is thrown unchecked. - */ - public static <T1, T2, T3, T4, T5> void autofuzz( - FuzzedDataProvider data, Consumer5<T1, T2, T3, T4, T5> func) { - try { - AUTOFUZZ_CONSUMER_5.invoke(data, func); - } catch (AutofuzzInvocationException e) { - rethrowUnchecked(e.getCause()); - } catch (Throwable t) { - rethrowUnchecked(t); - } - } - - /** - * Attempts to construct an instance of {@code type} from the fuzzer input using only public - * methods available on the classpath. - * <p> - * <b>Note:</b> This function is inherently heuristic and may fail to return meaningful values for - * a variety of reasons. - * - * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}. - * @param type the {@link Class} to construct an instance of. - * @return an instance of {@code type} constructed from the fuzzer input, or {@code null} if - * autofuzz failed to create an instance. - */ - @SuppressWarnings("unchecked") - public static <T> T consume(FuzzedDataProvider data, Class<T> type) { - try { - return (T) CONSUME.invokeExact(data, type); - } catch (AutofuzzConstructionException ignored) { - return null; - } catch (Throwable t) { - rethrowUnchecked(t); - // Not reached. - return null; - } - } - - /** - * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code - * target}. - * <p> - * If the relation between the raw fuzzer input and the value of {@code current} is relatively - * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to - * achieve equality. - * - * @param current a non-constant string observed during fuzz target execution - * @param target a string that {@code current} should become equal to, but currently isn't - * @param id a (probabilistically) unique identifier for this particular compare hint - */ - public static void guideTowardsEquality(String current, String target, int id) { - if (TRACE_STRCMP == null) { - return; - } - try { - TRACE_STRCMP.invokeExact(current, target, 1, id); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - /** - * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code - * target}. - * <p> - * If the relation between the raw fuzzer input and the value of {@code current} is relatively - * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to - * achieve equality. - * - * @param current a non-constant byte array observed during fuzz target execution - * @param target a byte array that {@code current} should become equal to, but currently isn't - * @param id a (probabilistically) unique identifier for this particular compare hint - */ - public static void guideTowardsEquality(byte[] current, byte[] target, int id) { - if (TRACE_MEMCMP == null) { - return; - } - try { - TRACE_MEMCMP.invokeExact(current, target, 1, id); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - /** - * Instructs the fuzzer to guide its mutations towards making {@code haystack} contain {@code - * needle} as a substring. - * <p> - * If the relation between the raw fuzzer input and the value of {@code haystack} is relatively - * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to - * satisfy the substring check. - * - * @param haystack a non-constant string observed during fuzz target execution - * @param needle a string that should be contained in {@code haystack} as a substring, but - * currently isn't - * @param id a (probabilistically) unique identifier for this particular compare hint - */ - public static void guideTowardsContainment(String haystack, String needle, int id) { - if (TRACE_STRSTR == null) { - return; - } - try { - TRACE_STRSTR.invokeExact(haystack, needle, id); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - /** - * Instructs the fuzzer to attain as many possible values for the absolute value of {@code state} - * as possible. - * <p> - * Call this function from a fuzz target or a hook to help the fuzzer track partial progress - * (e.g. by passing the length of a common prefix of two lists that should become equal) or - * explore different values of state that is not directly related to code coverage (see the - * MazeFuzzer example). - * <p> - * <b>Note:</b> This hint only takes effect if the fuzzer is run with the argument - * {@code -use_value_profile=1}. - * - * @param state a numeric encoding of a state that should be varied by the fuzzer - * @param id a (probabilistically) unique identifier for this particular state hint - */ - public static void exploreState(byte state, int id) { - if (TRACE_PC_INDIR == null) { - return; - } - // We only use the lower 7 bits of state, which allows for 128 different state values tracked - // per id. The particular amount of 7 bits of state is also used in libFuzzer's - // TracePC::HandleCmp: - // https://github.com/llvm/llvm-project/blob/c12d49c4e286fa108d4d69f1c6d2b8d691993ffd/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L390 - // This value should be large enough for most use cases (e.g. tracking the length of a prefix in - // a comparison) while being small enough that the bitmap isn't filled up too quickly - // (65536 bits/ 128 bits per id = 512 ids). - - // We use tracePcIndir as a way to set a bit in libFuzzer's value profile bitmap. In - // TracePC::HandleCallerCallee, which is what this function ultimately calls through to, the - // lower 12 bits of each argument are combined into a 24-bit index into the bitmap, which is - // then reduced modulo a 16-bit prime. To keep the modulo bias small, we should fill as many - // of the relevant bits as possible. However, there are the following restrictions: - // 1. Since we use the return address trampoline to set the caller address indirectly, its - // upper 3 bits are fixed, which leaves a total of 21 variable bits on x86_64. - // 2. On arm64 macOS, where every instruction is aligned to 4 bytes, the lower 2 bits of the - // caller address will always be zero, further reducing the number of variable bits in the - // caller parameter to 7. - // https://github.com/llvm/llvm-project/blob/c12d49c4e286fa108d4d69f1c6d2b8d691993ffd/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L121 - // Even taking these restrictions into consideration, we pass state in the lowest bits of the - // caller address, which is used to form the lowest bits of the bitmap index. This should result - // in the best caching behavior as state is expected to change quickly in consecutive runs and - // in this way all its bitmap entries would be located close to each other in memory. - int lowerBits = (state & 0x7f) | (id << 7); - int upperBits = id >>> 5; - try { - TRACE_PC_INDIR.invokeExact(upperBits, lowerBits); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - /** - * Make Jazzer report the provided {@link Throwable} as a finding. - * <p> - * <b>Note:</b> This method must only be called from a method hook. In a - * fuzz target, simply throw an exception to trigger a finding. - * @param finding the finding that Jazzer should report - */ - public static void reportFindingFromHook(Throwable finding) { - try { - JAZZER_INTERNAL.getMethod("reportFindingFromHook", Throwable.class).invoke(null, finding); - } catch (NullPointerException | IllegalAccessException | NoSuchMethodException e) { - // We can only reach this point if the runtime is not on the classpath, e.g. in case of a - // reproducer. Just throw the finding. - rethrowUnchecked(finding); - } catch (InvocationTargetException e) { - // reportFindingFromHook throws a HardToCatchThrowable, which will bubble up wrapped in an - // InvocationTargetException that should not be stopped here. - if (e.getCause().getClass().getName().endsWith(".HardToCatchError")) { - throw(Error) e.getCause(); - } else { - e.printStackTrace(); - } - } - } - - /** - * Register a callback to be executed right before the fuzz target is executed for the first time. - * <p> - * This can be used to disable hooks until after Jazzer has been fully initializing, e.g. to - * prevent Jazzer internals from triggering hooks on Java standard library classes. - * - * @param callback the callback to execute - */ - public static void onFuzzTargetReady(Runnable callback) { - try { - ON_FUZZ_TARGET_READY.invokeExact(callback); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - private static int getLibFuzzerSeed() { - // The Jazzer driver sets this property based on the value of libFuzzer's -seed command-line - // option, which allows for fully reproducible fuzzing runs if set. If not running in the - // context of the driver, fall back to a random number instead. - String rawSeed = System.getProperty("jazzer.seed"); - if (rawSeed == null) { - return new SecureRandom().nextInt(); - } - // If jazzer.seed is set, we expect it to be a valid integer. - return Integer.parseUnsignedInt(rawSeed); - } - - // Rethrows a (possibly checked) exception while avoiding a throws declaration. - @SuppressWarnings("unchecked") - private static <T extends Throwable> void rethrowUnchecked(Throwable t) throws T { - throw(T) t; - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java deleted file mode 100644 index 3a1c5f39..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java +++ /dev/null @@ -1,207 +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.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.invoke.MethodType; - -/** - * Registers the annotated method as a hook that should run before, instead or - * after the method specified by the annotation parameters. - * <p> - * Depending on {@link #type()} this method will be called after, instead or - * before every call to the target method and has - * access to its parameters and return value. The target method is specified by - * {@link #targetClassName()} and {@link #targetMethod()}. In case of an - * overloaded method, {@link #targetMethodDescriptor()} can be used to restrict - * the application of the hook to a particular overload. - * <p> - * The signature of the annotated method must be as follows (this does not - * restrict the method name and parameter names, which are arbitrary), - * depending on the value of {@link #type()}: - * - * <dl> - * <dt><span class="strong">{@link HookType#BEFORE}</span> - * <dd> - * <pre>{@code - * public static void hook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) - * }</pre> - * Arguments: - * <p><ul> - * <li>{@code method}: A {@link java.lang.invoke.MethodHandle} representing the - * original method. The original method can be invoked via - * {@link java.lang.invoke.MethodHandle#invokeWithArguments(Object...)}. This - * requires passing {@code thisObject} as the first argument if the method is - * not static. This argument can be {@code null}. - * <li>{@code thisObject}: An {@link Object} containing the implicit - * {@code this} argument to the original method. If the original method is - * static, this argument will be {@code null}. - * <li>{@code arguments}: An array of {@link Object}s containing the arguments - * passed to the original method. Primitive types (e.g. {@code boolean}) will be - * wrapped into their corresponding wrapper type (e.g. {@link Boolean}). - * <li>{@code hookId}: A random {@code int} identifying the particular call - * site.This can be used to derive additional coverage information. - * </ul> - * - * <dt><span class="strong">{@link HookType#REPLACE}</span> - * <dd> - * <pre>{@code - * public static Object hook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) - * }</pre> - * The return type may alternatively be taken to be the exact return type of - * target method or a wrapper type thereof. The returned object will be casted - * and unwrapped automatically. - * <p> - * Arguments: - * <p><ul> - * <li>{@code method}: A {@link java.lang.invoke.MethodHandle} representing the - * original method. The original method can be invoked via - * {@link java.lang.invoke.MethodHandle#invokeWithArguments(Object...)}. This - * requires passing {@code thisObject} as the first argument if the method is - * not static. This argument can be {@code null}. - * <li>{@code thisObject}: An {@link Object} containing the implicit - * {@code this} argument to the original method. If the original method is - * static, this argument will be {@code null}. - * <li>{@code arguments}: An array of {@link Object}s containing the arguments - * passed to the original method. Primitive types (e.g. {@code boolean}) will be - * wrapped into their corresponding wrapper type (e.g. {@link Boolean}). - * <li>{@code hookId}: A random {@code int} identifying the particular call - * site.This can be used to derive additional coverage information. - * </ul><p> - * <p> - * Return value: the value that should take the role of the value the target - * method would have returned - * <p> - * <dt><span class="strong">{@link HookType#AFTER}</span> - * <dd> - * <pre>{@code - * public static void hook(MethodHandle method, Object thisObject, Object[] arguments, int hookId, - * Object returnValue) - * }</pre> - * Arguments: - * <p><ul> - * <li>{@code method}: A {@link java.lang.invoke.MethodHandle} representing the - * original method. The original method can be invoked via - * {@link java.lang.invoke.MethodHandle#invokeWithArguments(Object...)}. This - * requires passing {@code thisObject} as the first argument if the method is - * not static. This argument can be {@code null}. - * <li>{@code thisObject}: An {@link Object} containing the implicit - * {@code this} argument to the original method. If the original method is - * static, this argument will be {@code null}. - * <li>{@code arguments}: An array of {@link Object}s containing the arguments - * passed to the original method. Primitive types (e.g. {@code boolean}) will be - * wrapped into their corresponding wrapper type (e.g. {@link Boolean}). - * <li>{@code hookId}: A random {@code int} identifying the particular call - * site.This can be used to derive additional coverage information. - * <li>{@code returnValue}: An {@link Object} containing the return value of the - * invocation of the original method. Primitive types (e.g. {@code boolean}) - * will be wrapped into their corresponding wrapper type (e.g. {@link Boolean}). - * If the original method has return type {@code void}, this value will be - * {@code null}. - * <p> - * Multiple {@link HookType#BEFORE} and {@link HookType#AFTER} hooks are - * allowed to reference the same target method. Exclusively one - * {@link HookType#REPLACE} hook may reference a target method, no other types - * allowed. Attention must be paid to not guide the Fuzzer in different - * directions via {@link Jazzer}'s {@code guideTowardsXY} methods in the - * different hooks. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Repeatable(MethodHooks.class) -@Documented -public @interface MethodHook { - /** - * The time at which the annotated method should be called. - * <p> - * If this is {@link HookType#BEFORE}, the annotated method will be called - * before the target method and has access to its arguments. - * <p> - * If this is {@link HookType#REPLACE}, the annotated method will be called - * instead of the target method. It has access to its arguments and can - * return a value that will replace the target method's return value. - * <p> - * If this is {@link HookType#AFTER}, the annotated method will be called - * after the target method and has access to its arguments and return - * value. - * - * @return when the hook should be called - */ - HookType type(); - - /** - * The name of the class that contains the method that should be hooked, - * as returned by {@link Class#getName()}. - * <p> - * If an interface or abstract class is specified, also calls to all - * implementations and subclasses available on the classpath during startup - * are hooked, respectively. Interfaces and subclasses are not taken into - * account for concrete classes. - * <p> - * Examples: - * <p><ul> - * <li>{@link String}: {@code "java.lang.String"} - * <li>{@link java.nio.file.FileSystem}: {@code "java.nio.file.FileSystem"} - * </ul><p> - * - * @return the name of the class containing the method to be hooked - */ - String targetClassName(); - - /** - * The name of the method to be hooked. Use {@code "<init>"} for - * constructors. - * <p> - * Examples: - * <p><ul> - * <li>{@link String#equals(Object)}: {@code "equals"} - * <li>{@link String#String()}: {@code "<init>"} - * </ul><p> - * - * @return the name of the method to be hooked - */ - String targetMethod(); - - /** - * The descriptor of the method to be hooked. This is only needed if there - * are multiple methods with the same name and not all of them should be - * hooked. - * <p> - * The descriptor of a method is an internal representation of the method's - * signature, which includes the types of its parameters and its return - * value. For more information on descriptors, see the - * <a href=https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html#jvms-4.3.3>JVM - * Specification, Section 4.3.3</a> and {@link MethodType#toMethodDescriptorString()} - * - * @return the descriptor of the method to be hooked - */ - String targetMethodDescriptor() default ""; - - /** - * Array of additional classes to hook. - * <p> - * Hooks are applied on call sites. This means that classes calling the one - * defined in this annotation need to be instrumented to actually execute - * the hook. This property can be used to hook normally ignored classes. - * - * @return fully qualified class names to hook - */ - String[] additionalClassesToHook() default {}; -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHooks.java deleted file mode 100644 index 7eec24b3..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHooks.java +++ /dev/null @@ -1,31 +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.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Internal helper allowing to apply multiple {@link MethodHook} annotations to the same method. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Documented -public @interface MethodHooks { - MethodHook[] value(); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java deleted file mode 100644 index 2fbed971..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java +++ /dev/null @@ -1,117 +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.autofuzz; - -import java.util.Stack; -import java.util.stream.Collectors; - -public class AutofuzzCodegenVisitor { - private final Stack<Group> groups = new Stack<>(); - private int variableCounter = 0; - - AutofuzzCodegenVisitor() { - init(); - } - - private void init() { - pushGroup("", "", ""); - } - - public void pushGroup(String prefix, String delimiter, String suffix) { - groups.push(new Group(prefix, delimiter, suffix)); - } - - public void pushElement(String element) { - groups.peek().push(element); - } - - public void popElement() { - groups.peek().pop(); - } - - public void popGroup() { - if (groups.size() == 1) { - throw new AutofuzzError( - "popGroup must be called exactly once for every pushGroup: " + toDebugString()); - } - pushElement(groups.pop().toString()); - } - - public String generate() { - if (groups.size() != 1) { - throw new AutofuzzError( - "popGroup must be called exactly once for every pushGroup: " + toDebugString()); - } - return groups.pop().toString(); - } - - public void addCharLiteral(char c) { - pushElement("'" + escapeForLiteral(Character.toString(c)) + "'"); - } - - public void addStringLiteral(String string) { - pushElement('"' + escapeForLiteral(string) + '"'); - } - - public String uniqueVariableName() { - return String.format("autofuzzVariable%s", variableCounter++); - } - - private String escapeForLiteral(String string) { - // The list of escape sequences is taken from: - // https://docs.oracle.com/javase/tutorial/java/data/characters.html - return string.replace("\t", "\\t") - .replace("\b", "\\b") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\f", "\\f") - .replace("\f", "\\f") - .replace("\"", "\\\"") - .replace("'", "\\'") - .replace("\\", "\\\\"); - } - - private String toDebugString() { - return groups.stream() - .map(group -> group.elements.stream().collect(Collectors.joining(", ", "[", "]"))) - .collect(Collectors.joining(", ", "[", "]")); - } - - private static class Group { - private final String prefix; - private final String delimiter; - private final String suffix; - private final Stack<String> elements = new Stack<>(); - - Group(String prefix, String delimiter, String suffix) { - this.prefix = prefix; - this.delimiter = delimiter; - this.suffix = suffix; - } - - public void push(String element) { - elements.push(element); - } - - public void pop() { - elements.pop(); - } - - @Override - public String toString() { - return elements.stream().collect(Collectors.joining(delimiter, prefix, suffix)); - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzError.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzError.java deleted file mode 100644 index a94b385d..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzError.java +++ /dev/null @@ -1,31 +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.autofuzz; - -/** - * An error indicating an internal error in the autofuzz functionality. - */ -public class AutofuzzError extends Error { - private static final String MESSAGE_TRAILER = String.format( - "%nPlease file an issue at:%n https://github.com/CodeIntelligenceTesting/jazzer/issues/new/choose"); - - public AutofuzzError(String message) { - super(message + MESSAGE_TRAILER); - } - - public AutofuzzError(String message, Throwable cause) { - super(message + MESSAGE_TRAILER, cause); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel deleted file mode 100644 index 779f79cb..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ /dev/null @@ -1,17 +0,0 @@ -java_library( - name = "autofuzz", - srcs = [ - "AutofuzzCodegenVisitor.java", - "AutofuzzError.java", - "FuzzTarget.java", - "Meta.java", - "YourAverageJavaClass.java", - ], - visibility = ["//visibility:public"], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/utils", - "@com_github_classgraph_classgraph//:classgraph", - "@com_github_jhalterman_typetools//:typetools", - ], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java deleted file mode 100644 index 3b0d046b..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java +++ /dev/null @@ -1,317 +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.autofuzz; - -import com.code_intelligence.jazzer.api.AutofuzzConstructionException; -import com.code_intelligence.jazzer.api.AutofuzzInvocationException; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import com.code_intelligence.jazzer.utils.SimpleGlobMatcher; -import com.code_intelligence.jazzer.utils.Utils; -import java.io.Closeable; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public final class FuzzTarget { - private static final String AUTOFUZZ_REPRODUCER_TEMPLATE = "public class Crash_%s {\n" - + " public static void main(String[] args) throws Throwable {\n" - + " %s;\n" - + " }\n" - + "}"; - private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100; - - private static String methodReference; - private static Executable[] targetExecutables; - private static Map<Executable, Class<?>[]> throwsDeclarations; - private static Set<SimpleGlobMatcher> ignoredExceptionMatchers; - private static long executionsSinceLastInvocation = 0; - - public static void fuzzerInitialize(String[] args) { - if (args.length == 0 || !args[0].contains("::")) { - System.err.println( - "Expected the argument to --autofuzz to be a method reference (e.g. System.out::println)"); - System.exit(1); - } - methodReference = args[0]; - String[] parts = methodReference.split("::", 2); - String className = parts[0]; - String methodNameAndOptionalDescriptor = parts[1]; - String methodName; - String descriptor; - int descriptorStart = methodNameAndOptionalDescriptor.indexOf('('); - if (descriptorStart != -1) { - methodName = methodNameAndOptionalDescriptor.substring(0, descriptorStart); - // URL decode the descriptor to allow copy-pasting from javadoc links such as: - // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#valueOf(char%5B%5D) - try { - descriptor = - URLDecoder.decode(methodNameAndOptionalDescriptor.substring(descriptorStart), "UTF-8"); - } catch (UnsupportedEncodingException e) { - // UTF-8 is always supported. - e.printStackTrace(); - System.exit(1); - return; - } - } else { - methodName = methodNameAndOptionalDescriptor; - descriptor = null; - } - - Class<?> targetClass = null; - String targetClassName = className; - do { - try { - // Explicitly invoking static initializers to trigger some coverage in the code. - targetClass = Class.forName(targetClassName, true, ClassLoader.getSystemClassLoader()); - } catch (ClassNotFoundException e) { - int classSeparatorIndex = targetClassName.lastIndexOf("."); - if (classSeparatorIndex == -1) { - System.err.printf( - "Failed to find class %s for autofuzz, please ensure it is contained in the classpath " - + "specified with --cp and specify the full package name%n", - className); - e.printStackTrace(); - System.exit(1); - return; - } - StringBuilder classNameBuilder = new StringBuilder(targetClassName); - classNameBuilder.setCharAt(classSeparatorIndex, '$'); - targetClassName = classNameBuilder.toString(); - } - } while (targetClass == null); - - boolean isConstructor = methodName.equals("new"); - if (isConstructor) { - targetExecutables = - Arrays.stream(targetClass.getConstructors()) - .filter(constructor - -> descriptor == null - || Utils.getReadableDescriptor(constructor).equals(descriptor)) - .toArray(Executable[] ::new); - } else { - // We use getDeclaredMethods and filter for the public access modifier instead of using - // getMethods as we want to exclude methods inherited from superclasses or interfaces, which - // can lead to unexpected results when autofuzzing. If desired, these can be autofuzzed - // explicitly instead. - targetExecutables = - Arrays.stream(targetClass.getDeclaredMethods()) - .filter(method -> Modifier.isPublic(method.getModifiers())) - .filter(method - -> method.getName().equals(methodName) - && (descriptor == null - || Utils.getReadableDescriptor(method).equals(descriptor))) - .toArray(Executable[] ::new); - } - if (targetExecutables.length == 0) { - if (isConstructor) { - if (descriptor == null) { - System.err.printf( - "Failed to find accessible constructors in class %s for autofuzz.%n", className); - } else { - System.err.printf( - "Failed to find accessible constructors with signature %s in class %s for autofuzz.%n" - + "Accessible constructors:%n%s", - descriptor, className, - Arrays.stream(targetClass.getConstructors()) - .map(method - -> String.format("%s::new%s", method.getDeclaringClass().getName(), - Utils.getReadableDescriptor(method))) - .distinct() - .collect(Collectors.joining(System.lineSeparator()))); - } - } else { - if (descriptor == null) { - System.err.printf("Failed to find accessible methods named %s in class %s for autofuzz.%n" - + "Accessible methods:%n%s", - methodName, className, - Arrays.stream(targetClass.getMethods()) - .map(method - -> String.format( - "%s::%s", method.getDeclaringClass().getName(), method.getName())) - .distinct() - .collect(Collectors.joining(System.lineSeparator()))); - } else { - System.err.printf( - "Failed to find accessible methods named %s with signature %s in class %s for autofuzz.%n" - + "Accessible methods with that name:%n%s", - methodName, descriptor, className, - Arrays.stream(targetClass.getMethods()) - .filter(method -> method.getName().equals(methodName)) - .map(method - -> String.format("%s::%s%s", method.getDeclaringClass().getName(), - method.getName(), Utils.getReadableDescriptor(method))) - .distinct() - .collect(Collectors.joining(System.lineSeparator()))); - } - } - System.exit(1); - } - - ignoredExceptionMatchers = Arrays.stream(args) - .skip(1) - .filter(s -> s.contains("*")) - .map(SimpleGlobMatcher::new) - .collect(Collectors.toSet()); - - List<Class<?>> alwaysIgnore = - Arrays.stream(args) - .skip(1) - .filter(s -> !s.contains("*")) - .map(name -> { - try { - return ClassLoader.getSystemClassLoader().loadClass(name); - } catch (ClassNotFoundException e) { - System.err.printf("Failed to find class '%s' specified in --autofuzz_ignore", name); - System.exit(1); - } - throw new Error("Not reached"); - }) - .collect(Collectors.toList()); - throwsDeclarations = - Arrays.stream(targetExecutables) - .collect(Collectors.toMap(method - -> method, - method - -> Stream.concat(Arrays.stream(method.getExceptionTypes()), alwaysIgnore.stream()) - .toArray(Class[] ::new))); - } - - public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Throwable { - AutofuzzCodegenVisitor codegenVisitor = null; - if (Meta.isDebug()) { - codegenVisitor = new AutofuzzCodegenVisitor(); - } - fuzzerTestOneInput(data, codegenVisitor); - if (codegenVisitor != null) { - System.err.println(codegenVisitor.generate()); - } - } - - public static void dumpReproducer(FuzzedDataProvider data, String reproducerPath, String sha) { - AutofuzzCodegenVisitor codegenVisitor = new AutofuzzCodegenVisitor(); - try { - fuzzerTestOneInput(data, codegenVisitor); - } catch (Throwable ignored) { - } - String javaSource = String.format(AUTOFUZZ_REPRODUCER_TEMPLATE, sha, codegenVisitor.generate()); - Path javaPath = Paths.get(reproducerPath, String.format("Crash_%s.java", sha)); - try { - Files.write(javaPath, javaSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); - } catch (IOException e) { - System.err.printf("ERROR: Failed to write Java reproducer to %s%n", javaPath); - e.printStackTrace(); - } - System.out.printf( - "reproducer_path='%s'; Java reproducer written to %s%n", reproducerPath, javaPath); - } - - private static void fuzzerTestOneInput( - FuzzedDataProvider data, AutofuzzCodegenVisitor codegenVisitor) throws Throwable { - Executable targetExecutable; - if (FuzzTarget.targetExecutables.length == 1) { - targetExecutable = FuzzTarget.targetExecutables[0]; - } else { - targetExecutable = data.pickValue(FuzzTarget.targetExecutables); - } - Object returnValue = null; - try { - if (targetExecutable instanceof Method) { - returnValue = Meta.autofuzz(data, (Method) targetExecutable, codegenVisitor); - } else { - returnValue = Meta.autofuzz(data, (Constructor<?>) targetExecutable, codegenVisitor); - } - executionsSinceLastInvocation = 0; - } catch (AutofuzzConstructionException e) { - if (Meta.isDebug()) { - e.printStackTrace(); - } - // Ignore exceptions thrown while constructing the parameters for the target method. We can - // only guess how to generate valid parameters and any exceptions thrown while doing so - // are most likely on us. However, if this happens too often, Autofuzz got stuck and we should - // let the user know. - executionsSinceLastInvocation++; - if (executionsSinceLastInvocation >= MAX_EXECUTIONS_WITHOUT_INVOCATION) { - System.err.printf("Failed to generate valid arguments to '%s' in %d attempts; giving up%n", - methodReference, executionsSinceLastInvocation); - System.exit(1); - } else if (executionsSinceLastInvocation == MAX_EXECUTIONS_WITHOUT_INVOCATION / 2) { - // The application under test might perform classpath modifications or create classes - // dynamically that implement interfaces or extend abstract classes. Rescanning the - // classpath might help with constructing objects. - Meta.rescanClasspath(); - } - } catch (AutofuzzInvocationException e) { - executionsSinceLastInvocation = 0; - Throwable cause = e.getCause(); - Class<?> causeClass = cause.getClass(); - // Do not report exceptions declared to be thrown by the method under test. - for (Class<?> declaredThrow : throwsDeclarations.get(targetExecutable)) { - if (declaredThrow.isAssignableFrom(causeClass)) { - return; - } - } - - if (ignoredExceptionMatchers.stream().anyMatch(m -> m.matches(causeClass.getName()))) { - return; - } - cleanStackTraces(cause); - throw cause; - } catch (Throwable t) { - System.err.println("Unexpected exception encountered during autofuzz"); - t.printStackTrace(); - System.exit(1); - } finally { - if (returnValue instanceof Closeable) { - ((Closeable) returnValue).close(); - } - } - } - - // Removes all stack trace elements that live in the Java reflection packages or the autofuzz - // package from the bottom of all stack frames. - private static void cleanStackTraces(Throwable t) { - Throwable cause = t; - while (cause != null) { - StackTraceElement[] elements = cause.getStackTrace(); - int firstInterestingPos; - for (firstInterestingPos = elements.length - 1; firstInterestingPos > 0; - firstInterestingPos--) { - String className = elements[firstInterestingPos].getClassName(); - if (!className.startsWith("com.code_intelligence.jazzer.autofuzz.") - && !className.startsWith("java.lang.reflect.") - && !className.startsWith("jdk.internal.reflect.")) { - break; - } - } - cause.setStackTrace(Arrays.copyOfRange(elements, 0, firstInterestingPos + 1)); - cause = cause.getCause(); - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java deleted file mode 100644 index 3d48017f..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ /dev/null @@ -1,716 +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.autofuzz; - -import com.code_intelligence.jazzer.api.AutofuzzConstructionException; -import com.code_intelligence.jazzer.api.AutofuzzInvocationException; -import com.code_intelligence.jazzer.api.Consumer1; -import com.code_intelligence.jazzer.api.Consumer2; -import com.code_intelligence.jazzer.api.Consumer3; -import com.code_intelligence.jazzer.api.Consumer4; -import com.code_intelligence.jazzer.api.Consumer5; -import com.code_intelligence.jazzer.api.Function1; -import com.code_intelligence.jazzer.api.Function2; -import com.code_intelligence.jazzer.api.Function3; -import com.code_intelligence.jazzer.api.Function4; -import com.code_intelligence.jazzer.api.Function5; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import com.code_intelligence.jazzer.utils.Utils; -import io.github.classgraph.ClassGraph; -import io.github.classgraph.ClassInfoList; -import io.github.classgraph.ScanResult; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import net.jodah.typetools.TypeResolver; -import net.jodah.typetools.TypeResolver.Unknown; - -public class Meta { - static final WeakHashMap<Class<?>, List<Class<?>>> implementingClassesCache = new WeakHashMap<>(); - static final WeakHashMap<Class<?>, List<Class<?>>> nestedBuilderClassesCache = - new WeakHashMap<>(); - static final WeakHashMap<Class<?>, List<Method>> originalObjectCreationMethodsCache = - new WeakHashMap<>(); - static final WeakHashMap<Class<?>, List<Method>> cascadingBuilderMethodsCache = - new WeakHashMap<>(); - - public static Object autofuzz(FuzzedDataProvider data, Method method) { - return autofuzz(data, method, null); - } - - static Object autofuzz(FuzzedDataProvider data, Method method, AutofuzzCodegenVisitor visitor) { - Object result; - if (Modifier.isStatic(method.getModifiers())) { - if (visitor != null) { - // This group will always have two elements: The class name and the method call. - visitor.pushGroup( - String.format("%s.", method.getDeclaringClass().getCanonicalName()), "", ""); - } - try { - result = autofuzz(data, method, null, visitor); - } finally { - if (visitor != null) { - visitor.popGroup(); - } - } - } else { - if (visitor != null) { - // This group will always have two elements: The thisObject and the method call. - // Since the this object can be a complex expression, wrap it in paranthesis. - visitor.pushGroup("(", ").", ""); - } - Object thisObject = consume(data, method.getDeclaringClass(), visitor); - if (thisObject == null) { - throw new AutofuzzConstructionException(); - } - try { - result = autofuzz(data, method, thisObject, visitor); - } finally { - if (visitor != null) { - visitor.popGroup(); - } - } - } - return result; - } - - public static Object autofuzz(FuzzedDataProvider data, Method method, Object thisObject) { - return autofuzz(data, method, thisObject, null); - } - - static Object autofuzz( - FuzzedDataProvider data, Method method, Object thisObject, AutofuzzCodegenVisitor visitor) { - if (visitor != null) { - visitor.pushGroup(String.format("%s(", method.getName()), ", ", ")"); - } - Object[] arguments = consumeArguments(data, method, visitor); - if (visitor != null) { - visitor.popGroup(); - } - try { - return method.invoke(thisObject, arguments); - } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) { - // We should ensure that the arguments fed into the method are always valid. - throw new AutofuzzError(getDebugSummary(method, thisObject, arguments), e); - } catch (InvocationTargetException e) { - throw new AutofuzzInvocationException(e.getCause()); - } - } - - public static <R> R autofuzz(FuzzedDataProvider data, Constructor<R> constructor) { - return autofuzz(data, constructor, null); - } - - static <R> R autofuzz( - FuzzedDataProvider data, Constructor<R> constructor, AutofuzzCodegenVisitor visitor) { - if (visitor != null) { - // getCanonicalName is correct also for nested classes. - visitor.pushGroup( - String.format("new %s(", constructor.getDeclaringClass().getCanonicalName()), ", ", ")"); - } - Object[] arguments = consumeArguments(data, constructor, visitor); - if (visitor != null) { - visitor.popGroup(); - } - try { - return constructor.newInstance(arguments); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { - // This should never be reached as the logic in consume should prevent us from e.g. calling - // constructors of abstract classes or private constructors. - throw new AutofuzzError(getDebugSummary(constructor, null, arguments), e); - } catch (InvocationTargetException e) { - throw new AutofuzzInvocationException(e.getCause()); - } - } - - @SuppressWarnings("unchecked") - public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Consumer1.class, func.getClass()); - func.accept((T1) consumeChecked(data, types, 0)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2> void autofuzz(FuzzedDataProvider data, Consumer2<T1, T2> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Consumer2.class, func.getClass()); - func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2, T3> void autofuzz(FuzzedDataProvider data, Consumer3<T1, T2, T3> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Consumer3.class, func.getClass()); - func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), - (T3) consumeChecked(data, types, 2)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2, T3, T4> void autofuzz( - FuzzedDataProvider data, Consumer4<T1, T2, T3, T4> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Consumer4.class, func.getClass()); - func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), - (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2, T3, T4, T5> void autofuzz( - FuzzedDataProvider data, Consumer5<T1, T2, T3, T4, T5> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Consumer5.class, func.getClass()); - func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), - (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3), - (T5) consumeChecked(data, types, 4)); - } - - @SuppressWarnings("unchecked") - public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Function1.class, func.getClass()); - return func.apply((T1) consumeChecked(data, types, 0)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Function2.class, func.getClass()); - return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2, T3, R> R autofuzz(FuzzedDataProvider data, Function3<T1, T2, T3, R> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Function3.class, func.getClass()); - return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), - (T3) consumeChecked(data, types, 2)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2, T3, T4, R> R autofuzz( - FuzzedDataProvider data, Function4<T1, T2, T3, T4, R> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Function4.class, func.getClass()); - return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), - (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3)); - } - - @SuppressWarnings("unchecked") - public static <T1, T2, T3, T4, T5, R> R autofuzz( - FuzzedDataProvider data, Function5<T1, T2, T3, T4, T5, R> func) { - Class<?>[] types = TypeResolver.resolveRawArguments(Function5.class, func.getClass()); - return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1), - (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3), - (T5) consumeChecked(data, types, 4)); - } - - public static Object consume(FuzzedDataProvider data, Class<?> type) { - return consume(data, type, null); - } - - // Invariant: The Java source code representation of the returned object visited by visitor must - // represent an object of the same type as genericType. For example, a null value returned for - // the genericType Class<java.lang.String> should lead to the generated code - // "(java.lang.String) null", not just "null". This makes it possible to safely use consume in - // recursive argument constructions. - static Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor visitor) { - Class<?> type = getRawType(genericType); - if (type == byte.class || type == Byte.class) { - byte result = data.consumeByte(); - if (visitor != null) - visitor.pushElement(String.format("(byte) %s", result)); - return result; - } else if (type == short.class || type == Short.class) { - short result = data.consumeShort(); - if (visitor != null) - visitor.pushElement(String.format("(short) %s", result)); - return result; - } else if (type == int.class || type == Integer.class) { - int result = data.consumeInt(); - if (visitor != null) - visitor.pushElement(Integer.toString(result)); - return result; - } else if (type == long.class || type == Long.class) { - long result = data.consumeLong(); - if (visitor != null) - visitor.pushElement(String.format("%sL", result)); - return result; - } else if (type == float.class || type == Float.class) { - float result = data.consumeFloat(); - if (visitor != null) - visitor.pushElement(String.format("%sF", result)); - return result; - } else if (type == double.class || type == Double.class) { - double result = data.consumeDouble(); - if (visitor != null) - visitor.pushElement(Double.toString(result)); - return result; - } else if (type == boolean.class || type == Boolean.class) { - boolean result = data.consumeBoolean(); - if (visitor != null) - visitor.pushElement(Boolean.toString(result)); - return result; - } else if (type == char.class || type == Character.class) { - char result = data.consumeChar(); - if (visitor != null) - visitor.addCharLiteral(result); - return result; - } - // Sometimes, but rarely return null for non-primitive and non-boxed types. - // TODO: We might want to return null for boxed types sometimes, but this is complicated by the - // fact that TypeUtils can't distinguish between a primitive type and its wrapper and may - // thus easily cause false-positive NullPointerExceptions. - if (!type.isPrimitive() && data.consumeByte() == 0) { - if (visitor != null) { - if (type == Object.class) { - visitor.pushElement("null"); - } else { - visitor.pushElement(String.format("(%s) null", type.getCanonicalName())); - } - } - return null; - } - if (type == String.class || type == CharSequence.class) { - String result = data.consumeString(consumeArrayLength(data, 1)); - if (visitor != null) - visitor.addStringLiteral(result); - return result; - } else if (type.isArray()) { - if (type == byte[].class) { - byte[] result = data.consumeBytes(consumeArrayLength(data, Byte.BYTES)); - if (visitor != null) { - visitor.pushElement(IntStream.range(0, result.length) - .mapToObj(i -> "(byte) " + result[i]) - .collect(Collectors.joining(", ", "new byte[]{", "}"))); - } - return result; - } else if (type == int[].class) { - int[] result = data.consumeInts(consumeArrayLength(data, Integer.BYTES)); - if (visitor != null) { - visitor.pushElement(Arrays.stream(result) - .mapToObj(String::valueOf) - .collect(Collectors.joining(", ", "new int[]{", "}"))); - } - return result; - } else if (type == short[].class) { - short[] result = data.consumeShorts(consumeArrayLength(data, Short.BYTES)); - if (visitor != null) { - visitor.pushElement(IntStream.range(0, result.length) - .mapToObj(i -> "(short) " + result[i]) - .collect(Collectors.joining(", ", "new short[]{", "}"))); - } - return result; - } else if (type == long[].class) { - long[] result = data.consumeLongs(consumeArrayLength(data, Long.BYTES)); - if (visitor != null) { - visitor.pushElement(Arrays.stream(result) - .mapToObj(e -> e + "L") - .collect(Collectors.joining(", ", "new long[]{", "}"))); - } - return result; - } else if (type == boolean[].class) { - boolean[] result = data.consumeBooleans(consumeArrayLength(data, 1)); - if (visitor != null) { - visitor.pushElement( - Arrays.toString(result).replace(']', '}').replace("[", "new boolean[]{")); - } - return result; - } else { - if (visitor != null) { - visitor.pushGroup( - String.format("new %s[]{", type.getComponentType().getName()), ", ", "}"); - } - int remainingBytesBeforeFirstElementCreation = data.remainingBytes(); - Object firstElement = consume(data, type.getComponentType(), visitor); - int remainingBytesAfterFirstElementCreation = data.remainingBytes(); - int sizeOfElementEstimate = - remainingBytesBeforeFirstElementCreation - remainingBytesAfterFirstElementCreation; - Object array = Array.newInstance( - type.getComponentType(), consumeArrayLength(data, sizeOfElementEstimate)); - for (int i = 0; i < Array.getLength(array); i++) { - if (i == 0) { - Array.set(array, i, firstElement); - } else { - Array.set(array, i, consume(data, type.getComponentType(), visitor)); - } - } - if (visitor != null) { - if (Array.getLength(array) == 0) { - // We implicitly pushed the first element with the call to consume above, but it is not - // part of the array. - visitor.popElement(); - } - visitor.popGroup(); - } - return array; - } - } else if (type == ByteArrayInputStream.class || type == InputStream.class) { - byte[] array = data.consumeBytes(consumeArrayLength(data, Byte.BYTES)); - if (visitor != null) { - visitor.pushElement(IntStream.range(0, array.length) - .mapToObj(i -> "(byte) " + array[i]) - .collect(Collectors.joining( - ", ", "new java.io.ByteArrayInputStream(new byte[]{", "})"))); - } - return new ByteArrayInputStream(array); - } else if (type == Map.class) { - ParameterizedType mapType = (ParameterizedType) genericType; - if (mapType.getActualTypeArguments().length != 2) { - throw new AutofuzzError( - "Expected Map generic type to have two type parameters: " + mapType); - } - Type keyType = mapType.getActualTypeArguments()[0]; - Type valueType = mapType.getActualTypeArguments()[1]; - if (visitor != null) { - // Do not use Collectors.toMap() since it cannot handle null values. - // Also annotate the type of the entry stream since it might be empty, in which case type - // inference on the accumulator could fail. - visitor.pushGroup( - String.format("java.util.stream.Stream.<java.util.AbstractMap.SimpleEntry<%s, %s>>of(", - keyType.getTypeName(), valueType.getTypeName()), - ", ", - ").collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)"); - } - int remainingBytesBeforeFirstEntryCreation = data.remainingBytes(); - if (visitor != null) { - visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")"); - } - Object firstKey = consume(data, keyType, visitor); - Object firstValue = consume(data, valueType, visitor); - if (visitor != null) { - visitor.popGroup(); - } - int remainingBytesAfterFirstEntryCreation = data.remainingBytes(); - int sizeOfElementEstimate = - remainingBytesBeforeFirstEntryCreation - remainingBytesAfterFirstEntryCreation; - int mapSize = consumeArrayLength(data, sizeOfElementEstimate); - Map<Object, Object> map = new HashMap<>(mapSize); - for (int i = 0; i < mapSize; i++) { - if (i == 0) { - map.put(firstKey, firstValue); - } else { - if (visitor != null) { - visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")"); - } - map.put(consume(data, keyType, visitor), consume(data, valueType, visitor)); - if (visitor != null) { - visitor.popGroup(); - } - } - } - if (visitor != null) { - if (mapSize == 0) { - // We implicitly pushed the first entry with the call to consume above, but it is not - // part of the array. - visitor.popElement(); - } - visitor.popGroup(); - } - return map; - } else if (type.isEnum()) { - Enum<?> enumValue = (Enum<?>) data.pickValue(type.getEnumConstants()); - if (visitor != null) { - visitor.pushElement(String.format("%s.%s", type.getName(), enumValue.name())); - } - return enumValue; - } else if (type == Class.class) { - if (visitor != null) - visitor.pushElement(String.format("%s.class", YourAverageJavaClass.class.getName())); - return YourAverageJavaClass.class; - } else if (type == Method.class) { - if (visitor != null) { - throw new AutofuzzError("codegen has not been implemented for Method.class"); - } - return data.pickValue(sortExecutables(YourAverageJavaClass.class.getMethods())); - } else if (type == Constructor.class) { - if (visitor != null) { - throw new AutofuzzError("codegen has not been implemented for Constructor.class"); - } - return data.pickValue(sortExecutables(YourAverageJavaClass.class.getConstructors())); - } else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { - List<Class<?>> implementingClasses = implementingClassesCache.get(type); - if (implementingClasses == null) { - ClassGraph classGraph = - new ClassGraph().enableClassInfo().enableInterClassDependencies().rejectPackages( - "jaz.*"); - if (!isTest()) { - classGraph.rejectPackages("com.code_intelligence.jazzer.*"); - } - try (ScanResult result = classGraph.scan()) { - ClassInfoList children = - type.isInterface() ? result.getClassesImplementing(type) : result.getSubclasses(type); - implementingClasses = - children.getStandardClasses().filter(cls -> !cls.isAbstract()).loadClasses(); - implementingClassesCache.put(type, implementingClasses); - } - } - if (implementingClasses.isEmpty()) { - if (isDebug()) { - throw new AutofuzzConstructionException(String.format( - "Could not find classes implementing %s on the classpath", type.getName())); - } else { - throw new AutofuzzConstructionException(); - } - } - if (visitor != null) { - // This group will always have a single element: The instance of the implementing class. - visitor.pushGroup(String.format("(%s) ", type.getName()), "", ""); - } - Object result = consume(data, data.pickValue(implementingClasses), visitor); - if (visitor != null) { - visitor.popGroup(); - } - return result; - } else if (type.getConstructors().length > 0) { - Constructor<?> constructor = data.pickValue(sortExecutables(type.getConstructors())); - boolean applySetters = constructor.getParameterCount() == 0; - if (visitor != null && applySetters) { - // Embed the instance creation and setters into an immediately invoked lambda expression to - // turn them into an expression. - String uniqueVariableName = visitor.uniqueVariableName(); - visitor.pushGroup(String.format("((java.util.function.Supplier<%1$s>) (() -> {%1$s %2$s = ", - type.getCanonicalName(), uniqueVariableName), - String.format("; %s.", uniqueVariableName), - String.format("; return %s;})).get()", uniqueVariableName)); - } - Object obj = autofuzz(data, constructor, visitor); - if (applySetters) { - List<Method> potentialSetters = getPotentialSetters(type); - if (!potentialSetters.isEmpty()) { - List<Method> pickedSetters = - data.pickValues(potentialSetters, data.consumeInt(0, potentialSetters.size())); - for (Method setter : pickedSetters) { - autofuzz(data, setter, obj, visitor); - } - } - if (visitor != null) { - visitor.popGroup(); - } - } - return obj; - } - // We are out of more or less canonical ways to construct an instance of this class and have to - // resort to more heuristic approaches. - - // First, try to find nested classes with names ending in Builder and call a subset of their - // chaining methods. - List<Class<?>> nestedBuilderClasses = getNestedBuilderClasses(type); - if (!nestedBuilderClasses.isEmpty()) { - Class<?> pickedBuilder = data.pickValue(nestedBuilderClasses); - List<Method> cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder); - List<Method> originalObjectCreationMethods = getOriginalObjectCreationMethods(pickedBuilder); - - int pickedMethodsNumber = data.consumeInt(0, cascadingBuilderMethods.size()); - List<Method> pickedMethods = data.pickValues(cascadingBuilderMethods, pickedMethodsNumber); - Method builderMethod = data.pickValue(originalObjectCreationMethods); - - if (visitor != null) { - // Group for the chain of builder methods. - visitor.pushGroup("", ".", ""); - } - Object builderObj = - autofuzz(data, data.pickValue(sortExecutables(pickedBuilder.getConstructors())), visitor); - for (Method method : pickedMethods) { - builderObj = autofuzz(data, method, builderObj, visitor); - } - - try { - Object obj = autofuzz(data, builderMethod, builderObj, visitor); - if (visitor != null) { - visitor.popGroup(); - } - return obj; - } catch (Exception e) { - throw new AutofuzzConstructionException(e); - } - } - - // We ran out of ways to construct an instance of the requested type. If in debug mode, report - // more detailed information. - if (!isDebug()) { - throw new AutofuzzConstructionException(); - } else { - String summary = String.format( - "Failed to generate instance of %s:%nAccessible constructors: %s%nNested subclasses: %s%n", - type.getName(), - Arrays.stream(type.getConstructors()) - .map(Utils::getReadableDescriptor) - .collect(Collectors.joining(", ")), - Arrays.stream(type.getClasses()).map(Class::getName).collect(Collectors.joining(", "))); - throw new AutofuzzConstructionException(summary); - } - } - - static void rescanClasspath() { - implementingClassesCache.clear(); - } - - static boolean isTest() { - String value = System.getenv("JAZZER_AUTOFUZZ_TESTING"); - return value != null && !value.isEmpty(); - } - - static boolean isDebug() { - String value = System.getenv("JAZZER_AUTOFUZZ_DEBUG"); - return value != null && !value.isEmpty(); - } - - private static int consumeArrayLength(FuzzedDataProvider data, int sizeOfElement) { - // Spend at most half of the fuzzer input bytes so that the remaining arguments that require - // construction still have non-trivial data to work with. - int bytesToSpend = data.remainingBytes() / 2; - return bytesToSpend / Math.max(sizeOfElement, 1); - } - - private static String getDebugSummary( - Executable executable, Object thisObject, Object[] arguments) { - return String.format("%nMethod: %s::%s%s%nthis: %s%nArguments: %s", - executable.getDeclaringClass().getName(), executable.getName(), - Utils.getReadableDescriptor(executable), thisObject, - Arrays.stream(arguments) - .map(arg -> arg == null ? "null" : arg.toString()) - .collect(Collectors.joining(", "))); - } - - private static <T extends Executable> List<T> sortExecutables(T[] executables) { - List<T> list = Arrays.asList(executables); - sortExecutables(list); - return list; - } - - private static void sortExecutables(List<? extends Executable> executables) { - executables.sort(Comparator.comparing(Executable::getName).thenComparing(Utils::getDescriptor)); - } - - private static void sortClasses(List<? extends Class<?>> classes) { - classes.sort(Comparator.comparing(Class::getName)); - } - - private static List<Class<?>> getNestedBuilderClasses(Class<?> type) { - List<Class<?>> nestedBuilderClasses = nestedBuilderClassesCache.get(type); - if (nestedBuilderClasses == null) { - nestedBuilderClasses = Arrays.stream(type.getClasses()) - .filter(cls -> cls.getName().endsWith("Builder")) - .filter(cls -> !getOriginalObjectCreationMethods(cls).isEmpty()) - .collect(Collectors.toList()); - sortClasses(nestedBuilderClasses); - nestedBuilderClassesCache.put(type, nestedBuilderClasses); - } - return nestedBuilderClasses; - } - - private static List<Method> getOriginalObjectCreationMethods(Class<?> builder) { - List<Method> originalObjectCreationMethods = originalObjectCreationMethodsCache.get(builder); - if (originalObjectCreationMethods == null) { - originalObjectCreationMethods = - Arrays.stream(builder.getMethods()) - .filter(m -> m.getReturnType() == builder.getEnclosingClass()) - .collect(Collectors.toList()); - sortExecutables(originalObjectCreationMethods); - originalObjectCreationMethodsCache.put(builder, originalObjectCreationMethods); - } - return originalObjectCreationMethods; - } - - private static List<Method> getCascadingBuilderMethods(Class<?> builder) { - List<Method> cascadingBuilderMethods = cascadingBuilderMethodsCache.get(builder); - if (cascadingBuilderMethods == null) { - cascadingBuilderMethods = Arrays.stream(builder.getMethods()) - .filter(m -> m.getReturnType() == builder) - .collect(Collectors.toList()); - sortExecutables(cascadingBuilderMethods); - cascadingBuilderMethodsCache.put(builder, cascadingBuilderMethods); - } - return cascadingBuilderMethods; - } - - private static List<Method> getPotentialSetters(Class<?> type) { - List<Method> potentialSetters = new ArrayList<>(); - Method[] methods = type.getMethods(); - for (Method method : methods) { - if (void.class.equals(method.getReturnType()) && method.getParameterCount() == 1 - && method.getName().startsWith("set")) { - potentialSetters.add(method); - } - } - sortExecutables(potentialSetters); - return potentialSetters; - } - - private static Object[] consumeArguments( - FuzzedDataProvider data, Executable executable, AutofuzzCodegenVisitor visitor) { - Object[] result; - try { - result = Arrays.stream(executable.getGenericParameterTypes()) - .map((type) -> consume(data, type, visitor)) - .toArray(); - return result; - } catch (AutofuzzConstructionException e) { - // Do not nest AutofuzzConstructionExceptions. - throw e; - } catch (AutofuzzInvocationException e) { - // If an invocation fails while creating the arguments for another invocation, the exception - // should not be reported, so we rewrap it. - throw new AutofuzzConstructionException(e.getCause()); - } catch (Throwable t) { - throw new AutofuzzConstructionException(t); - } - } - - private static Object consumeChecked(FuzzedDataProvider data, Class<?>[] types, int i) { - if (types[i] == Unknown.class) { - throw new AutofuzzError("Failed to determine type of argument " + (i + 1)); - } - Object result; - try { - result = consume(data, types[i]); - } catch (AutofuzzConstructionException e) { - // Do not nest AutofuzzConstructionExceptions. - throw e; - } catch (AutofuzzInvocationException e) { - // If an invocation fails while creating the arguments for another invocation, the exception - // should not be reported, so we rewrap it. - throw new AutofuzzConstructionException(e.getCause()); - } catch (Throwable t) { - throw new AutofuzzConstructionException(t); - } - if (result != null && !types[i].isAssignableFrom(result.getClass())) { - throw new AutofuzzError("consume returned " + result.getClass() + ", but need " + types[i]); - } - return result; - } - - private static Class<?> getRawType(Type genericType) { - if (genericType instanceof Class<?>) { - return (Class<?>) genericType; - } else if (genericType instanceof ParameterizedType) { - return getRawType(((ParameterizedType) genericType).getRawType()); - } else if (genericType instanceof WildcardType) { - // TODO: Improve this. - return Object.class; - } else if (genericType instanceof TypeVariable<?>) { - throw new AutofuzzError("Did not expect genericType to be a TypeVariable: " + genericType); - } else if (genericType instanceof GenericArrayType) { - // TODO: Improve this; - return Object[].class; - } else { - throw new AutofuzzError("Got unexpected class implementing Type: " + genericType); - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/YourAverageJavaClass.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/YourAverageJavaClass.java deleted file mode 100644 index 452ca878..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/YourAverageJavaClass.java +++ /dev/null @@ -1,229 +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.autofuzz; - -import java.io.Closeable; -import java.io.IOException; -import java.io.Serializable; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -// Returned by Meta when asked to construct a Class object. Its purpose is to be a relatively -// "interesting" Java data class that can serve as the target of methods that perform some kind of -// reflection or deserialization. -public class YourAverageJavaClass implements Cloneable, Closeable, Serializable { - public byte aByte; - public boolean aBoolean; - public double aDouble; - public float aFloat; - public int anInt; - public transient int transientInt; - public long aLong; - public short aShort; - public volatile short volatileShort; - public String string; - public byte[] bytes; - public List<YourAverageJavaClass> list; - public Map<String, YourAverageJavaClass> map; - - // Everything below has been automatically generated (apart from a minor modification to clone()); - - public YourAverageJavaClass(byte aByte, boolean aBoolean, double aDouble, float aFloat, int anInt, - int transientInt, long aLong, short aShort, short volatileShort, String string) { - this.aByte = aByte; - this.aBoolean = aBoolean; - this.aDouble = aDouble; - this.aFloat = aFloat; - this.anInt = anInt; - this.transientInt = transientInt; - this.aLong = aLong; - this.aShort = aShort; - this.volatileShort = volatileShort; - this.string = string; - } - - public YourAverageJavaClass() {} - - public YourAverageJavaClass(byte aByte, boolean aBoolean, double aDouble, float aFloat, int anInt, - int transientInt, long aLong, short aShort, short volatileShort, String string, byte[] bytes, - List<YourAverageJavaClass> list, Map<String, YourAverageJavaClass> map) { - this.aByte = aByte; - this.aBoolean = aBoolean; - this.aDouble = aDouble; - this.aFloat = aFloat; - this.anInt = anInt; - this.transientInt = transientInt; - this.aLong = aLong; - this.aShort = aShort; - this.volatileShort = volatileShort; - this.string = string; - this.bytes = bytes; - this.list = list; - this.map = map; - } - - public byte getaByte() { - return aByte; - } - - public void setaByte(byte aByte) { - this.aByte = aByte; - } - - public boolean isaBoolean() { - return aBoolean; - } - - public void setaBoolean(boolean aBoolean) { - this.aBoolean = aBoolean; - } - - public double getaDouble() { - return aDouble; - } - - public void setaDouble(double aDouble) { - this.aDouble = aDouble; - } - - public float getaFloat() { - return aFloat; - } - - public void setaFloat(float aFloat) { - this.aFloat = aFloat; - } - - public int getAnInt() { - return anInt; - } - - public void setAnInt(int anInt) { - this.anInt = anInt; - } - - public int getTransientInt() { - return transientInt; - } - - public void setTransientInt(int transientInt) { - this.transientInt = transientInt; - } - - public long getaLong() { - return aLong; - } - - public void setaLong(long aLong) { - this.aLong = aLong; - } - - public short getaShort() { - return aShort; - } - - public void setaShort(short aShort) { - this.aShort = aShort; - } - - public short getVolatileShort() { - return volatileShort; - } - - public void setVolatileShort(short volatileShort) { - this.volatileShort = volatileShort; - } - - public String getString() { - return string; - } - - public void setString(String string) { - this.string = string; - } - - public byte[] getBytes() { - return bytes; - } - - public void setBytes(byte[] bytes) { - this.bytes = bytes; - } - - public List<YourAverageJavaClass> getList() { - return list; - } - - public void setList(List<YourAverageJavaClass> list) { - this.list = list; - } - - public Map<String, YourAverageJavaClass> getMap() { - return map; - } - - public void setMap(Map<String, YourAverageJavaClass> map) { - this.map = map; - } - - @Override - public YourAverageJavaClass clone() { - try { - YourAverageJavaClass clone = (YourAverageJavaClass) super.clone(); - clone.transientInt = transientInt + 1; - clone.volatileShort = (short) (volatileShort - 1); - return clone; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof YourAverageJavaClass)) - return false; - YourAverageJavaClass that = (YourAverageJavaClass) o; - return aByte == that.aByte && aBoolean == that.aBoolean - && Double.compare(that.aDouble, aDouble) == 0 && Float.compare(that.aFloat, aFloat) == 0 - && anInt == that.anInt && transientInt == that.transientInt && aLong == that.aLong - && aShort == that.aShort && volatileShort == that.volatileShort - && Objects.equals(string, that.string) && Arrays.equals(bytes, that.bytes) - && Objects.equals(list, that.list) && Objects.equals(map, that.map); - } - - @Override - public int hashCode() { - int result = Objects.hash(aByte, aBoolean, aDouble, aFloat, anInt, transientInt, aLong, aShort, - volatileShort, string, list, map); - result = 31 * result + Arrays.hashCode(bytes); - return result; - } - - @Override - public String toString() { - return "YourAverageJavaClass{" - + "aByte=" + aByte + ", aBoolean=" + aBoolean + ", aDouble=" + aDouble + ", aFloat=" - + aFloat + ", anInt=" + anInt + ", transientInt=" + transientInt + ", aLong=" + aLong - + ", aShort=" + aShort + ", volatileShort=" + volatileShort + ", string='" + string + '\'' - + ", bytes=" + Arrays.toString(bytes) + ", list=" + list + ", map=" + map + '}'; - } - - @Override - public void close() throws IOException {} -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel deleted file mode 100644 index db93dcae..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ /dev/null @@ -1,35 +0,0 @@ -load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") -load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar") - -kt_jvm_library( - name = "instrumentor", - srcs = [ - "ClassInstrumentor.kt", - "CoverageRecorder.kt", - "DescriptorUtils.kt", - "DeterministicRandom.kt", - "EdgeCoverageInstrumentor.kt", - "Hook.kt", - "HookInstrumentor.kt", - "HookMethodVisitor.kt", - "Hooks.kt", - "Instrumentor.kt", - "StaticMethodStrategy.java", - "TraceDataFlowInstrumentor.kt", - ], - visibility = [ - "//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", - "//agent/src/main/java/com/code_intelligence/jazzer/agent:__pkg__", - "//agent/src/test/java/com/code_intelligence/jazzer/instrumentor:__pkg__", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", - ], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/runtime", - "//agent/src/main/java/com/code_intelligence/jazzer/utils", - "@com_github_classgraph_classgraph//:classgraph", - "@com_github_jetbrains_kotlin//:kotlin-reflect", - "@jazzer_jacoco//:jacoco_internal", - "@org_ow2_asm_asm//jar", - "@org_ow2_asm_asm_commons//jar", - ], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt deleted file mode 100644 index 4c3eabcb..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt +++ /dev/null @@ -1,53 +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.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 constructor(bytecode: ByteArray) { - - var instrumentedBytecode = bytecode - private set - - fun coverage(initialEdgeId: Int): Int { - val edgeCoverageInstrumentor = EdgeCoverageInstrumentor( - defaultEdgeCoverageStrategy, - defaultCoverageMap, - initialEdgeId, - ) - instrumentedBytecode = edgeCoverageInstrumentor.instrument(instrumentedBytecode) - return edgeCoverageInstrumentor.numEdges - } - - fun traceDataFlow(instrumentations: Set<InstrumentationType>) { - instrumentedBytecode = TraceDataFlowInstrumentor(instrumentations).instrument(instrumentedBytecode) - } - - fun hooks(hooks: Iterable<Hook>) { - instrumentedBytecode = HookInstrumentor( - hooks, - java6Mode = extractClassFileMajorVersion(instrumentedBytecode) < 51 - ).instrument(instrumentedBytecode) - } - - companion object { - val defaultEdgeCoverageStrategy = StaticMethodStrategy() - val defaultCoverageMap = CoverageMap::class.java - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt deleted file mode 100644 index 098cf389..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ /dev/null @@ -1,246 +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.instrumentor - -import com.code_intelligence.jazzer.runtime.CoverageMap -import com.code_intelligence.jazzer.third_party.org.jacoco.core.analysis.CoverageBuilder -import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionData -import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataStore -import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataWriter -import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.SessionInfo -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.data.CRC64 -import com.code_intelligence.jazzer.utils.ClassNameGlobber -import io.github.classgraph.ClassGraph -import java.io.File -import java.io.FileOutputStream -import java.io.OutputStream -import java.time.Instant -import java.util.UUID - -private data class InstrumentedClassInfo( - val classId: Long, - val initialEdgeId: Int, - val nextEdgeId: Int, - val bytecode: ByteArray, -) - -object CoverageRecorder { - var classNameGlobber = ClassNameGlobber(emptyList(), emptyList()) - private val instrumentedClassInfo = mutableMapOf<String, InstrumentedClassInfo>() - private var startTimestamp: Instant? = null - private val additionalCoverage = mutableSetOf<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 - ) - } - - /** - * Manually records coverage IDs based on the current state of [CoverageMap]. - * Should be called after static initializers have run. - */ - @JvmStatic - fun updateCoveredIdsWithCoverageMap() { - additionalCoverage.addAll(CoverageMap.getCoveredIds()) - } - - /** - * [dumpCoverageReport] dumps a human-readable coverage report of files using any [coveredIds] to [dumpFileName]. - */ - @JvmStatic - fun dumpCoverageReport(coveredIds: IntArray, dumpFileName: String) { - File(dumpFileName).bufferedWriter().use { writer -> - writer.write(computeFileCoverage(coveredIds)) - } - } - - private fun computeFileCoverage(coveredIds: IntArray): String { - fun Double.format(digits: Int) = "%.${digits}f".format(this) - val coverage = analyzeCoverage(coveredIds.toSet()) ?: return "No classes were instrumented" - return coverage.sourceFiles.joinToString( - "\n", - prefix = "Branch coverage:\n", - postfix = "\n\n" - ) { fileCoverage -> - 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() - } - } - - /** - * [dumpJacocoCoverage] dumps the JaCoCo coverage of files using any [coveredIds] to [dumpFileName]. - * JaCoCo only exports coverage for files containing at least one coverage data point. The dump - * can be used by the JaCoCo report command to create reports also including not covered files. - */ - @JvmStatic - fun dumpJacocoCoverage(coveredIds: IntArray, dumpFileName: String) { - FileOutputStream(dumpFileName).use { outStream -> - dumpJacocoCoverage(coveredIds, outStream) - } - } - - /** - * [dumpJacocoCoverage] dumps the JaCoCo coverage of files using any [coveredIds] to [outStream]. - */ - @JvmStatic - fun dumpJacocoCoverage(coveredIds: IntArray, outStream: OutputStream) { - // Return if no class has been instrumented. - val startTimestamp = startTimestamp ?: return - - // Update the list of covered IDs with the coverage information for the current run. - updateCoveredIdsWithCoverageMap() - - val dumpTimestamp = Instant.now() - val outWriter = ExecutionDataWriter(outStream) - outWriter.visitSessionInfo( - SessionInfo(UUID.randomUUID().toString(), startTimestamp.epochSecond, dumpTimestamp.epochSecond) - ) - analyzeJacocoCoverage(coveredIds.toSet()).accept(outWriter) - } - - /** - * Build up a JaCoCo [ExecutionDataStore] based on [coveredIds] containing the internally gathered coverage information. - */ - private fun analyzeJacocoCoverage(coveredIds: Set<Int>): ExecutionDataStore { - val executionDataStore = ExecutionDataStore() - val sortedCoveredIds = (additionalCoverage + coveredIds).sorted().toIntArray() - for ((internalClassName, info) in instrumentedClassInfo) { - // Determine the subarray of coverage IDs in sortedCoveredIds that contains the IDs generated while - // instrumenting the current class. Since the ID array is sorted, use binary search. - var coveredIdsStart = sortedCoveredIds.binarySearch(info.initialEdgeId) - if (coveredIdsStart < 0) { - coveredIdsStart = -(coveredIdsStart + 1) - } - var coveredIdsEnd = sortedCoveredIds.binarySearch(info.nextEdgeId) - if (coveredIdsEnd < 0) { - coveredIdsEnd = -(coveredIdsEnd + 1) - } - if (coveredIdsStart == coveredIdsEnd) { - // No coverage data for the class. - continue - } - check(coveredIdsStart in 0 until coveredIdsEnd && coveredIdsEnd <= sortedCoveredIds.size) { - "Invalid range [$coveredIdsStart, $coveredIdsEnd) with coveredIds.size=${sortedCoveredIds.size}" - } - // 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() - .map { - val globalEdgeId = sortedCoveredIds[it] - globalEdgeId - info.initialEdgeId - } - .forEach { classLocalEdgeId -> - probes[classLocalEdgeId] = true - } - executionDataStore.visitClassExecution(ExecutionData(info.classId, internalClassName, probes)) - } - return executionDataStore - } - - /** - * Create a [CoverageBuilder] containing all classes matching the include/exclude pattern and their coverage statistics. - */ - fun analyzeCoverage(coveredIds: Set<Int>): CoverageBuilder? { - return try { - val coverage = CoverageBuilder() - analyzeAllUncoveredClasses(coverage) - val executionDataStore = analyzeJacocoCoverage(coveredIds) - for ((internalClassName, info) in instrumentedClassInfo) { - EdgeCoverageInstrumentor(ClassInstrumentor.defaultEdgeCoverageStrategy, ClassInstrumentor.defaultCoverageMap, 0) - .analyze( - executionDataStore, - coverage, - info.bytecode, - internalClassName - ) - } - coverage - } catch (e: Exception) { - e.printStackTrace() - null - } - } - - /** - * Traverses the entire classpath and analyzes all uncovered classes that match the include/exclude pattern. - * The returned [CoverageBuilder] will report coverage information for *all* classes on the classpath, not just - * those that were loaded while the fuzzer ran. - */ - private fun analyzeAllUncoveredClasses(coverage: CoverageBuilder): CoverageBuilder { - val coveredClassNames = instrumentedClassInfo - .keys - .asSequence() - .map { it.replace('/', '.') } - .toSet() - ClassGraph() - .enableClassInfo() - .ignoreClassVisibility() - .rejectPackages( - // Always exclude Jazzer-internal packages (including ClassGraph itself) from coverage reports. Classes - // from the Java standard library are never traversed. - "com.code_intelligence.jazzer.*", - "jaz", - ) - .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() - result.allClasses - .asSequence() - .filter { classInfo -> classNameGlobber.includes(classInfo.name) } - .filterNot { classInfo -> classInfo.name in coveredClassNames } - .forEach { classInfo -> - classInfo.resource.use { resource -> - EdgeCoverageInstrumentor(ClassInstrumentor.defaultEdgeCoverageStrategy, ClassInstrumentor.defaultCoverageMap, 0).analyze( - emptyExecutionDataStore, - coverage, - resource.load(), - classInfo.name.replace('.', '/') - ) - } - } - } - return coverage - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt deleted file mode 100644 index 80cbe80b..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt +++ /dev/null @@ -1,90 +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.instrumentor - -internal fun isPrimitiveType(typeDescriptor: String): Boolean { - return 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 -} - -// 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(";")) { - typeDescriptor.substring(1, typeDescriptor.length - 1) - } else { - typeDescriptor - } -} - -internal fun extractParameterTypeDescriptors(methodDescriptor: String): List<String> { - require(methodDescriptor.startsWith('(')) { "Method descriptor must start with '('" } - val endOfParameterPart = methodDescriptor.indexOf(')') - 1 - require(endOfParameterPart >= 0) { "Method descriptor must contain ')'" } - var remainingDescriptorList = methodDescriptor.substring(1..endOfParameterPart) - val parameterDescriptors = mutableListOf<String>() - while (remainingDescriptorList.isNotEmpty()) { - val nextDescriptor = extractNextTypeDescriptor(remainingDescriptorList) - parameterDescriptors.add(nextDescriptor) - remainingDescriptorList = remainingDescriptorList.removePrefix(nextDescriptor) - } - return parameterDescriptors -} - -internal fun extractReturnTypeDescriptor(methodDescriptor: String): String { - require(methodDescriptor.startsWith('(')) { "Method descriptor must start with '('" } - val endBracketPos = methodDescriptor.indexOf(')') - require(endBracketPos >= 0) { "Method descriptor must contain ')'" } - val startOfReturnValue = endBracketPos + 1 - return extractNextTypeDescriptor(methodDescriptor.substring(startOfReturnValue)) -} - -private fun extractNextTypeDescriptor(input: String): String { - require(input.isNotEmpty()) { "Type descriptor must not be empty" } - // Skip over arbitrarily many '[' to support multi-dimensional arrays. - val firstNonArrayPrefixCharPos = input.indexOfFirst { it != '[' } - require(firstNonArrayPrefixCharPos >= 0) { "Array descriptor must contain type" } - val firstTypeChar = input[firstNonArrayPrefixCharPos] - return when { - // Primitive type - isPrimitiveType(firstTypeChar) -> { - input.substring(0..firstNonArrayPrefixCharPos) - } - // Object type - firstTypeChar == 'L' -> { - val endOfClassNamePos = input.indexOf(';') - require(endOfClassNamePos > 0) { "Class type indicated by L must end with ;" } - input.substring(0..endOfClassNamePos) - } - // Invalid type - else -> { - throw IllegalArgumentException("Invalid type: $firstTypeChar") - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt deleted file mode 100644 index d4210dc4..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt +++ /dev/null @@ -1,35 +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.instrumentor - -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() - } - setSeed(contextHash) - } - - fun nextInt(bound: Int) = random.nextInt(bound) - - fun nextInt() = random.nextInt() -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt deleted file mode 100644 index 8fb3dc2b..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt +++ /dev/null @@ -1,187 +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.instrumentor - -import com.code_intelligence.jazzer.third_party.org.jacoco.core.analysis.Analyzer -import com.code_intelligence.jazzer.third_party.org.jacoco.core.analysis.ICoverageVisitor -import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataStore -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.ClassProbesAdapter -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.ClassProbesVisitor -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.IClassProbesAdapterFactory -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.ClassInstrumenter -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.IProbeArrayStrategy -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.IProbeInserterFactory -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.InstrSupport -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.ProbeInserter -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.MethodVisitor -import java.lang.invoke.MethodHandle -import java.lang.invoke.MethodHandles.publicLookup -import java.lang.invoke.MethodType.methodType -import kotlin.math.max - -/** - * A particular way to instrument bytecode for edge coverage using a coverage map class available to - * 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 - * instrumentation injected in [loadLocalVariable]. - */ - fun instrumentControlFlowEdge( - mv: MethodVisitor, - edgeId: Int, - variable: Int, - coverageMapInternalClassName: String - ) - - /** - * The maximal number of stack elements used by [instrumentControlFlowEdge]. - */ - val instrumentControlFlowEdgeStackSize: Int - - /** - * The type of the local variable used by the instrumentation in the format used by - * [MethodVisitor.visitFrame]'s `local` parameter, or `null` if the instrumentation does not use - * one. - * @see https://asm.ow2.io/javadoc/org/objectweb/asm/MethodVisitor.html#visitFrame(int,int,java.lang.Object%5B%5D,int,java.lang.Object%5B%5D) - */ - val localVariableType: Any? - - /** - * 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) - - /** - * The maximal number of stack elements used by [loadLocalVariable]. - */ - val loadLocalVariableStackSize: Int -} - -// An instance of EdgeCoverageInstrumentor should only be used to instrument a single class as it -// internally tracks the edge IDs, which have to be globally unique. -class EdgeCoverageInstrumentor( - private val strategy: EdgeCoverageStrategy, - /** - * The class must have the following public static member - * - method enlargeIfNeeded(int nextEdgeId): Called before a new edge ID is emitted. - */ - coverageMapClass: Class<*>, - private val initialEdgeId: Int, -) : Instrumentor { - private var nextEdgeId = initialEdgeId - - private val coverageMapInternalClassName = coverageMapClass.name.replace('.', '/') - private val enlargeIfNeeded: MethodHandle = - publicLookup().findStatic( - coverageMapClass, - "enlargeIfNeeded", - methodType( - Void::class.javaPrimitiveType, - Int::class.javaPrimitiveType - ) - ) - - override fun instrument(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) - ) - reader.accept(visitor, ClassReader.EXPAND_FRAMES) - return writer.toByteArray() - } - - fun analyze(executionData: ExecutionDataStore, coverageVisitor: ICoverageVisitor, bytecode: ByteArray, internalClassName: String) { - Analyzer(executionData, coverageVisitor, edgeCoverageClassProbesAdapterFactory).run { - analyzeClass(bytecode, internalClassName) - } - } - - val numEdges - get() = nextEdgeId - initialEdgeId - - private fun nextEdgeId(): Int { - enlargeIfNeeded.invokeExact(nextEdgeId) - return nextEdgeId++ - } - - /** - * A [ProbeInserter] that injects bytecode instrumentation at every control flow edge and - * modifies the stack size and number of local variables accordingly. - */ - private inner class EdgeCoverageProbeInserter( - access: Int, - name: String, - desc: String, - mv: MethodVisitor, - arrayStrategy: IProbeArrayStrategy, - ) : ProbeInserter(access, name, desc, mv, arrayStrategy) { - override fun insertProbe(id: Int) { - strategy.instrumentControlFlowEdge(mv, id, variable, coverageMapInternalClassName) - } - - 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) - } - - override fun getLocalVariableType() = strategy.localVariableType - } - - private val edgeCoverageProbeInserterFactory = - IProbeInserterFactory { access, name, desc, mv, arrayStrategy -> - EdgeCoverageProbeInserter(access, name, desc, mv, arrayStrategy) - } - - private inner class EdgeCoverageClassProbesAdapter(private val cpv: ClassProbesVisitor, trackFrames: Boolean) : - ClassProbesAdapter(cpv, trackFrames) { - override fun nextId(): Int = nextEdgeId() - - override fun visitEnd() { - cpv.visitTotalProbeCount(numEdges) - // Avoid calling super.visitEnd() as that invokes cpv.visitTotalProbeCount with an - // incorrect value of `count`. - cv.visitEnd() - } - } - - 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 - } - - override fun addMembers(cv: ClassVisitor, probeCount: Int) {} - } -} - -fun MethodVisitor.push(value: Int) { - InstrSupport.push(this, value) -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt deleted file mode 100644 index ff68ad94..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ /dev/null @@ -1,133 +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:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - -package com.code_intelligence.jazzer.instrumentor - -import com.code_intelligence.jazzer.api.HookType -import com.code_intelligence.jazzer.api.MethodHook -import com.code_intelligence.jazzer.utils.descriptor -import java.lang.invoke.MethodHandle -import java.lang.reflect.Method -import java.lang.reflect.Modifier - -class Hook private constructor( - private val targetClassName: String, - val hookType: HookType, - val targetMethodName: String, - val targetMethodDescriptor: String?, - val additionalClassesToHook: List<String>, - val targetInternalClassName: String, - private val targetReturnTypeDescriptor: String?, - private val targetWrappedReturnTypeDescriptor: String?, - private val hookClassName: String, - val hookInternalClassName: String, - val hookMethodName: String, - val hookMethodDescriptor: String -) { - - override fun toString(): String { - return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName $additionalClassesToHook" - } - - companion object { - fun createAndVerifyHook(hookMethod: Method, hookData: MethodHook, className: String): Hook { - return 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) } - val hookClassName: String = hookMethod.declaringClass.name - return Hook( - targetClassName = targetClassName, - hookType = annotation.type, - targetMethodName = annotation.targetMethod, - targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotBlank() }, - additionalClassesToHook = annotation.additionalClassesToHook.asList(), - targetInternalClassName = targetClassName.replace('.', '/'), - targetReturnTypeDescriptor = targetReturnTypeDescriptor, - targetWrappedReturnTypeDescriptor = targetReturnTypeDescriptor?.let { getWrapperTypeDescriptor(it) }, - hookClassName = hookClassName, - hookInternalClassName = hookClassName.replace('.', '/'), - hookMethodName = hookMethod.name, - hookMethodDescriptor = hookMethod.descriptor - ) - } - - 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" } - - // 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.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[2] == Array<Object>::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 == "<init>") { - 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 - // known, it must not be a primitive value. - if (potentialHook.hookType == HookType.AFTER) { - if (potentialHook.targetReturnTypeDescriptor != null) { - require( - parameterTypes[4] == java.lang.Object::class.java || - parameterTypes[4].descriptor == potentialHook.targetWrappedReturnTypeDescriptor - ) { - "$potentialHook: fifth parameter must have type Object or match the descriptor ${potentialHook.targetWrappedReturnTypeDescriptor}" - } - } else { - require(!parameterTypes[4].isPrimitive) { - "$potentialHook: fifth parameter must not be a primitive type, use a boxed type instead" - } - } - } - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt deleted file mode 100644 index 6db76605..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt +++ /dev/null @@ -1,48 +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.instrumentor - -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.MethodVisitor - -internal class HookInstrumentor(private val hooks: Iterable<Hook>, private val java6Mode: Boolean) : Instrumentor { - - private lateinit var random: DeterministicRandom - - override fun instrument(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<String>?, - ): MethodVisitor? { - val mv = cv.visitMethod(access, name, descriptor, signature, exceptions) ?: return null - return if (shouldInstrument(access)) - makeHookMethodVisitor(access, descriptor, mv, hooks, java6Mode, random) - else - mv - } - } - reader.accept(interceptor, ClassReader.EXPAND_FRAMES) - return writer.toByteArray() - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt deleted file mode 100644 index 1694be58..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt +++ /dev/null @@ -1,399 +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.instrumentor - -import com.code_intelligence.jazzer.api.HookType -import org.objectweb.asm.Handle -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes -import org.objectweb.asm.Type -import org.objectweb.asm.commons.LocalVariablesSorter -import java.util.concurrent.atomic.AtomicBoolean - -internal fun makeHookMethodVisitor( - access: Int, - descriptor: String?, - methodVisitor: MethodVisitor?, - hooks: Iterable<Hook>, - java6Mode: Boolean, - random: DeterministicRandom, -): MethodVisitor { - return HookMethodVisitor(access, descriptor, methodVisitor, hooks, java6Mode, random).lvs -} - -private class HookMethodVisitor( - access: Int, - descriptor: String?, - methodVisitor: MethodVisitor?, - hooks: Iterable<Hook>, - private val java6Mode: Boolean, - private val random: DeterministicRandom, -) : MethodVisitor(Instrumentor.ASM_API_VERSION, methodVisitor) { - - companion object { - private val showUnsupportedHookWarning = AtomicBoolean(true) - } - - val lvs = object : LocalVariablesSorter(Instrumentor.ASM_API_VERSION, access, descriptor, this) { - override fun updateNewLocals(newLocals: Array<Any>) { - // 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}" - hookKey - } - - override fun visitMethodInsn( - opcode: Int, - owner: String, - methodName: String, - methodDescriptor: String, - isInterface: Boolean, - ) { - if (!isMethodInvocationOp(opcode)) { - mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) - return - } - handleMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) - } - - fun handleMethodInsn( - opcode: Int, - owner: String, - methodName: String, - methodDescriptor: String, - isInterface: Boolean, - ) { - val matchingHooks = findMatchingHooks(owner, methodName, methodDescriptor) - - if (matchingHooks.isEmpty()) { - mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) - return - } - - val paramDescriptors = extractParameterTypeDescriptors(methodDescriptor) - val localObjArr = storeMethodArguments(paramDescriptors) - // If the method we're hooking is not static there is now a reference to - // the object the method was invoked on at the top of the stack. - // If the method is static, that object is missing. We make up for it by pushing a null ref. - if (opcode == Opcodes.INVOKESTATIC) { - mv.visitInsn(Opcodes.ACONST_NULL) - } - - // Save the owner object to a new local variable - val ownerDescriptor = "L$owner;" - val localOwnerObj = lvs.newLocal(Type.getType(ownerDescriptor)) - mv.visitVarInsn(Opcodes.ASTORE, localOwnerObj) // consume objectref - // We now removed all values for the original method call from the operand stack - // and saved them to local variables. - - val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor) - // Create a local variable to store the return value - val localReturnObj = lvs.newLocal(Type.getType(getWrapperTypeDescriptor(returnTypeDescriptor))) - - matchingHooks.forEachIndexed { index, hook -> - // The hookId is used to identify a call site. - val hookId = random.nextInt() - - // Start to build the arguments for the hook method. - if (methodName == "<init>") { - // Constructor is invoked on an uninitialized object, and that's still on the stack. - // In case of REPLACE pop it from the stack and replace it afterwards with the returned - // one from the hook. - if (hook.hookType == HookType.REPLACE) { - mv.visitInsn(Opcodes.POP) - } - // Special case for constructors: - // We cannot create a MethodHandle for a constructor, so we push null instead. - mv.visitInsn(Opcodes.ACONST_NULL) // push nullref - // Only pass the this object if it has been initialized by the time the hook is invoked. - if (hook.hookType == HookType.AFTER) { - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) - } else { - mv.visitInsn(Opcodes.ACONST_NULL) // push nullref - } - } 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 - } - if (java6Mode) { - // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50). - mv.visitInsn(Opcodes.ACONST_NULL) // push nullref - } else { - mv.visitLdcInsn( - Handle( - handleOpcode, - owner, - methodName, - methodDescriptor, - isInterface - ) - ) // push MethodHandle - } - // Stack layout: ... | MethodHandle (objectref) - // Push the owner object again - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) - } - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) - // Push a reference to our object array with the saved arguments - mv.visitVarInsn(Opcodes.ALOAD, localObjArr) - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) - // Push the hook id - mv.visitLdcInsn(hookId) - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) - // How we proceed depends on the type of hook we want to implement - when (hook.hookType) { - HookType.BEFORE -> { - // Call the hook method - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - hook.hookInternalClassName, - hook.hookMethodName, - hook.hookMethodDescriptor, - false - ) - - // Call the original method if this is the last BEFORE hook. If not, the original method will be - // called by the next AFTER hook. - if (index == matchingHooks.lastIndex) { - // Stack layout: ... - // Push the values for the original method call onto the stack again - if (opcode != Opcodes.INVOKESTATIC) { - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object - } - loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments - // Stack layout: ... | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ... - mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) - } - } - HookType.REPLACE -> { - // Call the hook method - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - hook.hookInternalClassName, - hook.hookMethodName, - hook.hookMethodDescriptor, - false - ) - // Stack layout: ... | [return value (primitive/objectref)] - // Check if we need to process the return value - if (returnTypeDescriptor != "V") { - val hookMethodReturnType = extractReturnTypeDescriptor(hook.hookMethodDescriptor) - // if the hook method's return type is primitive we don't need to unwrap or cast it - if (!isPrimitiveType(hookMethodReturnType)) { - // Check if the returned object type is different than the one that should be returned - // If a primitive should be returned we check it's wrapper type - val expectedType = getWrapperTypeDescriptor(returnTypeDescriptor) - if (expectedType != hookMethodReturnType) { - // Cast object - mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(expectedType)) - } - // Check if we need to unwrap the returned object - unwrapTypeIfPrimitive(returnTypeDescriptor) - } - } - } - HookType.AFTER -> { - // Call the original method before the first AFTER hook - if (index == 0 || matchingHooks[index - 1].hookType != HookType.AFTER) { - // Push the values for the original method call again onto the stack - if (opcode != Opcodes.INVOKESTATIC) { - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object - } - loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) - // | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ... - mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) - if (returnTypeDescriptor == "V") { - // If the method didn't return anything, we push a nullref as placeholder - mv.visitInsn(Opcodes.ACONST_NULL) // push nullref - } - // Wrap return value if it is a primitive type - wrapTypeIfPrimitive(returnTypeDescriptor) - mv.visitVarInsn(Opcodes.ASTORE, localReturnObj) // consume objectref - } - mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref - - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) - // | return value (objectref) - // Store the result value in a local variable (but keep it on the stack) - // Call the hook method - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - hook.hookInternalClassName, - hook.hookMethodName, - hook.hookMethodDescriptor, - false - ) - // Stack layout: ... - // Push the return value on the stack after the last AFTER hook if the original method returns a value - if (index == matchingHooks.size - 1 && returnTypeDescriptor != "V") { - // Push the return value again - mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref - // Unwrap it, if it was a primitive value - unwrapTypeIfPrimitive(returnTypeDescriptor) - // Stack layout: ... | return value (primitive/objectref) - } - } - } - } - } - - 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<Hook> { - 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 || - (replaceHookCount == 1 && result.size == 1) - ) { - "For a given method, You can either have a single REPLACE hook or BEFORE/AFTER hooks. Found:\n $result" - } - - return result - .filter { !isReplaceHookInJava6mode(it) } - .sortedByDescending { it.toString() } - } - - private fun isReplaceHookInJava6mode(hook: Hook): Boolean { - if (java6Mode && hook.hookType == HookType.REPLACE) { - if (showUnsupportedHookWarning.getAndSet(false)) { - println( - """WARN: Some hooks could not be applied to class files built for Java 7 or lower. - |WARN: Ensure that the fuzz target and its dependencies are compiled with - |WARN: -target 8 or higher to identify as many bugs as possible. - """.trimMargin() - ) - } - return true - } - return false - } - - // Stores all arguments for a method call in a local object array. - // paramDescriptors: The type descriptors for all method arguments - private fun storeMethodArguments(paramDescriptors: List<String>): Int { - // Allocate a new Object[] for the methods parameters. - mv.visitIntInsn(Opcodes.SIPUSH, paramDescriptors.size) - mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object") - val localObjArr = lvs.newLocal(Type.getType("[Ljava/lang/Object;")) - mv.visitVarInsn(Opcodes.ASTORE, localObjArr) - - // Loop over all arguments in reverse order (because the last argument is on top). - for ((argIdx, argDescriptor) in paramDescriptors.withIndex().reversed()) { - // If the argument is a primitive type, wrap it in it's wrapper class - wrapTypeIfPrimitive(argDescriptor) - // Store the argument in our object array, for that we need to shape the stack first. - // Stack layout: ... | method argument (objectref) - mv.visitVarInsn(Opcodes.ALOAD, localObjArr) - // Stack layout: ... | method argument (objectref) | object array (arrayref) - mv.visitInsn(Opcodes.SWAP) - // Stack layout: ... | object array (arrayref) | method argument (objectref) - mv.visitIntInsn(Opcodes.SIPUSH, argIdx) - // Stack layout: ... | object array (arrayref) | method argument (objectref) | argument index (int) - mv.visitInsn(Opcodes.SWAP) - // Stack layout: ... | object array (arrayref) | argument index (int) | method argument (objectref) - mv.visitInsn(Opcodes.AASTORE) // consume all three: arrayref, index, value - // Stack layout: ... - // Continue with the remaining method arguments - } - - // Return a reference to the array with the parameters. - return localObjArr - } - - // 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<String>, localObjArr: Int) { - // Loop over all arguments - for ((argIdx, argDescriptor) in paramDescriptors.withIndex()) { - // Push a reference to the object array on the stack - mv.visitVarInsn(Opcodes.ALOAD, localObjArr) - // Stack layout: ... | object array (arrayref) - // Push the index of the current argument on the stack - mv.visitIntInsn(Opcodes.SIPUSH, argIdx) - // Stack layout: ... | object array (arrayref) | argument index (int) - // Load the argument from the array - mv.visitInsn(Opcodes.AALOAD) - // Stack layout: ... | method argument (objectref) - // Cast object to it's original type (or it's wrapper object) - val wrapperTypeDescriptor = getWrapperTypeDescriptor(argDescriptor) - mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(wrapperTypeDescriptor)) - // If the argument is a supposed to be a primitive type, unwrap the wrapped type - unwrapTypeIfPrimitive(argDescriptor) - // Stack layout: ... | method argument (primitive/objectref) - // Continue with the remaining method arguments - } - } - - // Removes a primitive value from the top of the operand stack - // and pushes it enclosed in its wrapper type (e.g. removes int, pushes Integer). - // This is done by calling .valueOf(...) on the wrapper class. - private fun wrapTypeIfPrimitive(unwrappedTypeDescriptor: String) { - if (!isPrimitiveType(unwrappedTypeDescriptor) || unwrappedTypeDescriptor == "V") return - val wrapperTypeDescriptor = getWrapperTypeDescriptor(unwrappedTypeDescriptor) - val wrapperType = extractInternalClassName(wrapperTypeDescriptor) - val valueOfDescriptor = "($unwrappedTypeDescriptor)$wrapperTypeDescriptor" - mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapperType, "valueOf", valueOfDescriptor, false) - } - - // Removes a wrapper object around a given primitive type from the top of the operand stack - // 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 - } - mv.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - wrappedTypeDescriptor, - methodName, - "()$primitiveTypeDescriptor", - false - ) - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt deleted file mode 100644 index 66a21ee7..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt +++ /dev/null @@ -1,114 +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.instrumentor - -import com.code_intelligence.jazzer.api.MethodHook -import com.code_intelligence.jazzer.api.MethodHooks -import com.code_intelligence.jazzer.utils.ClassNameGlobber -import com.code_intelligence.jazzer.utils.descriptor -import io.github.classgraph.ClassGraph -import io.github.classgraph.ScanResult -import java.lang.reflect.Method - -data class Hooks( - val hooks: List<Hook>, - val hookClasses: Set<Class<*>>, - val additionalHookClassNameGlobber: ClassNameGlobber -) { - - companion object { - fun loadHooks(vararg hookClassNames: Set<String>): List<Hooks> { - return ClassGraph() - .enableClassInfo() - .enableSystemJarsAndModules() - .rejectPackages("jaz.*", "com.code_intelligence.jazzer.*") - .scan() - .use { scanResult -> - // Capture scanResult in HooksLoader field to not pass it through - // all internal hook loading methods. - val loader = HooksLoader(scanResult) - hookClassNames.map(loader::load) - } - } - - private class HooksLoader(private val scanResult: ScanResult) { - fun load(hookClassNames: Set<String>): 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), - emptyList() - ) - return Hooks(hooks, hookClasses, additionalHookClassNameGlobber) - } - - private fun loadHooks(hookClassName: String): List<Pair<Hook, Class<*>>> { - return try { - // Custom hook classes outside the agent jar can not be found by bootstrap - // class loader, so use the system class loader as that will be the main application - // class loader and can access jars on the classpath. - // 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, ClassLoader.getSystemClassLoader()) - loadHooks(hookClass).also { - println("INFO: Loaded ${it.size} hooks from $hookClassName") - }.map { - it to hookClass - } - } catch (e: ClassNotFoundException) { - println("WARN: Failed to load hooks from $hookClassName: ${e.printStackTrace()}") - emptyList() - } - } - - private fun loadHooks(hookClass: Class<*>): List<Hook> { - val hooks = mutableListOf<Hook>() - for (method in hookClass.methods.sortedBy { it.descriptor }) { - method.getAnnotation(MethodHook::class.java)?.let { - hooks.addAll(verifyAndGetHooks(method, it)) - } - method.getAnnotation(MethodHooks::class.java)?.let { - it.value.forEach { hookAnnotation -> - hooks.addAll(verifyAndGetHooks(method, hookAnnotation)) - } - } - } - return hooks - } - - private fun verifyAndGetHooks(hookMethod: Method, hookData: MethodHook): List<Hook> { - return lookupClassesToHook(hookData.targetClassName) - .map { className -> - Hook.createAndVerifyHook(hookMethod, hookData, className) - } - } - - private fun lookupClassesToHook(annotationTargetClassName: String): List<String> { - // 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() - } - return (listOf(targetClassName) + additionalTargetClasses.map { it.name }).sorted() - } - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt deleted file mode 100644 index 78793842..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt +++ /dev/null @@ -1,45 +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.instrumentor - -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.MethodNode - -enum class InstrumentationType { - CMP, - COV, - DIV, - GEP, - INDIR, - NATIVE, -} - -internal interface Instrumentor { - fun instrument(bytecode: ByteArray): ByteArray - - fun shouldInstrument(access: Int): Boolean { - return (access and Opcodes.ACC_ABSTRACT == 0) && - (access and Opcodes.ACC_NATIVE == 0) - } - - fun shouldInstrument(method: MethodNode): Boolean { - return shouldInstrument(method.access) && - method.instructions.size() > 0 - } - - companion object { - const val ASM_API_VERSION = Opcodes.ASM9 - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java deleted file mode 100644 index 0512ec2a..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 Code Intelligence GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.code_intelligence.jazzer.instrumentor; - -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.InstrSupport; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -public class StaticMethodStrategy implements EdgeCoverageStrategy { - @Override - public void instrumentControlFlowEdge( - MethodVisitor mv, int edgeId, int variable, String coverageMapInternalClassName) { - InstrSupport.push(mv, edgeId); - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, coverageMapInternalClassName, "recordCoverage", "(I)V", false); - } - - @Override - public int getInstrumentControlFlowEdgeStackSize() { - return 1; - } - - @Override - public Object getLocalVariableType() { - return null; - } - - @Override - public void loadLocalVariable( - MethodVisitor mv, int variable, String coverageMapInternalClassName) {} - - @Override - public int getLoadLocalVariableStackSize() { - return 0; - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt deleted file mode 100644 index 65f11e52..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt +++ /dev/null @@ -1,258 +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.instrumentor - -import com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.AbstractInsnNode -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.InsnList -import org.objectweb.asm.tree.InsnNode -import org.objectweb.asm.tree.IntInsnNode -import org.objectweb.asm.tree.LdcInsnNode -import org.objectweb.asm.tree.LookupSwitchInsnNode -import org.objectweb.asm.tree.MethodInsnNode -import org.objectweb.asm.tree.MethodNode -import org.objectweb.asm.tree.TableSwitchInsnNode - -internal class TraceDataFlowInstrumentor(private val types: Set<InstrumentationType>, callbackClass: Class<*> = TraceDataFlowNativeCallbacks::class.java) : Instrumentor { - - private val callbackInternalClassName = callbackClass.name.replace('.', '/') - private lateinit var random: DeterministicRandom - - override fun instrument(bytecode: ByteArray): ByteArray { - val node = ClassNode() - val reader = ClassReader(bytecode) - reader.accept(node, 0) - random = DeterministicRandom("trace", node.name) - for (method in node.methods) { - if (shouldInstrument(method)) { - addDataFlowInstrumentation(method) - } - } - - val writer = ClassWriter(ClassWriter.COMPUTE_MAXS) - node.accept(writer) - return writer.toByteArray() - } - - @OptIn(ExperimentalUnsignedTypes::class) - private fun addDataFlowInstrumentation(method: MethodNode) { - loop@ for (inst in method.instructions.toArray()) { - when (inst.opcode) { - Opcodes.LCMP -> { - if (InstrumentationType.CMP !in types) continue@loop - method.instructions.insertBefore(inst, longCmpInstrumentation()) - method.instructions.remove(inst) - } - Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE, - Opcodes.IF_ICMPLT, Opcodes.IF_ICMPLE, - Opcodes.IF_ICMPGT, Opcodes.IF_ICMPGE -> { - if (InstrumentationType.CMP !in types) continue@loop - method.instructions.insertBefore(inst, intCmpInstrumentation()) - } - Opcodes.IFEQ, Opcodes.IFNE, - Opcodes.IFLT, Opcodes.IFLE, - Opcodes.IFGT, Opcodes.IFGE -> { - if (InstrumentationType.CMP !in types) continue@loop - // The IF* opcodes are often used to branch based on the result of a compare - // instruction for a type other than int. The operands of this compare will - // already be reported via the instrumentation above (for non-floating point - // numbers) and the follow-up compare does not provide a good signal as all - // operands will be in {-1, 0, 1}. Skip instrumentation for it. - if (inst.previous?.opcode in listOf(Opcodes.DCMPG, Opcodes.DCMPL, Opcodes.FCMPG, Opcodes.DCMPL) || - (inst.previous as? MethodInsnNode)?.name == "traceCmpLongWrapper" - ) - continue@loop - method.instructions.insertBefore(inst, ifInstrumentation()) - } - Opcodes.LOOKUPSWITCH, Opcodes.TABLESWITCH -> { - if (InstrumentationType.CMP !in types) continue@loop - // Mimic the exclusion logic for small label values in libFuzzer: - // 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 (inst.keys.isEmpty() || (0 <= inst.keys.first() && inst.keys.last() < 256)) - continue@loop - inst.keys - } - 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() - } - // Not reached. - else -> continue@loop - }.sortedBy { it.toUInt() }.map { it.toLong() }.toLongArray() - method.instructions.insertBefore(inst, switchInstrumentation(caseValues)) - } - Opcodes.IDIV -> { - if (InstrumentationType.DIV !in types) continue@loop - method.instructions.insertBefore(inst, intDivInstrumentation()) - } - Opcodes.LDIV -> { - if (InstrumentationType.DIV !in types) continue@loop - method.instructions.insertBefore(inst, longDivInstrumentation()) - } - Opcodes.AALOAD, Opcodes.BALOAD, - Opcodes.CALOAD, Opcodes.DALOAD, - Opcodes.FALOAD, Opcodes.IALOAD, - Opcodes.LALOAD, Opcodes.SALOAD -> { - if (InstrumentationType.GEP !in types) continue@loop - if (!isConstantIntegerPushInsn(inst.previous)) continue@loop - method.instructions.insertBefore(inst, gepLoadInstrumentation()) - } - Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL, Opcodes.INVOKESTATIC, Opcodes.INVOKEVIRTUAL -> { - if (InstrumentationType.GEP !in types) continue@loop - if (!isGepLoadMethodInsn(inst as MethodInsnNode)) continue@loop - if (!isConstantIntegerPushInsn(inst.previous)) continue@loop - method.instructions.insertBefore(inst, gepLoadInstrumentation()) - } - } - } - } - - private fun InsnList.pushFakePc() { - 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 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 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 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()) { - 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)) - } - - /** - * Returns true if [node] represents an instruction that possibly pushes a valid, non-zero, constant array index - * onto the stack. - */ - private fun isConstantIntegerPushInsn(node: AbstractInsnNode?) = node?.opcode in CONSTANT_INTEGER_PUSH_OPCODES - - /** - * Returns true if [node] represents a call to a method that performs an indexed lookup into an array-like - * structure. - */ - private fun isGepLoadMethodInsn(node: MethodInsnNode): Boolean { - if (!node.desc.startsWith("(I)")) return false - val returnType = node.desc.removePrefix("(I)") - 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)) - } - - 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 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/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel deleted file mode 100644 index 08bd7653..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel +++ /dev/null @@ -1,17 +0,0 @@ -load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") - -java_jni_library( - name = "replay", - srcs = ["Replayer.java"], - native_libs = ["//driver/src/main/native/com/code_intelligence/jazzer/driver:fuzzed_data_provider_standalone"], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", - ], -) - -java_binary( - name = "Replayer", - visibility = ["//visibility:public"], - runtime_deps = [":replay"], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java deleted file mode 100644 index 0a250d1a..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java +++ /dev/null @@ -1,156 +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.replay; - -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl; -import com.github.fmeum.rules_jni.RulesJni; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; - -public class Replayer { - public static final int STATUS_FINDING = 77; - public static final int STATUS_OTHER_ERROR = 1; - - static { - System.setProperty("jazzer.is_replayer", "true"); - try { - RulesJni.loadLibrary( - "fuzzed_data_provider_standalone", "/com/code_intelligence/jazzer/driver"); - } catch (Throwable t) { - t.printStackTrace(); - System.exit(STATUS_OTHER_ERROR); - } - } - - public static void main(String[] args) { - if (args.length < 2) { - System.err.println("Usage: <fuzz target class> <input file path> <fuzzerInitialize args>..."); - System.exit(STATUS_OTHER_ERROR); - } - ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); - - Class<?> fuzzTargetClass; - try { - fuzzTargetClass = Class.forName(args[0]); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - System.exit(STATUS_OTHER_ERROR); - // Without this return the compiler sees fuzzTargetClass as possibly uninitialized. - return; - } - - String inputFilePath = args[1]; - byte[] input = loadInput(inputFilePath); - - String[] fuzzTargetArgs = Arrays.copyOfRange(args, 2, args.length); - executeFuzzerInitialize(fuzzTargetClass, fuzzTargetArgs); - executeFuzzTarget(fuzzTargetClass, input); - } - - private static byte[] loadInput(String inputFilePath) { - try { - return Files.readAllBytes(Paths.get(inputFilePath)); - } catch (IOException e) { - e.printStackTrace(); - System.exit(STATUS_OTHER_ERROR); - // Without this return the compiler sees loadInput as possibly not returning a value. - return null; - } - } - - private static void executeFuzzerInitialize(Class<?> fuzzTarget, String[] args) { - // public static void fuzzerInitialize() - try { - Method fuzzerInitialize = fuzzTarget.getMethod("fuzzerInitialize"); - fuzzerInitialize.invoke(null); - return; - } catch (Exception e) { - handleInvokeException(e, fuzzTarget); - } - // public static void fuzzerInitialize(String[] args) - try { - Method fuzzerInitialize = fuzzTarget.getMethod("fuzzerInitialize", String[].class); - fuzzerInitialize.invoke(null, (Object) args); - } catch (Exception e) { - handleInvokeException(e, fuzzTarget); - } - } - - public static void executeFuzzTarget(Class<?> fuzzTarget, byte[] input) { - // public static void fuzzerTestOneInput(byte[] input) - try { - Method fuzzerTestOneInput = fuzzTarget.getMethod("fuzzerTestOneInput", byte[].class); - fuzzerTestOneInput.invoke(null, (Object) input); - return; - } catch (Exception e) { - handleInvokeException(e, fuzzTarget); - } - // public static void fuzzerTestOneInput(FuzzedDataProvider data) - try { - Method fuzzerTestOneInput = - fuzzTarget.getMethod("fuzzerTestOneInput", FuzzedDataProvider.class); - try (FuzzedDataProviderImpl fuzzedDataProvider = FuzzedDataProviderImpl.withJavaData(input)) { - fuzzerTestOneInput.invoke(null, fuzzedDataProvider); - } - return; - } catch (Exception e) { - handleInvokeException(e, fuzzTarget); - } - System.err.printf("%s must define exactly one of the following two functions:%n" - + " public static void fuzzerTestOneInput(byte[] ...)%n" - + " public static void fuzzerTestOneInput(FuzzedDataProvider ...)%n" - + "Note: Fuzz targets returning boolean are no longer supported; exceptions should%n" - + "be thrown instead of returning true.%n", - fuzzTarget.getName()); - System.exit(STATUS_OTHER_ERROR); - } - - private static void handleInvokeException(Exception e, Class<?> fuzzTarget) { - if (e instanceof NoSuchMethodException) - return; - if (e instanceof InvocationTargetException) { - filterOutOwnStackTraceElements(e.getCause(), fuzzTarget); - e.getCause().printStackTrace(); - System.exit(STATUS_FINDING); - } else { - e.printStackTrace(); - System.exit(STATUS_OTHER_ERROR); - } - } - - private static void filterOutOwnStackTraceElements(Throwable t, Class<?> fuzzTarget) { - if (t.getCause() != null) - filterOutOwnStackTraceElements(t.getCause(), fuzzTarget); - if (t.getStackTrace() == null || t.getStackTrace().length == 0) - return; - StackTraceElement lowestFrame = t.getStackTrace()[t.getStackTrace().length - 1]; - if (!lowestFrame.getClassName().equals(Replayer.class.getName()) - || !lowestFrame.getMethodName().equals("main")) - return; - for (int i = t.getStackTrace().length - 1; i >= 0; i--) { - StackTraceElement frame = t.getStackTrace()[i]; - if (frame.getClassName().equals(fuzzTarget.getName()) - && frame.getMethodName().equals("fuzzerTestOneInput")) { - t.setStackTrace(Arrays.copyOfRange(t.getStackTrace(), 0, i + 1)); - break; - } - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel deleted file mode 100644 index 0d8162d5..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ /dev/null @@ -1,88 +0,0 @@ -load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") - -java_jni_library( - name = "fuzzed_data_provider", - srcs = [ - "FuzzedDataProviderImpl.java", - ], - visibility = [ - "//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__", - "//agent/src/test/java/com/code_intelligence/jazzer/runtime:__pkg__", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - ], - deps = [ - ":unsafe_provider", - "//agent/src/main/java/com/code_intelligence/jazzer/api", - ], -) - -java_jni_library( - name = "coverage_map", - srcs = ["CoverageMap.java"], - visibility = [ - "//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", - "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:__pkg__", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - "//driver/src/test:__subpackages__", - ], - deps = [ - ":unsafe_provider", - ], -) - -java_jni_library( - name = "signal_handler", - srcs = ["SignalHandler.java"], - native_libs = ["//agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_signal_handler"], - visibility = [ - "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", - ], -) - -java_jni_library( - name = "trace_data_flow_native_callbacks", - srcs = ["TraceDataFlowNativeCallbacks.java"], - visibility = [ - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - ], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/utils", - ], -) - -java_library( - name = "unsafe_provider", - srcs = ["UnsafeProvider.java"], - visibility = [ - "//driver/src:__subpackages__", - "//sanitizers/src/main/java:__subpackages__", - ], -) - -java_library( - name = "runtime", - srcs = [ - "HardToCatchError.java", - "JazzerInternal.java", - "NativeLibHooks.java", - "RecordingFuzzedDataProvider.java", - "TraceCmpHooks.java", - "TraceDivHooks.java", - "TraceIndirHooks.java", - ], - visibility = ["//visibility:public"], - runtime_deps = [ - ":signal_handler", - "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", - ], - deps = [ - ":coverage_map", - ":fuzzed_data_provider", - ":trace_data_flow_native_callbacks", - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/utils", - ], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java deleted file mode 100644 index 4069d25a..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ /dev/null @@ -1,129 +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.runtime; - -import com.github.fmeum.rules_jni.RulesJni; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import sun.misc.Unsafe; - -/** - * Represents the Java view on a libFuzzer 8 bit counter coverage map. By using a direct ByteBuffer, - * the counters are shared directly with native code. - */ -final public class CoverageMap { - static { - RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver"); - } - - private static final String ENV_MAX_NUM_COUNTERS = "JAZZER_MAX_NUM_COUNTERS"; - - private static final int MAX_NUM_COUNTERS = System.getenv(ENV_MAX_NUM_COUNTERS) != null - ? Integer.parseInt(System.getenv(ENV_MAX_NUM_COUNTERS)) - : 1 << 20; - - private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); - - static { - if (UNSAFE == null) { - System.out.println("ERROR: Failed to get Unsafe instance for CoverageMap.%n" - + " Please file a bug at:%n" - + " https://github.com/CodeIntelligenceTesting/jazzer/issues/new"); - System.exit(1); - } - } - - /** - * The collection of coverage counters directly interacted with by classes that are instrumented - * for coverage. The instrumentation assumes that this is always one contiguous block of memory, - * so it is allocated once at maximum size. Using a larger number here increases the memory usage - * of all fuzz targets, but has otherwise no impact on performance. - */ - public static final long countersAddress = UNSAFE.allocateMemory(MAX_NUM_COUNTERS); - - static { - UNSAFE.setMemory(countersAddress, MAX_NUM_COUNTERS, (byte) 0); - initialize(countersAddress); - } - - private static final int INITIAL_NUM_COUNTERS = 1 << 9; - - static { - registerNewCounters(0, INITIAL_NUM_COUNTERS); - } - - /** - * The number of coverage counters that are currently registered with libFuzzer. This number grows - * dynamically as classes are instrumented and should be kept as low as possible as libFuzzer has - * to iterate over the whole map for every execution. - */ - private static int currentNumCounters = INITIAL_NUM_COUNTERS; - - // Called via reflection. - @SuppressWarnings("unused") - public static void enlargeIfNeeded(int nextId) { - int newNumCounters = currentNumCounters; - while (nextId >= newNumCounters) { - newNumCounters = 2 * newNumCounters; - if (newNumCounters > MAX_NUM_COUNTERS) { - System.out.printf("ERROR: Maximum number (%s) of coverage counters exceeded. Try to%n" - + " limit the scope of a single fuzz target as much as possible to keep the%n" - + " fuzzer fast.%n" - + " If that is not possible, the maximum number of counters can be increased%n" - + " via the %s environment variable.", - MAX_NUM_COUNTERS, ENV_MAX_NUM_COUNTERS); - System.exit(1); - } - } - if (newNumCounters > currentNumCounters) { - registerNewCounters(currentNumCounters, newNumCounters); - currentNumCounters = newNumCounters; - System.out.println("INFO: New number of coverage counters: " + currentNumCounters); - } - } - - // Called by the coverage instrumentation. - @SuppressWarnings("unused") - public static void recordCoverage(final int id) { - final long address = countersAddress + id; - final byte counter = UNSAFE.getByte(address); - UNSAFE.putByte(address, (byte) (counter == -1 ? 1 : counter + 1)); - } - - public static Set<Integer> getCoveredIds() { - Set<Integer> coveredIds = new HashSet<>(); - for (int id = 0; id < currentNumCounters; id++) { - if (UNSAFE.getByte(countersAddress + id) > 0) { - coveredIds.add(id); - } - } - return Collections.unmodifiableSet(coveredIds); - } - - public static void replayCoveredIds(Set<Integer> coveredIds) { - for (int id : coveredIds) { - UNSAFE.putByte(countersAddress + id, (byte) 1); - } - } - - // Returns the IDs of all blocks that have been covered in at least one run (not just the current - // one). - public static native int[] getEverCoveredIds(); - - private static native void initialize(long countersAddress); - - private static native void registerNewCounters(int oldNumCounters, int newNumCounters); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java deleted file mode 100644 index b7aad33e..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java +++ /dev/null @@ -1,250 +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.runtime; - -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import com.github.fmeum.rules_jni.RulesJni; -import sun.misc.Unsafe; - -public class FuzzedDataProviderImpl implements FuzzedDataProvider, AutoCloseable { - static { - // The replayer loads a standalone version of the FuzzedDataProvider. - if (System.getProperty("jazzer.is_replayer") == null) { - RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver"); - } - nativeInit(); - } - - private static native void nativeInit(); - - private final boolean ownsNativeData; - private long originalDataPtr; - private int originalRemainingBytes; - - // Accessed in fuzzed_data_provider.cpp. - private long dataPtr; - private int remainingBytes; - - private FuzzedDataProviderImpl(long dataPtr, int remainingBytes, boolean ownsNativeData) { - this.ownsNativeData = ownsNativeData; - this.originalDataPtr = dataPtr; - this.dataPtr = dataPtr; - this.originalRemainingBytes = remainingBytes; - this.remainingBytes = remainingBytes; - } - - /** - * Creates a {@link FuzzedDataProvider} that consumes bytes from an already existing native array. - * - * <ul> - * <li>{@link #close()} <b>must</b> be called on instances created with this method to free the - * native copy of the Java - * {@code byte} array. - * <li>{@link #setNativeData(long, int)} <b>must not</b> be called on instances created with this - * method. - * - * @param data the raw bytes used as input - * @return a {@link FuzzedDataProvider} backed by {@code data} - */ - public static FuzzedDataProviderImpl withJavaData(byte[] data) { - return new FuzzedDataProviderImpl(allocateNativeCopy(data), data.length, true); - } - - /** - * Creates a {@link FuzzedDataProvider} that consumes bytes from an already existing native array. - * - * <p>The backing array can be set at any time using {@link #setNativeData(long, int)} and is - * initially empty. - * - * @return a {@link FuzzedDataProvider} backed by an empty array. - */ - public static FuzzedDataProviderImpl withNativeData() { - return new FuzzedDataProviderImpl(0, 0, false); - } - - /** - * Replaces the current native backing array. - * - * <p><b>Must not</b> be called on instances created with {@link #withJavaData(byte[])}. - * - * @param dataPtr a native pointer to the new backing array - * @param dataLength the length of the new backing array - */ - public void setNativeData(long dataPtr, int dataLength) { - this.originalDataPtr = dataPtr; - this.dataPtr = dataPtr; - this.originalRemainingBytes = dataLength; - this.remainingBytes = dataLength; - } - - /** - * Resets the FuzzedDataProvider state to read from the beginning to the end of its current - * backing item. - */ - public void reset() { - dataPtr = originalDataPtr; - remainingBytes = originalRemainingBytes; - } - - /** - * Releases native memory allocated for this instance (if any). - * - * <p>While the instance should not be used after this method returns, no usage of {@link - * FuzzedDataProvider} methods can result in memory corruption. - */ - @Override - public void close() { - if (originalDataPtr == 0) { - return; - } - if (ownsNativeData) { - UNSAFE.freeMemory(originalDataPtr); - } - // Prevent double-frees and use-after-frees by effectively making all methods no-ops after - // close() has been called. - originalDataPtr = 0; - originalRemainingBytes = 0; - dataPtr = 0; - remainingBytes = 0; - } - - private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); - private static final long BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); - - private static long allocateNativeCopy(byte[] data) { - long nativeCopy = UNSAFE.allocateMemory(data.length); - UNSAFE.copyMemory(data, BYTE_ARRAY_OFFSET, null, nativeCopy, data.length); - return nativeCopy; - } - - @Override public native boolean consumeBoolean(); - - @Override public native boolean[] consumeBooleans(int maxLength); - - @Override public native byte consumeByte(); - - @Override - public byte consumeByte(byte min, byte max) { - if (min > max) { - throw new IllegalArgumentException( - String.format("min must be <= max (got min: %d, max: %d)", min, max)); - } - return consumeByteUnchecked(min, max); - } - - @Override public native short consumeShort(); - - @Override - public short consumeShort(short min, short max) { - if (min > max) { - throw new IllegalArgumentException( - String.format("min must be <= max (got min: %d, max: %d)", min, max)); - } - return consumeShortUnchecked(min, max); - } - - @Override public native short[] consumeShorts(int maxLength); - - @Override public native int consumeInt(); - - @Override - public int consumeInt(int min, int max) { - if (min > max) { - throw new IllegalArgumentException( - String.format("min must be <= max (got min: %d, max: %d)", min, max)); - } - return consumeIntUnchecked(min, max); - } - - @Override public native int[] consumeInts(int maxLength); - - @Override public native long consumeLong(); - - @Override - public long consumeLong(long min, long max) { - if (min > max) { - throw new IllegalArgumentException( - String.format("min must be <= max (got min: %d, max: %d)", min, max)); - } - return consumeLongUnchecked(min, max); - } - - @Override public native long[] consumeLongs(int maxLength); - - @Override public native float consumeFloat(); - - @Override public native float consumeRegularFloat(); - - @Override - public float consumeRegularFloat(float min, float max) { - if (min > max) { - throw new IllegalArgumentException( - String.format("min must be <= max (got min: %f, max: %f)", min, max)); - } - return consumeRegularFloatUnchecked(min, max); - } - - @Override public native float consumeProbabilityFloat(); - - @Override public native double consumeDouble(); - - @Override - public double consumeRegularDouble(double min, double max) { - if (min > max) { - throw new IllegalArgumentException( - String.format("min must be <= max (got min: %f, max: %f)", min, max)); - } - return consumeRegularDoubleUnchecked(min, max); - } - - @Override public native double consumeRegularDouble(); - - @Override public native double consumeProbabilityDouble(); - - @Override public native char consumeChar(); - - @Override - public char consumeChar(char min, char max) { - if (min > max) { - throw new IllegalArgumentException( - String.format("min must be <= max (got min: %c, max: %c)", min, max)); - } - return consumeCharUnchecked(min, max); - } - - @Override public native char consumeCharNoSurrogates(); - - @Override public native String consumeAsciiString(int maxLength); - - @Override public native String consumeString(int maxLength); - - @Override public native String consumeRemainingAsAsciiString(); - - @Override public native String consumeRemainingAsString(); - - @Override public native byte[] consumeBytes(int maxLength); - - @Override public native byte[] consumeRemainingAsBytes(); - - @Override public native int remainingBytes(); - - private native byte consumeByteUnchecked(byte min, byte max); - private native short consumeShortUnchecked(short min, short max); - private native char consumeCharUnchecked(char min, char max); - private native int consumeIntUnchecked(int min, int max); - private native long consumeLongUnchecked(long min, long max); - private native float consumeRegularFloatUnchecked(float min, float max); - private native double consumeRegularDoubleUnchecked(double min, double max); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java deleted file mode 100644 index cf136051..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java +++ /dev/null @@ -1,82 +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.runtime; - -import java.io.PrintStream; -import java.io.PrintWriter; - -/** - * An Error that rethrows itself when any of its getters is invoked. - */ -public class HardToCatchError extends Error { - public HardToCatchError() { - super(); - } - - @Override - public String getMessage() { - throw this; - } - - @Override - public String getLocalizedMessage() { - throw this; - } - - @Override - public synchronized Throwable initCause(Throwable cause) { - throw this; - } - - @Override - public String toString() { - throw this; - } - - @Override - public void printStackTrace() { - throw this; - } - - @Override - public void printStackTrace(PrintStream s) { - throw this; - } - - @Override - public void printStackTrace(PrintWriter s) { - throw this; - } - - @Override - public StackTraceElement[] getStackTrace() { - throw this; - } - - @Override - public int hashCode() { - throw this; - } - - @Override - public boolean equals(Object obj) { - throw this; - } - - @Override - public Object clone() { - throw this; - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java deleted file mode 100644 index 79c851ad..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java +++ /dev/null @@ -1,41 +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.runtime; - -import java.util.ArrayList; - -final public class JazzerInternal { - private static final ArrayList<Runnable> ON_FUZZ_TARGET_READY_CALLBACKS = new ArrayList<>(); - - public static Throwable lastFinding; - - // Accessed from api.Jazzer via reflection. - public static void reportFindingFromHook(Throwable finding) { - lastFinding = finding; - // Throw an Error that is hard to catch (short of outright ignoring it) in order to quickly - // terminate the execution of the fuzz target. The finding will be reported as soon as the fuzz - // target returns even if this Error is swallowed. - throw new HardToCatchError(); - } - - public static void registerOnFuzzTargetReadyCallback(Runnable callback) { - ON_FUZZ_TARGET_READY_CALLBACKS.add(callback); - } - - public static void onFuzzTargetReady(String fuzzTargetClass) { - ON_FUZZ_TARGET_READY_CALLBACKS.forEach(Runnable::run); - ON_FUZZ_TARGET_READY_CALLBACKS.clear(); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java deleted file mode 100644 index 495cad7c..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java +++ /dev/null @@ -1,35 +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.runtime; - -import com.code_intelligence.jazzer.api.HookType; -import com.code_intelligence.jazzer.api.MethodHook; -import java.lang.invoke.MethodHandle; - -@SuppressWarnings("unused") -final public class NativeLibHooks { - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", - targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", - targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "load", - targetMethodDescriptor = "(Ljava/lang/String;)V") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "load", - targetMethodDescriptor = "(Ljava/lang/String;)V") - public static void - loadLibraryHook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { - TraceDataFlowNativeCallbacks.handleLibraryLoad(); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java deleted file mode 100644 index 4eb80222..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java +++ /dev/null @@ -1,213 +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.runtime; - -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Base64; - -// Wraps the native FuzzedDataProviderImpl and serializes all its return values -// into a Base64-encoded string. -public final class RecordingFuzzedDataProvider implements FuzzedDataProvider { - private final FuzzedDataProvider target; - private final ArrayList<Object> recordedReplies = new ArrayList<>(); - - private RecordingFuzzedDataProvider(FuzzedDataProvider target) { - this.target = target; - } - - public static FuzzedDataProvider makeFuzzedDataProviderProxy(FuzzedDataProvider target) { - return new RecordingFuzzedDataProvider(target); - } - - public static String serializeFuzzedDataProviderProxy(FuzzedDataProvider proxy) - throws IOException { - return ((RecordingFuzzedDataProvider) proxy).serialize(); - } - - private <T> T recordAndReturn(T object) { - recordedReplies.add(object); - return object; - } - - private String serialize() throws IOException { - byte[] rawOut; - try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) { - try (ObjectOutputStream objectStream = new ObjectOutputStream(byteStream)) { - objectStream.writeObject(recordedReplies); - } - rawOut = byteStream.toByteArray(); - } - return Base64.getEncoder().encodeToString(rawOut); - } - - @Override - public boolean consumeBoolean() { - return recordAndReturn(target.consumeBoolean()); - } - - @Override - public boolean[] consumeBooleans(int maxLength) { - return recordAndReturn(target.consumeBooleans(maxLength)); - } - - @Override - public byte consumeByte() { - return recordAndReturn(target.consumeByte()); - } - - @Override - public byte consumeByte(byte min, byte max) { - return recordAndReturn(target.consumeByte(min, max)); - } - - @Override - public byte[] consumeBytes(int maxLength) { - return recordAndReturn(target.consumeBytes(maxLength)); - } - - @Override - public byte[] consumeRemainingAsBytes() { - return recordAndReturn(target.consumeRemainingAsBytes()); - } - - @Override - public short consumeShort() { - return recordAndReturn(target.consumeShort()); - } - - @Override - public short consumeShort(short min, short max) { - return recordAndReturn(target.consumeShort(min, max)); - } - - @Override - public short[] consumeShorts(int maxLength) { - return recordAndReturn(target.consumeShorts(maxLength)); - } - - @Override - public int consumeInt() { - return recordAndReturn(target.consumeInt()); - } - - @Override - public int consumeInt(int min, int max) { - return recordAndReturn(target.consumeInt(min, max)); - } - - @Override - public int[] consumeInts(int maxLength) { - return recordAndReturn(target.consumeInts(maxLength)); - } - - @Override - public long consumeLong() { - return recordAndReturn(target.consumeLong()); - } - - @Override - public long consumeLong(long min, long max) { - return recordAndReturn(target.consumeLong(min, max)); - } - - @Override - public long[] consumeLongs(int maxLength) { - return recordAndReturn(target.consumeLongs(maxLength)); - } - - @Override - public float consumeFloat() { - return recordAndReturn(target.consumeFloat()); - } - - @Override - public float consumeRegularFloat() { - return recordAndReturn(target.consumeRegularFloat()); - } - - @Override - public float consumeRegularFloat(float min, float max) { - return recordAndReturn(target.consumeRegularFloat(min, max)); - } - - @Override - public float consumeProbabilityFloat() { - return recordAndReturn(target.consumeProbabilityFloat()); - } - - @Override - public double consumeDouble() { - return recordAndReturn(target.consumeDouble()); - } - - @Override - public double consumeRegularDouble() { - return recordAndReturn(target.consumeRegularDouble()); - } - - @Override - public double consumeRegularDouble(double min, double max) { - return recordAndReturn(target.consumeRegularDouble(min, max)); - } - - @Override - public double consumeProbabilityDouble() { - return recordAndReturn(target.consumeProbabilityDouble()); - } - - @Override - public char consumeChar() { - return recordAndReturn(target.consumeChar()); - } - - @Override - public char consumeChar(char min, char max) { - return recordAndReturn(target.consumeChar(min, max)); - } - - @Override - public char consumeCharNoSurrogates() { - return recordAndReturn(target.consumeCharNoSurrogates()); - } - - @Override - public String consumeString(int maxLength) { - return recordAndReturn(target.consumeString(maxLength)); - } - - @Override - public String consumeRemainingAsString() { - return recordAndReturn(target.consumeRemainingAsString()); - } - - @Override - public String consumeAsciiString(int maxLength) { - return recordAndReturn(target.consumeAsciiString(maxLength)); - } - - @Override - public String consumeRemainingAsAsciiString() { - return recordAndReturn(target.consumeRemainingAsAsciiString()); - } - - @Override - public int remainingBytes() { - return recordAndReturn(target.remainingBytes()); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java deleted file mode 100644 index 49ee80c8..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java +++ /dev/null @@ -1,31 +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.runtime; - -import com.github.fmeum.rules_jni.RulesJni; -import sun.misc.Signal; - -public final class SignalHandler { - static { - RulesJni.loadLibrary("jazzer_signal_handler", SignalHandler.class); - Signal.handle(new Signal("INT"), sig -> handleInterrupt()); - } - - public static void initialize() { - // Implicitly runs the static initializer. - } - - private static native void handleInterrupt(); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java deleted file mode 100644 index 37e8eaeb..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ /dev/null @@ -1,347 +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.runtime; - -import com.code_intelligence.jazzer.api.HookType; -import com.code_intelligence.jazzer.api.MethodHook; -import java.lang.invoke.MethodHandle; -import java.util.Arrays; -import java.util.ConcurrentModificationException; -import java.util.Map; -import java.util.TreeMap; - -@SuppressWarnings("unused") -final public class TraceCmpHooks { - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte", targetMethod = "compare", - targetMethodDescriptor = "(BB)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte", - targetMethod = "compareUnsigned", targetMethodDescriptor = "(BB)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short", targetMethod = "compare", - targetMethodDescriptor = "(SS)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short", - targetMethod = "compareUnsigned", targetMethodDescriptor = "(SS)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer", - targetMethod = "compare", targetMethodDescriptor = "(II)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer", - targetMethod = "compareUnsigned", targetMethodDescriptor = "(II)I") - public static void - integerCompare(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { - TraceDataFlowNativeCallbacks.traceCmpInt((int) arguments[0], (int) arguments[1], hookId); - } - - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte", - targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Byte;)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short", - targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Short;)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer", - targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Integer;)I") - public static void - integerCompareTo(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { - TraceDataFlowNativeCallbacks.traceCmpInt((int) thisObject, (int) arguments[0], hookId); - } - - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", targetMethod = "compare", - targetMethodDescriptor = "(JJ)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", - targetMethod = "compareUnsigned", targetMethodDescriptor = "(JJ)I") - public static void - longCompare(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { - TraceDataFlowNativeCallbacks.traceCmpLong((long) arguments[0], (long) arguments[1], hookId); - } - - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", - targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Long;)I") - public static void - longCompareTo(MethodHandle method, Long thisObject, Object[] arguments, int hookId) { - TraceDataFlowNativeCallbacks.traceCmpLong(thisObject, (long) arguments[0], hookId); - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", - targetMethod = "equalsIgnoreCase") - public static void - equals( - MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (arguments[0] instanceof String && !returnValue) { - // The precise value of the result of the comparison is not used by libFuzzer as long as it is - // non-zero. - TraceDataFlowNativeCallbacks.traceStrcmp(thisObject, (String) arguments[0], 1, hookId); - } - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Object", targetMethod = "equals") - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.CharSequence", targetMethod = "equals") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Number", targetMethod = "equals") - public static void - genericEquals( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (!returnValue && arguments[0] != null && thisObject.getClass() == arguments[0].getClass()) { - TraceDataFlowNativeCallbacks.traceGenericCmp(thisObject, arguments[0], hookId); - } - } - - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "compareTo") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", - targetMethod = "compareToIgnoreCase") - public static void - compareTo( - MethodHandle method, String thisObject, Object[] arguments, int hookId, Integer returnValue) { - if (arguments[0] instanceof String && returnValue != 0) { - TraceDataFlowNativeCallbacks.traceStrcmp( - thisObject, (String) arguments[0], returnValue, hookId); - } - } - - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contentEquals") - public static void - contentEquals( - MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (arguments[0] instanceof CharSequence && !returnValue) { - TraceDataFlowNativeCallbacks.traceStrcmp( - thisObject, ((CharSequence) arguments[0]).toString(), 1, hookId); - } - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", - targetMethod = "regionMatches", targetMethodDescriptor = "(ZILjava/lang/String;II)Z") - public static void - regionsMatches5( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (!returnValue) { - int toffset = (int) arguments[1]; - String other = (String) arguments[2]; - int ooffset = (int) arguments[3]; - int len = (int) arguments[4]; - regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId); - } - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", - targetMethod = "regionMatches", targetMethodDescriptor = "(ILjava/lang/String;II)Z") - public static void - regionMatches4( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (!returnValue) { - int toffset = (int) arguments[0]; - String other = (String) arguments[1]; - int ooffset = (int) arguments[2]; - int len = (int) arguments[3]; - regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId); - } - } - - private static void regionMatchesInternal( - String thisString, int toffset, String other, int ooffset, int len, int hookId) { - if (toffset < 0 || ooffset < 0) - return; - int cappedThisStringEnd = Math.min(toffset + len, thisString.length()); - int cappedOtherStringEnd = Math.min(ooffset + len, other.length()); - String thisPart = thisString.substring(toffset, cappedThisStringEnd); - String otherPart = other.substring(ooffset, cappedOtherStringEnd); - TraceDataFlowNativeCallbacks.traceStrcmp(thisPart, otherPart, 1, hookId); - } - - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contains") - public static void - contains( - MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (arguments[0] instanceof CharSequence && !returnValue) { - TraceDataFlowNativeCallbacks.traceStrstr( - thisObject, ((CharSequence) arguments[0]).toString(), hookId); - } - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "indexOf") - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "lastIndexOf") - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.StringBuffer", targetMethod = "indexOf") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuffer", - targetMethod = "lastIndexOf") - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", targetMethod = "indexOf") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", - targetMethod = "lastIndexOf") - public static void - indexOf( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) { - if (arguments[0] instanceof String && returnValue == -1) { - TraceDataFlowNativeCallbacks.traceStrstr( - thisObject.toString(), (String) arguments[0], hookId); - } - } - - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "startsWith") - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "endsWith") - public static void - startsWith( - MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (!returnValue) { - TraceDataFlowNativeCallbacks.traceStrstr(thisObject, (String) arguments[0], hookId); - } - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "replace", - targetMethodDescriptor = - "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;") - public static void - replace( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) { - String original = (String) thisObject; - // Report only if the replacement was not successful. - if (original.equals(returnValue)) { - String target = arguments[0].toString(); - TraceDataFlowNativeCallbacks.traceStrstr(original, target, hookId); - } - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals", - targetMethodDescriptor = "([B[B)Z") - public static void - arraysEquals( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (returnValue) - return; - byte[] first = (byte[]) arguments[0]; - byte[] second = (byte[]) arguments[1]; - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId); - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals", - targetMethodDescriptor = "([BII[BII)Z") - public static void - arraysEqualsRange( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (returnValue) - return; - byte[] first = - Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]); - byte[] second = - Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]); - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId); - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare", - targetMethodDescriptor = "([B[B)I") - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", - targetMethod = "compareUnsigned", targetMethodDescriptor = "([B[B)I") - public static void - arraysCompare( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) { - if (returnValue == 0) - return; - byte[] first = (byte[]) arguments[0]; - byte[] second = (byte[]) arguments[1]; - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId); - } - - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare", - targetMethodDescriptor = "([BII[BII)I") - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", - targetMethod = "compareUnsigned", targetMethodDescriptor = "([BII[BII)I") - public static void - arraysCompareRange( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) { - if (returnValue == 0) - return; - byte[] first = - Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]); - byte[] second = - Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]); - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId); - } - - // The maximal number of elements of a non-TreeMap Map that will be sorted and searched for the - // key closest to the current lookup key in the mapGet hook. - private static final int MAX_NUM_KEYS_TO_ENUMERATE = 100; - - @SuppressWarnings({"rawtypes", "unchecked"}) - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Map", targetMethod = "get") - public static void mapGet( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) { - if (returnValue != null) - return; - if (thisObject == null) - return; - final Map map = (Map) thisObject; - if (map.size() == 0) - return; - final Object currentKey = arguments[0]; - if (currentKey == null) - return; - // Find two valid map keys that bracket currentKey. - // This is a generalization of libFuzzer's __sanitizer_cov_trace_switch: - // https://github.com/llvm/llvm-project/blob/318942de229beb3b2587df09e776a50327b5cef0/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L564 - Object lowerBoundKey = null; - Object upperBoundKey = null; - try { - if (map instanceof TreeMap) { - final TreeMap treeMap = (TreeMap) map; - try { - lowerBoundKey = treeMap.floorKey(currentKey); - upperBoundKey = treeMap.ceilingKey(currentKey); - } catch (ClassCastException ignored) { - // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be - // compared to the maps keys. - } - } else if (currentKey instanceof Comparable) { - final Comparable comparableCurrentKey = (Comparable) currentKey; - // Find two keys that bracket currentKey. - // Note: This is not deterministic if map.size() > MAX_NUM_KEYS_TO_ENUMERATE. - int enumeratedKeys = 0; - for (Object validKey : map.keySet()) { - if (!(validKey instanceof Comparable)) - continue; - final Comparable comparableValidKey = (Comparable) validKey; - // If the key sorts lower than the non-existing key, but higher than the current lower - // bound, update the lower bound and vice versa for the upper bound. - try { - if (comparableValidKey.compareTo(comparableCurrentKey) < 0 - && (lowerBoundKey == null || comparableValidKey.compareTo(lowerBoundKey) > 0)) { - lowerBoundKey = validKey; - } - if (comparableValidKey.compareTo(comparableCurrentKey) > 0 - && (upperBoundKey == null || comparableValidKey.compareTo(upperBoundKey) < 0)) { - upperBoundKey = validKey; - } - } catch (ClassCastException ignored) { - // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be - // compared to the maps keys. - } - if (enumeratedKeys++ > MAX_NUM_KEYS_TO_ENUMERATE) - break; - } - } - } catch (ConcurrentModificationException ignored) { - // map was modified by another thread, skip this invocation - return; - } - // Modify the hook ID so that compares against distinct valid keys are traced separately. - if (lowerBoundKey != null) { - TraceDataFlowNativeCallbacks.traceGenericCmp( - currentKey, lowerBoundKey, hookId + lowerBoundKey.hashCode()); - } - if (upperBoundKey != null) { - TraceDataFlowNativeCallbacks.traceGenericCmp( - currentKey, upperBoundKey, hookId + upperBoundKey.hashCode()); - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java deleted file mode 100644 index 821ade0d..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ /dev/null @@ -1,102 +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.runtime; - -import com.code_intelligence.jazzer.utils.Utils; -import com.github.fmeum.rules_jni.RulesJni; -import java.lang.reflect.Executable; -import java.nio.charset.Charset; - -@SuppressWarnings("unused") -final public class TraceDataFlowNativeCallbacks { - static { - RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver"); - } - - // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently - // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is more - // likely to insert literal null bytes, having both the fuzzer input and the reported string - // comparisons be CESU8 should perform even better than the current implementation using modified - // UTF-8. - private static final Charset FUZZED_DATA_CHARSET = Charset.forName("CESU8"); - - public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); - - public static void traceStrcmp(String s1, String s2, int result, int pc) { - traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc); - } - - public static void traceStrstr(String s1, String s2, int pc) { - traceStrstr0(encodeForLibFuzzer(s2), pc); - } - - public static void traceReflectiveCall(Executable callee, int pc) { - String className = callee.getDeclaringClass().getCanonicalName(); - String executableName = callee.getName(); - String descriptor = Utils.getDescriptor(callee); - tracePcIndir(Utils.simpleFastHash(className, executableName, descriptor), pc); - } - - public static int traceCmpLongWrapper(long arg1, long arg2, int pc) { - traceCmpLong(arg1, arg2, pc); - // Long.compare serves as a substitute for the lcmp opcode, which can't be used directly - // as the stack layout required for the call can't be achieved without local variables. - return Long.compare(arg1, arg2); - } - - // The caller has to ensure that arg1 and arg2 have the same class. - public static void traceGenericCmp(Object arg1, Object arg2, int pc) { - if (arg1 instanceof CharSequence) { - traceStrcmp(arg1.toString(), arg2.toString(), 1, pc); - } else if (arg1 instanceof Integer) { - traceCmpInt((int) arg1, (int) arg2, pc); - } else if (arg1 instanceof Long) { - traceCmpLong((long) arg1, (long) arg2, pc); - } else if (arg1 instanceof Short) { - traceCmpInt((short) arg1, (short) arg2, pc); - } else if (arg1 instanceof Byte) { - traceCmpInt((byte) arg1, (byte) arg2, pc); - } else if (arg1 instanceof Character) { - traceCmpInt((char) arg1, (char) arg2, pc); - } else if (arg1 instanceof Number) { - traceCmpLong(((Number) arg1).longValue(), ((Number) arg2).longValue(), pc); - } else if (arg1 instanceof byte[]) { - traceMemcmp((byte[]) arg1, (byte[]) arg2, 1, pc); - } - } - - /* trace-cmp */ - public static native void traceCmpInt(int arg1, int arg2, int pc); - public static native void traceConstCmpInt(int arg1, int arg2, int pc); - public static native void traceCmpLong(long arg1, long arg2, int pc); - public static native void traceSwitch(long val, long[] cases, int pc); - /* trace-div */ - public static native void traceDivInt(int val, int pc); - public static native void traceDivLong(long val, int pc); - /* trace-gep */ - public static native void traceGep(long val, int pc); - /* indirect-calls */ - public static native void tracePcIndir(int callee, int caller); - - public static native void handleLibraryLoad(); - - private static byte[] encodeForLibFuzzer(String str) { - // libFuzzer string hooks only ever consume the first 64 bytes, so we can definitely cut the - // string off after 64 characters. - return str.substring(0, Math.min(str.length(), 64)).getBytes(FUZZED_DATA_CHARSET); - } - - private static native void traceStrstr0(byte[] needle, int pc); -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java deleted file mode 100644 index c4991eb5..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java +++ /dev/null @@ -1,47 +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.runtime; - -import com.code_intelligence.jazzer.api.HookType; -import com.code_intelligence.jazzer.api.MethodHook; -import java.lang.invoke.MethodHandle; - -@SuppressWarnings("unused") -final public class TraceDivHooks { - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer", - targetMethod = "divideUnsigned", targetMethodDescriptor = "(II)I") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer", - targetMethod = "remainderUnsigned", targetMethodDescriptor = "(II)I") - public static void - intUnsignedDivide(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { - // Since the arguments are to be treated as unsigned integers we need a long to fit the - // divisor. - TraceDataFlowNativeCallbacks.traceDivLong(Integer.toUnsignedLong((int) arguments[1]), hookId); - } - - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", - targetMethod = "divideUnsigned", targetMethodDescriptor = "(JJ)J") - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", - targetMethod = "remainderUnsigned", targetMethodDescriptor = "(JJ)J") - public static void - longUnsignedDivide(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { - long divisor = (long) arguments[1]; - // Run the callback only if the divisor, which is regarded as an unsigned long, fits in a - // signed long, i.e., does not have the sign bit set. - if (divisor > 0) { - TraceDataFlowNativeCallbacks.traceDivLong(divisor, hookId); - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java deleted file mode 100644 index 897ede6c..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java +++ /dev/null @@ -1,35 +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.runtime; - -import com.code_intelligence.jazzer.api.HookType; -import com.code_intelligence.jazzer.api.MethodHook; -import java.lang.invoke.MethodHandle; -import java.lang.reflect.Executable; - -@SuppressWarnings("unused") -final public class TraceIndirHooks { - // The reflection hook is of type AFTER as it should only report calls that did not fail because - // of incorrect arguments passed. - @MethodHook( - type = HookType.AFTER, targetClassName = "java.lang.reflect.Method", targetMethod = "invoke") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.reflect.Constructor", - targetMethod = "newInstance") - public static void - methodInvoke( - MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) { - TraceDataFlowNativeCallbacks.traceReflectiveCall((Executable) thisObject, hookId); - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java deleted file mode 100644 index 81f2a208..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2022 Code Intelligence GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.code_intelligence.jazzer.runtime; - -import java.lang.reflect.Field; -import sun.misc.Unsafe; - -public final class UnsafeProvider { - private static final Unsafe UNSAFE = getUnsafeInternal(); - - public static Unsafe getUnsafe() { - return UNSAFE; - } - - private static Unsafe getUnsafeInternal() { - try { - // The Java agent is loaded by the bootstrap class loader and should thus - // pass the security checks in getUnsafe. - return Unsafe.getUnsafe(); - } catch (Throwable unused) { - // If not running as an agent, use the classical reflection trick to get an Unsafe instance, - // taking into account that the private field may have a name other than "theUnsafe": - // https://android.googlesource.com/platform/libcore/+/gingerbread/luni/src/main/java/sun/misc/Unsafe.java#32 - try { - for (Field f : Unsafe.class.getDeclaredFields()) { - if (f.getType() == Unsafe.class) { - f.setAccessible(true); - return (Unsafe) f.get(null); - } - } - return null; - } catch (Throwable t) { - t.printStackTrace(); - return null; - } - } - } -} 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())) -} |