• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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