<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.HookType
18 import org.objectweb.asm.Handle
19 import org.objectweb.asm.MethodVisitor
20 import org.objectweb.asm.Opcodes
21 import org.objectweb.asm.Type
22 import org.objectweb.asm.commons.LocalVariablesSorter
23 import java.util.concurrent.atomic.AtomicBoolean
24
25 internal fun makeHookMethodVisitor(
26 access: Int,
27 descriptor: String?,
28 methodVisitor: MethodVisitor?,
29 hooks: Iterable<Hook>,
30 java6Mode: Boolean,
31 random: DeterministicRandom,
32 ): MethodVisitor {
33 return HookMethodVisitor(access, descriptor, methodVisitor, hooks, java6Mode, random).lvs
34 }
35
36 private class HookMethodVisitor(
37 access: Int,
38 descriptor: String?,
39 methodVisitor: MethodVisitor?,
40 hooks: Iterable<Hook>,
41 private val java6Mode: Boolean,
42 private val random: DeterministicRandom,
43 ) : MethodVisitor(Instrumentor.ASM_API_VERSION, methodVisitor) {
44
45 companion object {
46 private val showUnsupportedHookWarning = AtomicBoolean(true)
47 }
48
49 val lvs = object : LocalVariablesSorter(Instrumentor.ASM_API_VERSION, access, descriptor, this) {
updateNewLocalsnull50 override fun updateNewLocals(newLocals: Array<Any>) {
51 // The local variables involved in calling hooks do not need to outlive the current
52 // basic block and should thus not appear in stack map frames. By requesting the
53 // LocalVariableSorter to fill their entries in stack map frames with TOP, they will
54 // be treated like an unused local variable slot.
55 newLocals.fill(Opcodes.TOP)
56 }
57 }
58
hooknull59 private val hooks = hooks.groupBy { hook ->
60 var hookKey = "${hook.hookType}#${hook.targetInternalClassName}#${hook.targetMethodName}"
61 if (hook.targetMethodDescriptor != null)
62 hookKey += "#${hook.targetMethodDescriptor}"
63 hookKey
64 }
65
visitMethodInsnnull66 override fun visitMethodInsn(
67 opcode: Int,
68 owner: String,
69 methodName: String,
70 methodDescriptor: String,
71 isInterface: Boolean,
72 ) {
73 if (!isMethodInvocationOp(opcode)) {
74 mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
75 return
76 }
77 handleMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
78 }
79
handleMethodInsnnull80 fun handleMethodInsn(
81 opcode: Int,
82 owner: String,
83 methodName: String,
84 methodDescriptor: String,
85 isInterface: Boolean,
86 ) {
87 val matchingHooks = findMatchingHooks(owner, methodName, methodDescriptor)
88
89 if (matchingHooks.isEmpty()) {
90 mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
91 return
92 }
93
94 val paramDescriptors = extractParameterTypeDescriptors(methodDescriptor)
95 val localObjArr = storeMethodArguments(paramDescriptors)
96 // If the method we're hooking is not static there is now a reference to
97 // the object the method was invoked on at the top of the stack.
98 // If the method is static, that object is missing. We make up for it by pushing a null ref.
99 if (opcode == Opcodes.INVOKESTATIC) {
100 mv.visitInsn(Opcodes.ACONST_NULL)
101 }
102
103 // Save the owner object to a new local variable
104 val ownerDescriptor = "L$owner;"
105 val localOwnerObj = lvs.newLocal(Type.getType(ownerDescriptor))
106 mv.visitVarInsn(Opcodes.ASTORE, localOwnerObj) // consume objectref
107 // We now removed all values for the original method call from the operand stack
108 // and saved them to local variables.
109
110 val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor)
111 // Create a local variable to store the return value
112 val localReturnObj = lvs.newLocal(Type.getType(getWrapperTypeDescriptor(returnTypeDescriptor)))
113
114 matchingHooks.forEachIndexed { index, hook ->
115 // The hookId is used to identify a call site.
116 val hookId = random.nextInt()
117
118 // Start to build the arguments for the hook method.
119 if (methodName == "<init>") {
120 // Constructor is invoked on an uninitialized object, and that's still on the stack.
121 // In case of REPLACE pop it from the stack and replace it afterwards with the returned
122 // one from the hook.
123 if (hook.hookType == HookType.REPLACE) {
124 mv.visitInsn(Opcodes.POP)
125 }
126 // Special case for constructors:
127 // We cannot create a MethodHandle for a constructor, so we push null instead.
128 mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
129 // Only pass the this object if it has been initialized by the time the hook is invoked.
130 if (hook.hookType == HookType.AFTER) {
131 mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
132 } else {
133 mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
134 }
135 } else {
136 // Push a MethodHandle representing the hooked method.
137 val handleOpcode = when (opcode) {
138 Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL
139 Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE
140 Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC
141 Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL
142 else -> -1
143 }
144 if (java6Mode) {
145 // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50).
146 mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
147 } else {
148 mv.visitLdcInsn(
149 Handle(
150 handleOpcode,
151 owner,
152 methodName,
153 methodDescriptor,
154 isInterface
155 )
156 ) // push MethodHandle
157 }
158 // Stack layout: ... | MethodHandle (objectref)
159 // Push the owner object again
160 mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
161 }
162 // Stack layout: ... | MethodHandle (objectref) | owner (objectref)
163 // Push a reference to our object array with the saved arguments
164 mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
165 // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref)
166 // Push the hook id
167 mv.visitLdcInsn(hookId)
168 // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
169 // How we proceed depends on the type of hook we want to implement
170 when (hook.hookType) {
171 HookType.BEFORE -> {
172 // Call the hook method
173 mv.visitMethodInsn(
174 Opcodes.INVOKESTATIC,
175 hook.hookInternalClassName,
176 hook.hookMethodName,
177 hook.hookMethodDescriptor,
178 false
179 )
180
181 // Call the original method if this is the last BEFORE hook. If not, the original method will be
182 // called by the next AFTER hook.
183 if (index == matchingHooks.lastIndex) {
184 // Stack layout: ...
185 // Push the values for the original method call onto the stack again
186 if (opcode != Opcodes.INVOKESTATIC) {
187 mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
188 }
189 loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
190 // Stack layout: ... | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
191 mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
192 }
193 }
194 HookType.REPLACE -> {
195 // Call the hook method
196 mv.visitMethodInsn(
197 Opcodes.INVOKESTATIC,
198 hook.hookInternalClassName,
199 hook.hookMethodName,
200 hook.hookMethodDescriptor,
201 false
202 )
203 // Stack layout: ... | [return value (primitive/objectref)]
204 // Check if we need to process the return value
205 if (returnTypeDescriptor != "V") {
206 val hookMethodReturnType = extractReturnTypeDescriptor(hook.hookMethodDescriptor)
207 // if the hook method's return type is primitive we don't need to unwrap or cast it
208 if (!isPrimitiveType(hookMethodReturnType)) {
209 // Check if the returned object type is different than the one that should be returned
210 // If a primitive should be returned we check it's wrapper type
211 val expectedType = getWrapperTypeDescriptor(returnTypeDescriptor)
212 if (expectedType != hookMethodReturnType) {
213 // Cast object
214 mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(expectedType))
215 }
216 // Check if we need to unwrap the returned object
217 unwrapTypeIfPrimitive(returnTypeDescriptor)
218 }
219 }
220 }
221 HookType.AFTER -> {
222 // Call the original method before the first AFTER hook
223 if (index == 0 || matchingHooks[index - 1].hookType != HookType.AFTER) {
224 // Push the values for the original method call again onto the stack
225 if (opcode != Opcodes.INVOKESTATIC) {
226 mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
227 }
228 loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
229 // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
230 // | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
231 mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
232 if (returnTypeDescriptor == "V") {
233 // If the method didn't return anything, we push a nullref as placeholder
234 mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
235 }
236 // Wrap return value if it is a primitive type
237 wrapTypeIfPrimitive(returnTypeDescriptor)
238 mv.visitVarInsn(Opcodes.ASTORE, localReturnObj) // consume objectref
239 }
240 mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref
241
242 // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
243 // | return value (objectref)
244 // Store the result value in a local variable (but keep it on the stack)
245 // Call the hook method
246 mv.visitMethodInsn(
247 Opcodes.INVOKESTATIC,
248 hook.hookInternalClassName,
249 hook.hookMethodName,
250 hook.hookMethodDescriptor,
251 false
252 )
253 // Stack layout: ...
254 // Push the return value on the stack after the last AFTER hook if the original method returns a value
255 if (index == matchingHooks.size - 1 && returnTypeDescriptor != "V") {
256 // Push the return value again
257 mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref
258 // Unwrap it, if it was a primitive value
259 unwrapTypeIfPrimitive(returnTypeDescriptor)
260 // Stack layout: ... | return value (primitive/objectref)
261 }
262 }
263 }
264 }
265 }
266
isMethodInvocationOpnull267 private fun isMethodInvocationOp(opcode: Int) = opcode in listOf(
268 Opcodes.INVOKEVIRTUAL,
269 Opcodes.INVOKEINTERFACE,
270 Opcodes.INVOKESTATIC,
271 Opcodes.INVOKESPECIAL
272 )
273
274 private fun findMatchingHooks(owner: String, name: String, descriptor: String): List<Hook> {
275 val result = HookType.values().flatMap { hookType ->
276 val withoutDescriptorKey = "$hookType#$owner#$name"
277 val withDescriptorKey = "$withoutDescriptorKey#$descriptor"
278 hooks[withDescriptorKey].orEmpty() + hooks[withoutDescriptorKey].orEmpty()
279 }.sortedBy { it.hookType }
280 val replaceHookCount = result.count { it.hookType == HookType.REPLACE }
281 check(
282 replaceHookCount == 0 ||
283 (replaceHookCount == 1 && result.size == 1)
284 ) {
285 "For a given method, You can either have a single REPLACE hook or BEFORE/AFTER hooks. Found:\n $result"
286 }
287
288 return result
289 .filter { !isReplaceHookInJava6mode(it) }
290 .sortedByDescending { it.toString() }
291 }
292
isReplaceHookInJava6modenull293 private fun isReplaceHookInJava6mode(hook: Hook): Boolean {
294 if (java6Mode && hook.hookType == HookType.REPLACE) {
295 if (showUnsupportedHookWarning.getAndSet(false)) {
296 println(
297 """WARN: Some hooks could not be applied to class files built for Java 7 or lower.
298 |WARN: Ensure that the fuzz target and its dependencies are compiled with
299 |WARN: -target 8 or higher to identify as many bugs as possible.
300 """.trimMargin()
301 )
302 }
303 return true
304 }
305 return false
306 }
307
308 // Stores all arguments for a method call in a local object array.
309 // paramDescriptors: The type descriptors for all method arguments
storeMethodArgumentsnull310 private fun storeMethodArguments(paramDescriptors: List<String>): Int {
311 // Allocate a new Object[] for the methods parameters.
312 mv.visitIntInsn(Opcodes.SIPUSH, paramDescriptors.size)
313 mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object")
314 val localObjArr = lvs.newLocal(Type.getType("[Ljava/lang/Object;"))
315 mv.visitVarInsn(Opcodes.ASTORE, localObjArr)
316
317 // Loop over all arguments in reverse order (because the last argument is on top).
318 for ((argIdx, argDescriptor) in paramDescriptors.withIndex().reversed()) {
319 // If the argument is a primitive type, wrap it in it's wrapper class
320 wrapTypeIfPrimitive(argDescriptor)
321 // Store the argument in our object array, for that we need to shape the stack first.
322 // Stack layout: ... | method argument (objectref)
323 mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
324 // Stack layout: ... | method argument (objectref) | object array (arrayref)
325 mv.visitInsn(Opcodes.SWAP)
326 // Stack layout: ... | object array (arrayref) | method argument (objectref)
327 mv.visitIntInsn(Opcodes.SIPUSH, argIdx)
328 // Stack layout: ... | object array (arrayref) | method argument (objectref) | argument index (int)
329 mv.visitInsn(Opcodes.SWAP)
330 // Stack layout: ... | object array (arrayref) | argument index (int) | method argument (objectref)
331 mv.visitInsn(Opcodes.AASTORE) // consume all three: arrayref, index, value
332 // Stack layout: ...
333 // Continue with the remaining method arguments
334 }
335
336 // Return a reference to the array with the parameters.
337 return localObjArr
338 }
339
340 // Loads all arguments for a method call from a local object array.
341 // argTypeSigs: The type signatures for all method arguments
342 // localObjArr: Index of a local variable containing an object array where the arguments will be loaded from
loadMethodArgumentsnull343 private fun loadMethodArguments(paramDescriptors: List<String>, localObjArr: Int) {
344 // Loop over all arguments
345 for ((argIdx, argDescriptor) in paramDescriptors.withIndex()) {
346 // Push a reference to the object array on the stack
347 mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
348 // Stack layout: ... | object array (arrayref)
349 // Push the index of the current argument on the stack
350 mv.visitIntInsn(Opcodes.SIPUSH, argIdx)
351 // Stack layout: ... | object array (arrayref) | argument index (int)
352 // Load the argument from the array
353 mv.visitInsn(Opcodes.AALOAD)
354 // Stack layout: ... | method argument (objectref)
355 // Cast object to it's original type (or it's wrapper object)
356 val wrapperTypeDescriptor = getWrapperTypeDescriptor(argDescriptor)
357 mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(wrapperTypeDescriptor))
358 // If the argument is a supposed to be a primitive type, unwrap the wrapped type
359 unwrapTypeIfPrimitive(argDescriptor)
360 // Stack layout: ... | method argument (primitive/objectref)
361 // Continue with the remaining method arguments
362 }
363 }
364
365 // Removes a primitive value from the top of the operand stack
366 // and pushes it enclosed in its wrapper type (e.g. removes int, pushes Integer).
367 // This is done by calling .valueOf(...) on the wrapper class.
wrapTypeIfPrimitivenull368 private fun wrapTypeIfPrimitive(unwrappedTypeDescriptor: String) {
369 if (!isPrimitiveType(unwrappedTypeDescriptor) || unwrappedTypeDescriptor == "V") return
370 val wrapperTypeDescriptor = getWrapperTypeDescriptor(unwrappedTypeDescriptor)
371 val wrapperType = extractInternalClassName(wrapperTypeDescriptor)
372 val valueOfDescriptor = "($unwrappedTypeDescriptor)$wrapperTypeDescriptor"
373 mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapperType, "valueOf", valueOfDescriptor, false)
374 }
375
376 // Removes a wrapper object around a given primitive type from the top of the operand stack
377 // and pushes the primitive value it contains (e.g. removes Integer, pushes int).
378 // This is done by calling .intValue(...) / .charValue(...) / ... on the wrapper object.
unwrapTypeIfPrimitivenull379 private fun unwrapTypeIfPrimitive(primitiveTypeDescriptor: String) {
380 val (methodName, wrappedTypeDescriptor) = when (primitiveTypeDescriptor) {
381 "B" -> Pair("byteValue", "java/lang/Byte")
382 "C" -> Pair("charValue", "java/lang/Character")
383 "D" -> Pair("doubleValue", "java/lang/Double")
384 "F" -> Pair("floatValue", "java/lang/Float")
385 "I" -> Pair("intValue", "java/lang/Integer")
386 "J" -> Pair("longValue", "java/lang/Long")
387 "S" -> Pair("shortValue", "java/lang/Short")
388 "Z" -> Pair("booleanValue", "java/lang/Boolean")
389 else -> return
390 }
391 mv.visitMethodInsn(
392 Opcodes.INVOKEVIRTUAL,
393 wrappedTypeDescriptor,
394 methodName,
395 "()$primitiveTypeDescriptor",
396 false
397 )
398 }
399 }
400