diff options
author | Mark <mteffeteller@google.com> | 2023-06-21 23:30:18 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-06-21 23:30:18 +0000 |
commit | 54819157eaa66e14f2c68b54609bd6bfa360b708 (patch) | |
tree | 68cf332a40b94b2d28b256b19b916f99220bb0c4 /src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | |
parent | ba37c2e361c2ba91bacc47fcae5383c52e50f6be (diff) | |
parent | e73be1680dae58cb83d869104def1c59102d59b2 (diff) | |
download | jazzer-api-54819157eaa66e14f2c68b54609bd6bfa360b708.tar.gz |
Sync jazzer in AOSP with upstream repo (new SHA: 30decf81a147c66fa5a098072c38ab6924ba0aa6) am: 9350e0ab03 am: 99d9a79746 am: 34a8e5c8aa am: e73be1680d
Original change: https://android-review.googlesource.com/c/platform/external/jazzer-api/+/2627336
Change-Id: I1b97ed5cdcf2adda4d443148cc0d447974e51785
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Diffstat (limited to 'src/main/java/com/code_intelligence/jazzer/agent/Agent.kt')
-rw-r--r-- | src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | 172 |
1 files changed, 172 insertions, 0 deletions
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..9bcd744f --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -0,0 +1,172 @@ +// 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.Log +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) { + installInternal(instrumentation) +} + +fun installInternal( + instrumentation: Instrumentation, + userHookNames: List<String> = findManifestCustomHookNames() + Opt.customHooks, + disabledHookNames: List<String> = Opt.disabledHooks, + instrumentationIncludes: List<String> = Opt.instrumentationIncludes.get(), + instrumentationExcludes: List<String> = Opt.instrumentationExcludes.get(), + customHookIncludes: List<String> = Opt.customHookIncludes.get(), + customHookExcludes: List<String> = Opt.customHookExcludes.get(), + trace: List<String> = Opt.trace, + idSyncFile: String? = Opt.idSyncFile, + dumpClassesDir: String = Opt.dumpClassesDir, + additionalClassesExcludes: List<String> = Opt.additionalClassesExcludes, +) { + val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + userHookNames).toSet() + check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" } + val customHookNames = allCustomHookNames - disabledHookNames.toSet() + val disabledCustomHooksToPrint = allCustomHookNames - customHookNames.toSet() + if (disabledCustomHooksToPrint.isNotEmpty()) { + Log.info("Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}") + } + + val classNameGlobber = ClassNameGlobber(instrumentationIncludes, instrumentationExcludes + customHookNames) + CoverageRecorder.classNameGlobber = classNameGlobber + val customHookClassNameGlobber = ClassNameGlobber(customHookIncludes, customHookExcludes + customHookNames) + // FIXME: Setting trace to the empty string explicitly results in all rather than no trace types + // being applied - this is unintuitive. + val instrumentationTypes = (trace.takeIf { it.isNotEmpty() } ?: listOf("all")).flatMap { + when (it) { + "cmp" -> setOf(InstrumentationType.CMP) + "cov" -> setOf(InstrumentationType.COV) + "div" -> setOf(InstrumentationType.DIV) + "gep" -> setOf(InstrumentationType.GEP) + "indir" -> setOf(InstrumentationType.INDIR) + "native" -> setOf(InstrumentationType.NATIVE) + // Disable GEP instrumentation by default as it appears to negatively affect fuzzing + // performance. Our current GEP instrumentation only reports constant indices, but even + // when we instead reported non-constant indices, they tended to completely fill up the + // table of recent compares and value profile map. + "all" -> InstrumentationType.values().toSet() - InstrumentationType.GEP + else -> { + println("WARN: Skipping unknown instrumentation type $it") + emptySet() + } + } + }.toSet() + + val idSyncFilePath = idSyncFile?.takeUnless { it.isEmpty() }?.let { + Paths.get(it).also { path -> + Log.info("Synchronizing coverage IDs in ${path.toAbsolutePath()}") + } + } + val dumpClassesDirPath = dumpClassesDir.takeUnless { it.isEmpty() }?.let { + Paths.get(it).toAbsolutePath().also { path -> + if (path.exists() && path.isDirectory()) { + Log.info("Dumping instrumented classes into $path") + } else { + Log.error("Cannot dump instrumented classes into $path; does not exist or not a directory") + } + } + } + val includedHookNames = instrumentationTypes + .mapNotNull { type -> + when (type) { + InstrumentationType.CMP -> "com.code_intelligence.jazzer.runtime.TraceCmpHooks" + InstrumentationType.DIV -> "com.code_intelligence.jazzer.runtime.TraceDivHooks" + InstrumentationType.INDIR -> "com.code_intelligence.jazzer.runtime.TraceIndirHooks" + InstrumentationType.NATIVE -> "com.code_intelligence.jazzer.runtime.NativeLibHooks" + else -> null + } + } + val coverageIdSynchronizer = if (idSyncFilePath != null) { + FileSyncCoverageIdStrategy(idSyncFilePath) + } else { + MemSyncCoverageIdStrategy() + } + + // 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(additionalClassesExcludes, includedHookNames.toSet(), customHookNames.toSet()) + + val runtimeInstrumentor = RuntimeInstrumentor( + instrumentation, + classNameGlobber, + customHookClassNameGlobber, + instrumentationTypes, + includedHooks.hooks, + customHooks.hooks, + customHooks.additionalHookClassNameGlobber, + coverageIdSynchronizer, + dumpClassesDirPath, + ) + + // These classes are e.g. dependencies of the RuntimeInstrumentor or hooks and thus were loaded + // before the instrumentor was ready. Since we haven't enabled it yet, they can safely be + // "retransformed": They haven't been transformed yet. + val classesToRetransform = instrumentation.allLoadedClasses + .filter { + 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) { + retransformClassesWithRetry(instrumentation, classesToRetransform) + } + } +} + +private fun retransformClassesWithRetry(instrumentation: Instrumentation, classesToRetransform: Array<Class<*>>) { + try { + instrumentation.retransformClasses(*classesToRetransform) + } catch (e: Throwable) { + if (classesToRetransform.size == 1) { + Log.warn("Error retransforming class ${classesToRetransform[0].name }", e) + } else { + // 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)) + } + } +} + +private fun findManifestCustomHookNames() = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES) + .flatMap { it.split(':') } + .filter { it.isNotBlank() } |