• 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 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