aboutsummaryrefslogtreecommitdiff
path: root/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt
diff options
context:
space:
mode:
Diffstat (limited to 'agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt')
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt371
1 files changed, 192 insertions, 179 deletions
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt
index 7c23c703..1694be58 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt
@@ -15,11 +15,12 @@
package com.code_intelligence.jazzer.instrumentor
import com.code_intelligence.jazzer.api.HookType
-import com.code_intelligence.jazzer.third_party.objectweb.asm.Handle
-import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor
-import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes
-import com.code_intelligence.jazzer.third_party.objectweb.asm.Type
-import com.code_intelligence.jazzer.third_party.objectweb.asm.commons.LocalVariablesSorter
+import org.objectweb.asm.Handle
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Type
+import org.objectweb.asm.commons.LocalVariablesSorter
+import java.util.concurrent.atomic.AtomicBoolean
internal fun makeHookMethodVisitor(
access: Int,
@@ -41,6 +42,10 @@ private class HookMethodVisitor(
private val random: DeterministicRandom,
) : MethodVisitor(Instrumentor.ASM_API_VERSION, methodVisitor) {
+ companion object {
+ private val showUnsupportedHookWarning = AtomicBoolean(true)
+ }
+
val lvs = object : LocalVariablesSorter(Instrumentor.ASM_API_VERSION, access, descriptor, this) {
override fun updateNewLocals(newLocals: Array<Any>) {
// The local variables involved in calling hooks do not need to outlive the current
@@ -51,7 +56,7 @@ private class HookMethodVisitor(
}
}
- private val hooks = hooks.associateBy { hook ->
+ private val hooks = hooks.groupBy { hook ->
var hookKey = "${hook.hookType}#${hook.targetInternalClassName}#${hook.targetMethodName}"
if (hook.targetMethodDescriptor != null)
hookKey += "#${hook.targetMethodDescriptor}"
@@ -69,63 +74,23 @@ private class HookMethodVisitor(
mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
return
}
- handleMethodInsn(HookType.BEFORE, opcode, owner, methodName, methodDescriptor, isInterface)
- }
-
- /**
- * Emits the bytecode for a method call instruction for the next applicable hook type in order (BEFORE, REPLACE,
- * AFTER). Since the instrumented code is indistinguishable from an uninstrumented call instruction, it can be
- * safely nested. Combining REPLACE hooks with other hooks is however not supported as these hooks already subsume
- * the functionality of BEFORE and AFTER hooks.
- */
- private fun visitNextHookTypeOrCall(
- hookType: HookType,
- appliedHook: Boolean,
- opcode: Int,
- owner: String,
- methodName: String,
- methodDescriptor: String,
- isInterface: Boolean,
- ) = when (hookType) {
- HookType.BEFORE -> {
- val nextHookType = if (appliedHook) {
- // After a BEFORE hook has been applied, we can safely apply an AFTER hook by replacing the actual
- // call instruction with the full bytecode injected for the AFTER hook.
- HookType.AFTER
- } else {
- // If no BEFORE hook is registered, look for a REPLACE hook next.
- HookType.REPLACE
- }
- handleMethodInsn(nextHookType, opcode, owner, methodName, methodDescriptor, isInterface)
- }
- HookType.REPLACE -> {
- // REPLACE hooks can't (and don't need to) be mixed with other hooks. We only cycle through them if we
- // couldn't find a matching REPLACE hook, in which case we try an AFTER hook next.
- require(!appliedHook)
- handleMethodInsn(HookType.AFTER, opcode, owner, methodName, methodDescriptor, isInterface)
- }
- // An AFTER hook is always the last in the chain. Whether a hook has been applied or not, always emit the
- // actual call instruction.
- HookType.AFTER -> mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
+ handleMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
}
fun handleMethodInsn(
- hookType: HookType,
opcode: Int,
owner: String,
methodName: String,
methodDescriptor: String,
isInterface: Boolean,
) {
- val hook = findMatchingHook(hookType, owner, methodName, methodDescriptor)
- if (hook == null) {
- visitNextHookTypeOrCall(hookType, false, opcode, owner, methodName, methodDescriptor, isInterface)
+ val matchingHooks = findMatchingHooks(owner, methodName, methodDescriptor)
+
+ if (matchingHooks.isEmpty()) {
+ mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
return
}
- // The hookId is used to identify a call site.
- val hookId = random.nextInt()
-
val paramDescriptors = extractParameterTypeDescriptors(methodDescriptor)
val localObjArr = storeMethodArguments(paramDescriptors)
// If the method we're hooking is not static there is now a reference to
@@ -142,138 +107,158 @@ private class HookMethodVisitor(
// We now removed all values for the original method call from the operand stack
// and saved them to local variables.
- // Start to build the arguments for the hook method.
- if (methodName == "<init>") {
- // Special case for constructors:
- // We cannot create a MethodHandle for a constructor, so we push null instead.
- mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
- // Only pass the this object if it has been initialized by the time the hook is invoked.
- if (hook.hookType == HookType.AFTER) {
- mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
- } else {
- mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
- }
- } else {
- // Push a MethodHandle representing the hooked method.
- val handleOpcode = when (opcode) {
- Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL
- Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE
- Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC
- Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL
- else -> -1
- }
- if (java6Mode) {
- // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50).
+ val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor)
+ // Create a local variable to store the return value
+ val localReturnObj = lvs.newLocal(Type.getType(getWrapperTypeDescriptor(returnTypeDescriptor)))
+
+ matchingHooks.forEachIndexed { index, hook ->
+ // The hookId is used to identify a call site.
+ val hookId = random.nextInt()
+
+ // Start to build the arguments for the hook method.
+ if (methodName == "<init>") {
+ // Constructor is invoked on an uninitialized object, and that's still on the stack.
+ // In case of REPLACE pop it from the stack and replace it afterwards with the returned
+ // one from the hook.
+ if (hook.hookType == HookType.REPLACE) {
+ mv.visitInsn(Opcodes.POP)
+ }
+ // Special case for constructors:
+ // We cannot create a MethodHandle for a constructor, so we push null instead.
mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ // Only pass the this object if it has been initialized by the time the hook is invoked.
+ if (hook.hookType == HookType.AFTER) {
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
+ } else {
+ mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ }
} else {
- mv.visitLdcInsn(
- Handle(
- handleOpcode,
- owner,
- methodName,
- methodDescriptor,
- isInterface
- )
- ) // push MethodHandle
- }
- // Stack layout: ... | MethodHandle (objectref)
- // Push the owner object again
- mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
- }
- // Stack layout: ... | MethodHandle (objectref) | owner (objectref)
- // Push a reference to our object array with the saved arguments
- mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
- // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref)
- // Push the hook id
- mv.visitLdcInsn(hookId)
- // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
- // How we proceed depends on the type of hook we want to implement
- when (hook.hookType) {
- HookType.BEFORE -> {
- // Call the hook method
- mv.visitMethodInsn(
- Opcodes.INVOKESTATIC,
- hook.hookInternalClassName,
- hook.hookMethodName,
- hook.hookMethodDescriptor,
- false
- )
- // Stack layout: ...
- // Push the values for the original method call onto the stack again
- if (opcode != Opcodes.INVOKESTATIC) {
- mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
+ // Push a MethodHandle representing the hooked method.
+ val handleOpcode = when (opcode) {
+ Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL
+ Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE
+ Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC
+ Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL
+ else -> -1
}
- loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
- // Stack layout: ... | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
- // Call the original method or the next hook in order.
- visitNextHookTypeOrCall(hookType, true, opcode, owner, methodName, methodDescriptor, isInterface)
+ if (java6Mode) {
+ // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50).
+ mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ } else {
+ mv.visitLdcInsn(
+ Handle(
+ handleOpcode,
+ owner,
+ methodName,
+ methodDescriptor,
+ isInterface
+ )
+ ) // push MethodHandle
+ }
+ // Stack layout: ... | MethodHandle (objectref)
+ // Push the owner object again
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
}
- HookType.REPLACE -> {
- // Call the hook method
- mv.visitMethodInsn(
- Opcodes.INVOKESTATIC,
- hook.hookInternalClassName,
- hook.hookMethodName,
- hook.hookMethodDescriptor,
- false
- )
- // Stack layout: ... | [return value (primitive/objectref)]
- // Check if we need to process the return value
- val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor)
- if (returnTypeDescriptor != "V") {
- val hookMethodReturnType = extractReturnTypeDescriptor(hook.hookMethodDescriptor)
- // if the hook method's return type is primitive we don't need to unwrap or cast it
- if (!isPrimitiveType(hookMethodReturnType)) {
- // Check if the returned object type is different than the one that should be returned
- // If a primitive should be returned we check it's wrapper type
- val expectedType = getWrapperTypeDescriptor(returnTypeDescriptor)
- if (expectedType != hookMethodReturnType) {
- // Cast object
- mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(expectedType))
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref)
+ // Push a reference to our object array with the saved arguments
+ mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref)
+ // Push the hook id
+ mv.visitLdcInsn(hookId)
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
+ // How we proceed depends on the type of hook we want to implement
+ when (hook.hookType) {
+ HookType.BEFORE -> {
+ // Call the hook method
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ hook.hookInternalClassName,
+ hook.hookMethodName,
+ hook.hookMethodDescriptor,
+ false
+ )
+
+ // Call the original method if this is the last BEFORE hook. If not, the original method will be
+ // called by the next AFTER hook.
+ if (index == matchingHooks.lastIndex) {
+ // Stack layout: ...
+ // Push the values for the original method call onto the stack again
+ if (opcode != Opcodes.INVOKESTATIC) {
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
}
- // Check if we need to unwrap the returned object
- unwrapTypeIfPrimitive(returnTypeDescriptor)
+ loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
+ // Stack layout: ... | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
+ mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
}
}
- }
- HookType.AFTER -> {
- // Push the values for the original method call again onto the stack
- if (opcode != Opcodes.INVOKESTATIC) {
- mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
- }
- loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
- // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
- // | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
- // Call the original method or the next hook in order
- visitNextHookTypeOrCall(hookType, true, opcode, owner, methodName, methodDescriptor, isInterface)
- val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor)
- if (returnTypeDescriptor == "V") {
- // If the method didn't return anything, we push a nullref as placeholder
- mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ HookType.REPLACE -> {
+ // Call the hook method
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ hook.hookInternalClassName,
+ hook.hookMethodName,
+ hook.hookMethodDescriptor,
+ false
+ )
+ // Stack layout: ... | [return value (primitive/objectref)]
+ // Check if we need to process the return value
+ if (returnTypeDescriptor != "V") {
+ val hookMethodReturnType = extractReturnTypeDescriptor(hook.hookMethodDescriptor)
+ // if the hook method's return type is primitive we don't need to unwrap or cast it
+ if (!isPrimitiveType(hookMethodReturnType)) {
+ // Check if the returned object type is different than the one that should be returned
+ // If a primitive should be returned we check it's wrapper type
+ val expectedType = getWrapperTypeDescriptor(returnTypeDescriptor)
+ if (expectedType != hookMethodReturnType) {
+ // Cast object
+ mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(expectedType))
+ }
+ // Check if we need to unwrap the returned object
+ unwrapTypeIfPrimitive(returnTypeDescriptor)
+ }
+ }
}
- // Wrap return value if it is a primitive type
- wrapTypeIfPrimitive(returnTypeDescriptor)
- // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
- // | return value (objectref)
- // Store the result value in a local variable (but keep it on the stack)
- val localReturnObj = lvs.newLocal(Type.getType(getWrapperTypeDescriptor(returnTypeDescriptor)))
- mv.visitVarInsn(Opcodes.ASTORE, localReturnObj) // consume objectref
- mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref
- // Call the hook method
- mv.visitMethodInsn(
- Opcodes.INVOKESTATIC,
- hook.hookInternalClassName,
- hook.hookMethodName,
- hook.hookMethodDescriptor,
- false
- )
- // Stack layout: ...
- if (returnTypeDescriptor != "V") {
- // Push the return value again
+ HookType.AFTER -> {
+ // Call the original method before the first AFTER hook
+ if (index == 0 || matchingHooks[index - 1].hookType != HookType.AFTER) {
+ // Push the values for the original method call again onto the stack
+ if (opcode != Opcodes.INVOKESTATIC) {
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
+ }
+ loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
+ // | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
+ mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
+ if (returnTypeDescriptor == "V") {
+ // If the method didn't return anything, we push a nullref as placeholder
+ mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ }
+ // Wrap return value if it is a primitive type
+ wrapTypeIfPrimitive(returnTypeDescriptor)
+ mv.visitVarInsn(Opcodes.ASTORE, localReturnObj) // consume objectref
+ }
mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref
- // Unwrap it, if it was a primitive value
- unwrapTypeIfPrimitive(returnTypeDescriptor)
- // Stack layout: ... | return value (primitive/objectref)
+
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
+ // | return value (objectref)
+ // Store the result value in a local variable (but keep it on the stack)
+ // Call the hook method
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ hook.hookInternalClassName,
+ hook.hookMethodName,
+ hook.hookMethodDescriptor,
+ false
+ )
+ // Stack layout: ...
+ // Push the return value on the stack after the last AFTER hook if the original method returns a value
+ if (index == matchingHooks.size - 1 && returnTypeDescriptor != "V") {
+ // Push the return value again
+ mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref
+ // Unwrap it, if it was a primitive value
+ unwrapTypeIfPrimitive(returnTypeDescriptor)
+ // Stack layout: ... | return value (primitive/objectref)
+ }
}
}
}
@@ -286,10 +271,38 @@ private class HookMethodVisitor(
Opcodes.INVOKESPECIAL
)
- private fun findMatchingHook(hookType: HookType, owner: String, name: String, descriptor: String): Hook? {
- val withoutDescriptorKey = "$hookType#$owner#$name"
- val withDescriptorKey = "$withoutDescriptorKey#$descriptor"
- return hooks[withDescriptorKey] ?: hooks[withoutDescriptorKey]
+ private fun findMatchingHooks(owner: String, name: String, descriptor: String): List<Hook> {
+ val result = HookType.values().flatMap { hookType ->
+ val withoutDescriptorKey = "$hookType#$owner#$name"
+ val withDescriptorKey = "$withoutDescriptorKey#$descriptor"
+ hooks[withDescriptorKey].orEmpty() + hooks[withoutDescriptorKey].orEmpty()
+ }.sortedBy { it.hookType }
+ val replaceHookCount = result.count { it.hookType == HookType.REPLACE }
+ check(
+ replaceHookCount == 0 ||
+ (replaceHookCount == 1 && result.size == 1)
+ ) {
+ "For a given method, You can either have a single REPLACE hook or BEFORE/AFTER hooks. Found:\n $result"
+ }
+
+ return result
+ .filter { !isReplaceHookInJava6mode(it) }
+ .sortedByDescending { it.toString() }
+ }
+
+ private fun isReplaceHookInJava6mode(hook: Hook): Boolean {
+ if (java6Mode && hook.hookType == HookType.REPLACE) {
+ if (showUnsupportedHookWarning.getAndSet(false)) {
+ println(
+ """WARN: Some hooks could not be applied to class files built for Java 7 or lower.
+ |WARN: Ensure that the fuzz target and its dependencies are compiled with
+ |WARN: -target 8 or higher to identify as many bugs as possible.
+ """.trimMargin()
+ )
+ }
+ return true
+ }
+ return false
}
// Stores all arguments for a method call in a local object array.
@@ -350,7 +363,7 @@ private class HookMethodVisitor(
}
// Removes a primitive value from the top of the operand stack
- // and pushes it enclosed in it's wrapper type (e.g. removes int, pushes Integer).
+ // and pushes it enclosed in its wrapper type (e.g. removes int, pushes Integer).
// This is done by calling .valueOf(...) on the wrapper class.
private fun wrapTypeIfPrimitive(unwrappedTypeDescriptor: String) {
if (!isPrimitiveType(unwrappedTypeDescriptor) || unwrappedTypeDescriptor == "V") return