<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 package com.code_intelligence.jazzer.instrumentor
16
17 import com.code_intelligence.jazzer.api.MethodHook
18 import com.code_intelligence.jazzer.api.MethodHooks
19 import com.code_intelligence.jazzer.utils.ClassNameGlobber
20 import com.code_intelligence.jazzer.utils.Log
21 import io.github.classgraph.ClassGraph
22 import io.github.classgraph.ScanResult
23 import java.lang.instrument.Instrumentation
24 import java.lang.reflect.Method
25 import java.util.jar.JarFile
26
27 data class Hooks(
28 val hooks: List<Hook>,
29 val hookClasses: Set<Class<*>>,
30 val additionalHookClassNameGlobber: ClassNameGlobber,
31 ) {
32
33 companion object {
34
35 fun appendHooksToBootstrapClassLoaderSearch(instrumentation: Instrumentation, hookClassNames: Set<String>) {
36 hookClassNames.mapNotNull { hook ->
37 val hookClassFilePath = "/${hook.replace('.', '/')}.class"
38 val hookClassFile = Companion::class.java.getResource(hookClassFilePath) ?: return@mapNotNull null
39 if ("jar" != hookClassFile.protocol) {
40 return@mapNotNull null
41 }
42 // hookClassFile.file looks as follows:
43 // file:/tmp/ExampleFuzzerHooks_deploy.jar!/com/example/ExampleFuzzerHooks.class
44 hookClassFile.file.removePrefix("file:").takeWhile { it != '!' }
45 }
46 .toSet()
47 .map { JarFile(it) }
48 .forEach { instrumentation.appendToBootstrapClassLoaderSearch(it) }
49 }
50
51 fun loadHooks(excludeHookClassNames: List<String>, vararg hookClassNames: Set<String>): List<Hooks> {
52 return ClassGraph()
53 .enableClassInfo()
54 .enableSystemJarsAndModules()
55 .rejectPackages("jaz.*", "com.code_intelligence.jazzer.*")
56 .scan()
57 .use { scanResult ->
58 // Capture scanResult in HooksLoader field to not pass it through
59 // all internal hook loading methods.
60 val loader = HooksLoader(scanResult, excludeHookClassNames)
61 hookClassNames.map(loader::load)
62 }
63 }
64
65 private class HooksLoader(private val scanResult: ScanResult, val excludeHookClassNames: List<String>) {
66
67 fun load(hookClassNames: Set<String>): Hooks {
68 val hooksWithHookClasses = hookClassNames.flatMap(::loadHooks)
69 val hooks = hooksWithHookClasses.map { it.first }
70 val hookClasses = hooksWithHookClasses.map { it.second }.toSet()
71 val additionalHookClassNameGlobber = ClassNameGlobber(
72 hooks.flatMap(Hook::additionalClassesToHook),
73 excludeHookClassNames,
74 )
75 return Hooks(hooks, hookClasses, additionalHookClassNameGlobber)
76 }
77
78 private fun loadHooks(hookClassName: String): List<Pair<Hook, Class<*>>> {
79 return try {
80 // We let the static initializers of hook classes execute so that hooks can run
81 // code before the fuzz target class has been loaded (e.g., register themselves
82 // for the onFuzzTargetReady callback).
83 val hookClass =
84 Class.forName(hookClassName, true, Companion::class.java.classLoader)
85 loadHooks(hookClass).also {
86 Log.info("Loaded ${it.size} hooks from $hookClassName")
87 }.map {
88 it to hookClass
89 }
90 } catch (e: ClassNotFoundException) {
91 Log.warn("Failed to load hooks from $hookClassName", e)
92 emptyList()
93 }
94 }
95
96 private fun loadHooks(hookClass: Class<*>): List<Hook> {
97 val hooks = mutableListOf<Hook>()
98 for (method in hookClass.methods.sortedBy { it.descriptor }) {
99 method.getAnnotation(MethodHook::class.java)?.let {
100 hooks.addAll(verifyAndGetHooks(method, it))
101 }
102 method.getAnnotation(MethodHooks::class.java)?.let {
103 it.value.forEach { hookAnnotation ->
104 hooks.addAll(verifyAndGetHooks(method, hookAnnotation))
105 }
106 }
107 }
108 return hooks
109 }
110
111 private fun verifyAndGetHooks(hookMethod: Method, hookData: MethodHook): List<Hook> {
112 return lookupClassesToHook(hookData.targetClassName)
113 .map { className ->
114 Hook.createAndVerifyHook(hookMethod, hookData, className)
115 }
116 }
117
118 private fun lookupClassesToHook(annotationTargetClassName: String): List<String> {
119 // Allowing arbitrary exterior whitespace in the target class name allows for an easy workaround
120 // for mangled hooks due to shading applied to hooks.
121 val targetClassName = annotationTargetClassName.trim()
122 val targetClassInfo = scanResult.getClassInfo(targetClassName) ?: return listOf(targetClassName)
123 val additionalTargetClasses = when {
124 targetClassInfo.isInterface -> scanResult.getClassesImplementing(targetClassName)
125 targetClassInfo.isAbstract -> scanResult.getSubclasses(targetClassName)
126 else -> emptyList()
127 }
128 return (listOf(targetClassName) + additionalTargetClasses.map { it.name }).sorted()
129 }
130 }
131 }
132 }
133