<lambda>null1 // Copyright 2021 Code Intelligence GmbH
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 @file:JvmName("Agent")
16
17 package com.code_intelligence.jazzer.agent
18
19 import com.code_intelligence.jazzer.driver.Opt
20 import com.code_intelligence.jazzer.instrumentor.CoverageRecorder
21 import com.code_intelligence.jazzer.instrumentor.Hooks
22 import com.code_intelligence.jazzer.instrumentor.InstrumentationType
23 import com.code_intelligence.jazzer.sanitizers.Constants
24 import com.code_intelligence.jazzer.utils.ClassNameGlobber
25 import com.code_intelligence.jazzer.utils.Log
26 import com.code_intelligence.jazzer.utils.ManifestUtils
27 import java.lang.instrument.Instrumentation
28 import java.nio.file.Paths
29 import kotlin.io.path.exists
30 import kotlin.io.path.isDirectory
31
32 fun install(instrumentation: Instrumentation) {
33 installInternal(instrumentation)
34 }
35
installInternalnull36 fun installInternal(
37 instrumentation: Instrumentation,
38 userHookNames: List<String> = findManifestCustomHookNames() + Opt.customHooks,
39 disabledHookNames: List<String> = Opt.disabledHooks,
40 instrumentationIncludes: List<String> = Opt.instrumentationIncludes.get(),
41 instrumentationExcludes: List<String> = Opt.instrumentationExcludes.get(),
42 customHookIncludes: List<String> = Opt.customHookIncludes.get(),
43 customHookExcludes: List<String> = Opt.customHookExcludes.get(),
44 trace: List<String> = Opt.trace,
45 idSyncFile: String? = Opt.idSyncFile,
46 dumpClassesDir: String = Opt.dumpClassesDir,
47 additionalClassesExcludes: List<String> = Opt.additionalClassesExcludes,
48 ) {
49 val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + userHookNames).toSet()
50 check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" }
51 val customHookNames = allCustomHookNames - disabledHookNames.toSet()
52 val disabledCustomHooksToPrint = allCustomHookNames - customHookNames.toSet()
53 if (disabledCustomHooksToPrint.isNotEmpty()) {
54 Log.info("Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}")
55 }
56
57 val classNameGlobber = ClassNameGlobber(instrumentationIncludes, instrumentationExcludes + customHookNames)
58 CoverageRecorder.classNameGlobber = classNameGlobber
59 val customHookClassNameGlobber = ClassNameGlobber(customHookIncludes, customHookExcludes + customHookNames)
60 // FIXME: Setting trace to the empty string explicitly results in all rather than no trace types
61 // being applied - this is unintuitive.
62 val instrumentationTypes = (trace.takeIf { it.isNotEmpty() } ?: listOf("all")).flatMap {
63 when (it) {
64 "cmp" -> setOf(InstrumentationType.CMP)
65 "cov" -> setOf(InstrumentationType.COV)
66 "div" -> setOf(InstrumentationType.DIV)
67 "gep" -> setOf(InstrumentationType.GEP)
68 "indir" -> setOf(InstrumentationType.INDIR)
69 "native" -> setOf(InstrumentationType.NATIVE)
70 // Disable GEP instrumentation by default as it appears to negatively affect fuzzing
71 // performance. Our current GEP instrumentation only reports constant indices, but even
72 // when we instead reported non-constant indices, they tended to completely fill up the
73 // table of recent compares and value profile map.
74 "all" -> InstrumentationType.values().toSet() - InstrumentationType.GEP
75 else -> {
76 println("WARN: Skipping unknown instrumentation type $it")
77 emptySet()
78 }
79 }
80 }.toSet()
81
82 val idSyncFilePath = idSyncFile?.takeUnless { it.isEmpty() }?.let {
83 Paths.get(it).also { path ->
84 Log.info("Synchronizing coverage IDs in ${path.toAbsolutePath()}")
85 }
86 }
87 val dumpClassesDirPath = dumpClassesDir.takeUnless { it.isEmpty() }?.let {
88 Paths.get(it).toAbsolutePath().also { path ->
89 if (path.exists() && path.isDirectory()) {
90 Log.info("Dumping instrumented classes into $path")
91 } else {
92 Log.error("Cannot dump instrumented classes into $path; does not exist or not a directory")
93 }
94 }
95 }
96 val includedHookNames = instrumentationTypes
97 .mapNotNull { type ->
98 when (type) {
99 InstrumentationType.CMP -> "com.code_intelligence.jazzer.runtime.TraceCmpHooks"
100 InstrumentationType.DIV -> "com.code_intelligence.jazzer.runtime.TraceDivHooks"
101 InstrumentationType.INDIR -> "com.code_intelligence.jazzer.runtime.TraceIndirHooks"
102 InstrumentationType.NATIVE -> "com.code_intelligence.jazzer.runtime.NativeLibHooks"
103 else -> null
104 }
105 }
106 val coverageIdSynchronizer = if (idSyncFilePath != null) {
107 FileSyncCoverageIdStrategy(idSyncFilePath)
108 } else {
109 MemSyncCoverageIdStrategy()
110 }
111
112 // If we don't append the JARs containing the custom hooks to the bootstrap class loader,
113 // third-party hooks not contained in the agent JAR will not be able to instrument Java standard
114 // library classes. These classes are loaded by the bootstrap / system class loader and would
115 // not be considered when resolving references to hook methods, leading to NoClassDefFoundError
116 // being thrown.
117 Hooks.appendHooksToBootstrapClassLoaderSearch(instrumentation, customHookNames.toSet())
118 val (includedHooks, customHooks) = Hooks.loadHooks(additionalClassesExcludes, includedHookNames.toSet(), customHookNames.toSet())
119
120 val runtimeInstrumentor = RuntimeInstrumentor(
121 instrumentation,
122 classNameGlobber,
123 customHookClassNameGlobber,
124 instrumentationTypes,
125 includedHooks.hooks,
126 customHooks.hooks,
127 customHooks.additionalHookClassNameGlobber,
128 coverageIdSynchronizer,
129 dumpClassesDirPath,
130 )
131
132 // These classes are e.g. dependencies of the RuntimeInstrumentor or hooks and thus were loaded
133 // before the instrumentor was ready. Since we haven't enabled it yet, they can safely be
134 // "retransformed": They haven't been transformed yet.
135 val classesToRetransform = instrumentation.allLoadedClasses
136 .filter {
137 classNameGlobber.includes(it.name) ||
138 customHookClassNameGlobber.includes(it.name) ||
139 customHooks.additionalHookClassNameGlobber.includes(it.name)
140 }
141 .filter {
142 instrumentation.isModifiableClass(it)
143 }
144 .toTypedArray()
145
146 instrumentation.addTransformer(runtimeInstrumentor, true)
147
148 if (classesToRetransform.isNotEmpty()) {
149 if (instrumentation.isRetransformClassesSupported) {
150 retransformClassesWithRetry(instrumentation, classesToRetransform)
151 }
152 }
153 }
154
retransformClassesWithRetrynull155 private fun retransformClassesWithRetry(instrumentation: Instrumentation, classesToRetransform: Array<Class<*>>) {
156 try {
157 instrumentation.retransformClasses(*classesToRetransform)
158 } catch (e: Throwable) {
159 if (classesToRetransform.size == 1) {
160 Log.warn("Error retransforming class ${classesToRetransform[0].name }", e)
161 } else {
162 // The docs state that no transformation was performed if an exception is thrown.
163 // Try again in a binary search fashion, until the not transformable classes have been isolated and reported.
164 retransformClassesWithRetry(instrumentation, classesToRetransform.copyOfRange(0, classesToRetransform.size / 2))
165 retransformClassesWithRetry(instrumentation, classesToRetransform.copyOfRange(classesToRetransform.size / 2, classesToRetransform.size))
166 }
167 }
168 }
169
findManifestCustomHookNamesnull170 private fun findManifestCustomHookNames() = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES)
171 .flatMap { it.split(':') }
<lambda>null172 .filter { it.isNotBlank() }
173