From 752875c3de6365aad8c590cb6170b40422217fe2 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 15 Dec 2022 09:03:19 +0100 Subject: all: Merge `//driver/...` and `//agent/...` The "driver" and the "agent" used to be separate components of Jazzer that were written in different languages (C++ and Java), but this is no longer the case: They are now mostly implemented in Java and deployed as a single jar. The natural separation into Java packages rather than top-level directories fits this architecture better. This commit has been produced by moving the directory contents, replacing `//(agent|driver)/` with `//`, running `buildifier -r .` and manually editing non-BUILD occurences (e.g. .bazelrc, format.sh as well as runfiles paths). --- .../com/code_intelligence/jazzer/agent/Agent.kt | 142 +++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/main/java/com/code_intelligence/jazzer/agent/Agent.kt (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt') diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt new file mode 100644 index 00000000..b0f3a845 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -0,0 +1,142 @@ +// 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.sanitizers.Constants +import com.code_intelligence.jazzer.utils.ClassNameGlobber +import com.code_intelligence.jazzer.utils.ManifestUtils +import java.lang.instrument.Instrumentation +import java.nio.file.Paths +import kotlin.io.path.exists +import kotlin.io.path.isDirectory + +fun install(instrumentation: Instrumentation) { + val manifestCustomHookNames = + ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { + it.split(':') + }.filter { it.isNotBlank() } + val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + manifestCustomHookNames + Opt.customHooks).toSet() + check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" } + 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 -> "com.code_intelligence.jazzer.runtime.TraceCmpHooks" + InstrumentationType.DIV -> "com.code_intelligence.jazzer.runtime.TraceDivHooks" + InstrumentationType.INDIR -> "com.code_intelligence.jazzer.runtime.TraceIndirHooks" + InstrumentationType.NATIVE -> "com.code_intelligence.jazzer.runtime.NativeLibHooks" + else -> null + } + } + val coverageIdSynchronizer = if (idSyncFile != null) + FileSyncCoverageIdStrategy(idSyncFile) + else + MemSyncCoverageIdStrategy() + + // If we don't append the JARs containing the custom hooks to the bootstrap class loader, + // third-party hooks not contained in the agent JAR will not be able to instrument Java standard + // 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. + Hooks.appendHooksToBootstrapClassLoaderSearch(instrumentation, customHookNames.toSet()) + val (includedHooks, customHooks) = Hooks.loadHooks(includedHookNames.toSet(), customHookNames.toSet()) + + 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()}") + } + } +} -- cgit v1.2.3 From e5f3fba18a6e81081da563c769929ed1ab6e524d Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Thu, 22 Dec 2022 03:55:46 +0100 Subject: format: update scripts and docs, and rerun the scripts --- src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt') diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index b0f3a845..83b7876b 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -90,10 +90,11 @@ fun install(instrumentation: Instrumentation) { else -> null } } - val coverageIdSynchronizer = if (idSyncFile != null) + val coverageIdSynchronizer = if (idSyncFile != null) { FileSyncCoverageIdStrategy(idSyncFile) - else + } else { MemSyncCoverageIdStrategy() + } // If we don't append the JARs containing the custom hooks to the bootstrap class loader, // third-party hooks not contained in the agent JAR will not be able to instrument Java standard -- cgit v1.2.3 From 3426ce3612d4fd20800c2e29115f533e0b141f27 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 10 Jan 2023 11:29:48 +0100 Subject: agent: Extract option processing out of Agent.install --- .../com/code_intelligence/jazzer/agent/Agent.kt | 44 ++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt') diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 83b7876b..47c774ac 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -29,25 +29,35 @@ import kotlin.io.path.exists import kotlin.io.path.isDirectory fun install(instrumentation: Instrumentation) { - val manifestCustomHookNames = - ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { - it.split(':') - }.filter { it.isNotBlank() } - val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + manifestCustomHookNames + Opt.customHooks).toSet() + installInternal(instrumentation) +} + +fun installInternal( + instrumentation: Instrumentation, + userHookNames: List = findManifestCustomHookNames() + Opt.customHooks, + disabledHookNames: List = Opt.disabledHooks, + instrumentationIncludes: List = Opt.instrumentationIncludes, + instrumentationExcludes: List = Opt.instrumentationExcludes, + customHookIncludes: List = Opt.customHookIncludes, + customHookExcludes: List = Opt.customHookExcludes, + trace: List = Opt.trace, + idSyncFile: String? = Opt.idSyncFile, + dumpClassesDir: String = Opt.dumpClassesDir, +) { + val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + userHookNames).toSet() check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" } - val disabledCustomHookNames = Opt.disabledHooks.toSet() - val customHookNames = allCustomHookNames - disabledCustomHookNames + val customHookNames = allCustomHookNames - disabledHookNames.toSet() 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) + val classNameGlobber = ClassNameGlobber(instrumentationIncludes, instrumentationExcludes + customHookNames) CoverageRecorder.classNameGlobber = classNameGlobber - val customHookClassNameGlobber = ClassNameGlobber(Opt.customHookIncludes, Opt.customHookExcludes + customHookNames) + val customHookClassNameGlobber = ClassNameGlobber(customHookIncludes, customHookExcludes + customHookNames) // FIXME: Setting trace to the empty string explicitly results in all rather than no trace types // being applied - this is unintuitive. - val instrumentationTypes = (Opt.trace.takeIf { it.isNotEmpty() } ?: listOf("all")).flatMap { + val instrumentationTypes = (trace.takeIf { it.isNotEmpty() } ?: listOf("all")).flatMap { when (it) { "cmp" -> setOf(InstrumentationType.CMP) "cov" -> setOf(InstrumentationType.COV) @@ -66,12 +76,12 @@ fun install(instrumentation: Instrumentation) { } } }.toSet() - val idSyncFile = Opt.idSyncFile?.takeUnless { it.isEmpty() }?.let { + val idSyncFilePath = 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 { + val dumpClassesDirPath = dumpClassesDir.takeUnless { it.isEmpty() }?.let { Paths.get(it).toAbsolutePath().also { path -> if (path.exists() && path.isDirectory()) { println("INFO: Dumping instrumented classes into $path") @@ -90,8 +100,8 @@ fun install(instrumentation: Instrumentation) { else -> null } } - val coverageIdSynchronizer = if (idSyncFile != null) { - FileSyncCoverageIdStrategy(idSyncFile) + val coverageIdSynchronizer = if (idSyncFilePath != null) { + FileSyncCoverageIdStrategy(idSyncFilePath) } else { MemSyncCoverageIdStrategy() } @@ -113,7 +123,7 @@ fun install(instrumentation: Instrumentation) { customHooks.hooks, customHooks.additionalHookClassNameGlobber, coverageIdSynchronizer, - dumpClassesDir, + dumpClassesDirPath, ) // These classes are e.g. dependencies of the RuntimeInstrumentor or hooks and thus were loaded @@ -141,3 +151,7 @@ fun install(instrumentation: Instrumentation) { } } } + +private fun findManifestCustomHookNames() = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES) + .flatMap { it.split(':') } + .filter { it.isNotBlank() } -- cgit v1.2.3 From efc2341a009c1459d904bd9bef9d2bdb9fb68529 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 30 Dec 2022 19:11:08 +0100 Subject: all: Use logger class everywhere This commit replaces all direct usages of `System.err` and `System.out` with equivalent `Log` method calls. This change is meant to be mostly invisible to users. In some cases the formatting is changed as we no longer manually wrap lines, which we never did in a consistent manner. If we want to bring back line breaks, we should implement this in `Log` instead in a follow-up PR. In some cases unstructured logging to stdout is moved to stderr. Fixes FUZZ-480 --- src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt') diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 47c774ac..aed8821f 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -22,6 +22,7 @@ import com.code_intelligence.jazzer.instrumentor.Hooks import com.code_intelligence.jazzer.instrumentor.InstrumentationType import com.code_intelligence.jazzer.sanitizers.Constants import com.code_intelligence.jazzer.utils.ClassNameGlobber +import com.code_intelligence.jazzer.utils.Log import com.code_intelligence.jazzer.utils.ManifestUtils import java.lang.instrument.Instrumentation import java.nio.file.Paths @@ -49,7 +50,7 @@ fun installInternal( val customHookNames = allCustomHookNames - disabledHookNames.toSet() val disabledCustomHooksToPrint = allCustomHookNames - customHookNames.toSet() if (disabledCustomHooksToPrint.isNotEmpty()) { - println("INFO: Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}") + Log.info("Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}") } val classNameGlobber = ClassNameGlobber(instrumentationIncludes, instrumentationExcludes + customHookNames) @@ -78,15 +79,15 @@ fun installInternal( }.toSet() val idSyncFilePath = idSyncFile?.takeUnless { it.isEmpty() }?.let { Paths.get(it).also { path -> - println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}") + Log.info("Synchronizing coverage IDs in ${path.toAbsolutePath()}") } } val dumpClassesDirPath = dumpClassesDir.takeUnless { it.isEmpty() }?.let { Paths.get(it).toAbsolutePath().also { path -> if (path.exists() && path.isDirectory()) { - println("INFO: Dumping instrumented classes into $path") + Log.info("Dumping instrumented classes into $path") } else { - println("ERROR: Cannot dump instrumented classes into $path; does not exist or not a directory") + Log.error("Cannot dump instrumented classes into $path; does not exist or not a directory") } } } @@ -146,8 +147,8 @@ fun installInternal( 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()}") + Log.warn("Instrumentation was not applied to the following classes as they are dependencies of hooks:") + Log.warn(classesToRetransform.joinToString()) } } } -- cgit v1.2.3 From db58481ddd6908fc37fcb5dcf357fa11b155b349 Mon Sep 17 00:00:00 2001 From: Cory Barker Date: Mon, 6 Mar 2023 21:25:20 +0000 Subject: Added offline instrumentation support --- src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt') diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index aed8821f..8788aa23 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -44,6 +44,7 @@ fun installInternal( trace: List = Opt.trace, idSyncFile: String? = Opt.idSyncFile, dumpClassesDir: String = Opt.dumpClassesDir, + additionalClassesExcludes: List = Opt.additionalClassesExcludes, ) { val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + userHookNames).toSet() check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" } @@ -77,6 +78,7 @@ fun installInternal( } } }.toSet() + val idSyncFilePath = idSyncFile?.takeUnless { it.isEmpty() }?.let { Paths.get(it).also { path -> Log.info("Synchronizing coverage IDs in ${path.toAbsolutePath()}") @@ -113,7 +115,7 @@ fun installInternal( // not be considered when resolving references to hook methods, leading to NoClassDefFoundError // being thrown. Hooks.appendHooksToBootstrapClassLoaderSearch(instrumentation, customHookNames.toSet()) - val (includedHooks, customHooks) = Hooks.loadHooks(includedHookNames.toSet(), customHookNames.toSet()) + val (includedHooks, customHooks) = Hooks.loadHooks(additionalClassesExcludes, includedHookNames.toSet(), customHookNames.toSet()) val runtimeInstrumentor = RuntimeInstrumentor( instrumentation, -- cgit v1.2.3 From 48ff37c56954ca50c4439f7023006026c22c4057 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 6 Mar 2023 12:18:30 +0100 Subject: agent: retry instrumentation on errors Errors retransforming a class should not prevent Jazzer from starting. These classes are assumed to be not interesting nor important for the fuzzing run and can be safely ignored. --- .../java/com/code_intelligence/jazzer/agent/Agent.kt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt') diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 8788aa23..6e89aafc 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -147,10 +147,22 @@ fun installInternal( if (classesToRetransform.isNotEmpty()) { if (instrumentation.isRetransformClassesSupported) { - instrumentation.retransformClasses(*classesToRetransform) + retransformClassesWithRetry(instrumentation, classesToRetransform) + } + } +} + +private fun retransformClassesWithRetry(instrumentation: Instrumentation, classesToRetransform: Array>) { + try { + instrumentation.retransformClasses(*classesToRetransform) + } catch (e: Throwable) { + if (classesToRetransform.size == 1) { + Log.warn("Error retransforming class ${classesToRetransform[0].name }", e) } else { - Log.warn("Instrumentation was not applied to the following classes as they are dependencies of hooks:") - Log.warn(classesToRetransform.joinToString()) + // The docs state that no transformation was performed if an exception is thrown. + // Try again in a binary search fashion, until the not transformable classes have been isolated and reported. + retransformClassesWithRetry(instrumentation, classesToRetransform.copyOfRange(0, classesToRetransform.size / 2)) + retransformClassesWithRetry(instrumentation, classesToRetransform.copyOfRange(classesToRetransform.size / 2, classesToRetransform.size)) } } } -- cgit v1.2.3 From 7883e9876bf8ac00669ffd50075b7768f0a7b8f9 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 25 May 2023 14:42:48 +0200 Subject: junit: Honor instrumentation settings when run from the driver The `Driver` accesses `Opt` settings and thus locked in their values before the JUnit integration, specifically `AgentConfigurator`, had a chance to update the instrumentation filter. This is worked around by evaluating the instrumentation settings lazily, with a more conceptual fix coming with the ongoing config overhaul. --- src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt') diff --git a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 6e89aafc..9bcd744f 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -37,10 +37,10 @@ fun installInternal( instrumentation: Instrumentation, userHookNames: List = findManifestCustomHookNames() + Opt.customHooks, disabledHookNames: List = Opt.disabledHooks, - instrumentationIncludes: List = Opt.instrumentationIncludes, - instrumentationExcludes: List = Opt.instrumentationExcludes, - customHookIncludes: List = Opt.customHookIncludes, - customHookExcludes: List = Opt.customHookExcludes, + instrumentationIncludes: List = Opt.instrumentationIncludes.get(), + instrumentationExcludes: List = Opt.instrumentationExcludes.get(), + customHookIncludes: List = Opt.customHookIncludes.get(), + customHookExcludes: List = Opt.customHookExcludes.get(), trace: List = Opt.trace, idSyncFile: String? = Opt.idSyncFile, dumpClassesDir: String = Opt.dumpClassesDir, -- cgit v1.2.3