aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt
blob: a26c0d6bfc0582e736dff36fb2e08a34d2d1f583 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 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.

package com.code_intelligence.jazzer.instrumentor

import com.code_intelligence.jazzer.api.MethodHook
import com.code_intelligence.jazzer.api.MethodHooks
import com.code_intelligence.jazzer.utils.ClassNameGlobber
import com.code_intelligence.jazzer.utils.Log
import io.github.classgraph.ClassGraph
import io.github.classgraph.ScanResult
import java.lang.instrument.Instrumentation
import java.lang.reflect.Method
import java.util.jar.JarFile

data class Hooks(
    val hooks: List<Hook>,
    val hookClasses: Set<Class<*>>,
    val additionalHookClassNameGlobber: ClassNameGlobber,
) {

    companion object {

        fun appendHooksToBootstrapClassLoaderSearch(instrumentation: Instrumentation, hookClassNames: Set<String>) {
            hookClassNames.mapNotNull { hook ->
                val hookClassFilePath = "/${hook.replace('.', '/')}.class"
                val hookClassFile = Companion::class.java.getResource(hookClassFilePath) ?: return@mapNotNull null
                if ("jar" != hookClassFile.protocol) {
                    return@mapNotNull null
                }
                // hookClassFile.file looks as follows:
                // file:/tmp/ExampleFuzzerHooks_deploy.jar!/com/example/ExampleFuzzerHooks.class
                hookClassFile.file.removePrefix("file:").takeWhile { it != '!' }
            }
                .toSet()
                .map { JarFile(it) }
                .forEach { instrumentation.appendToBootstrapClassLoaderSearch(it) }
        }

        fun loadHooks(excludeHookClassNames: List<String>, vararg hookClassNames: Set<String>): List<Hooks> {
            return ClassGraph()
                .enableClassInfo()
                .enableSystemJarsAndModules()
                .rejectPackages("jaz.*", "com.code_intelligence.jazzer.*")
                .scan()
                .use { scanResult ->
                    // Capture scanResult in HooksLoader field to not pass it through
                    // all internal hook loading methods.
                    val loader = HooksLoader(scanResult, excludeHookClassNames)
                    hookClassNames.map(loader::load)
                }
        }

        private class HooksLoader(private val scanResult: ScanResult, val excludeHookClassNames: List<String>) {

            fun load(hookClassNames: Set<String>): Hooks {
                val hooksWithHookClasses = hookClassNames.flatMap(::loadHooks)
                val hooks = hooksWithHookClasses.map { it.first }
                val hookClasses = hooksWithHookClasses.map { it.second }.toSet()
                val additionalHookClassNameGlobber = ClassNameGlobber(
                    hooks.flatMap(Hook::additionalClassesToHook),
                    excludeHookClassNames,
                )
                return Hooks(hooks, hookClasses, additionalHookClassNameGlobber)
            }

            private fun loadHooks(hookClassName: String): List<Pair<Hook, Class<*>>> {
                return try {
                    // We let the static initializers of hook classes execute so that hooks can run
                    // code before the fuzz target class has been loaded (e.g., register themselves
                    // for the onFuzzTargetReady callback).
                    val hookClass =
                        Class.forName(hookClassName, true, Companion::class.java.classLoader)
                    loadHooks(hookClass).also {
                        Log.info("Loaded ${it.size} hooks from $hookClassName")
                    }.map {
                        it to hookClass
                    }
                } catch (e: ClassNotFoundException) {
                    Log.warn("Failed to load hooks from $hookClassName", e)
                    emptyList()
                }
            }

            private fun loadHooks(hookClass: Class<*>): List<Hook> {
                val hooks = mutableListOf<Hook>()
                for (method in hookClass.methods.sortedBy { it.descriptor }) {
                    method.getAnnotation(MethodHook::class.java)?.let {
                        hooks.addAll(verifyAndGetHooks(method, it))
                    }
                    method.getAnnotation(MethodHooks::class.java)?.let {
                        it.value.forEach { hookAnnotation ->
                            hooks.addAll(verifyAndGetHooks(method, hookAnnotation))
                        }
                    }
                }
                return hooks
            }

            private fun verifyAndGetHooks(hookMethod: Method, hookData: MethodHook): List<Hook> {
                return lookupClassesToHook(hookData.targetClassName)
                    .map { className ->
                        Hook.createAndVerifyHook(hookMethod, hookData, className)
                    }
            }

            private fun lookupClassesToHook(annotationTargetClassName: String): List<String> {
                // Allowing arbitrary exterior whitespace in the target class name allows for an easy workaround
                // for mangled hooks due to shading applied to hooks.
                val targetClassName = annotationTargetClassName.trim()
                val targetClassInfo = scanResult.getClassInfo(targetClassName) ?: return listOf(targetClassName)
                val additionalTargetClasses = when {
                    targetClassInfo.isInterface -> scanResult.getClassesImplementing(targetClassName)
                    targetClassInfo.isAbstract -> scanResult.getSubclasses(targetClassName)
                    else -> emptyList()
                }
                return (listOf(targetClassName) + additionalTargetClasses.map { it.name }).sorted()
            }
        }
    }
}