blob: 37e8eaeba7a0824aba6b2a68b457e5af0401bf42 [file] [log] [blame]
// 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.runtime;
import com.code_intelligence.jazzer.api.HookType;
import com.code_intelligence.jazzer.api.MethodHook;
import java.lang.invoke.MethodHandle;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.TreeMap;
@SuppressWarnings("unused")
final public class TraceCmpHooks {
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte", targetMethod = "compare",
targetMethodDescriptor = "(BB)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte",
targetMethod = "compareUnsigned", targetMethodDescriptor = "(BB)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short", targetMethod = "compare",
targetMethodDescriptor = "(SS)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short",
targetMethod = "compareUnsigned", targetMethodDescriptor = "(SS)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
targetMethod = "compare", targetMethodDescriptor = "(II)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
targetMethod = "compareUnsigned", targetMethodDescriptor = "(II)I")
public static void
integerCompare(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
TraceDataFlowNativeCallbacks.traceCmpInt((int) arguments[0], (int) arguments[1], hookId);
}
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte",
targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Byte;)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short",
targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Short;)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Integer;)I")
public static void
integerCompareTo(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
TraceDataFlowNativeCallbacks.traceCmpInt((int) thisObject, (int) arguments[0], hookId);
}
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", targetMethod = "compare",
targetMethodDescriptor = "(JJ)I")
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
targetMethod = "compareUnsigned", targetMethodDescriptor = "(JJ)I")
public static void
longCompare(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
TraceDataFlowNativeCallbacks.traceCmpLong((long) arguments[0], (long) arguments[1], hookId);
}
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Long;)I")
public static void
longCompareTo(MethodHandle method, Long thisObject, Object[] arguments, int hookId) {
TraceDataFlowNativeCallbacks.traceCmpLong(thisObject, (long) arguments[0], hookId);
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
targetMethod = "equalsIgnoreCase")
public static void
equals(
MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (arguments[0] instanceof String && !returnValue) {
// The precise value of the result of the comparison is not used by libFuzzer as long as it is
// non-zero.
TraceDataFlowNativeCallbacks.traceStrcmp(thisObject, (String) arguments[0], 1, hookId);
}
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Object", targetMethod = "equals")
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.CharSequence", targetMethod = "equals")
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Number", targetMethod = "equals")
public static void
genericEquals(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (!returnValue && arguments[0] != null && thisObject.getClass() == arguments[0].getClass()) {
TraceDataFlowNativeCallbacks.traceGenericCmp(thisObject, arguments[0], hookId);
}
}
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "compareTo")
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
targetMethod = "compareToIgnoreCase")
public static void
compareTo(
MethodHandle method, String thisObject, Object[] arguments, int hookId, Integer returnValue) {
if (arguments[0] instanceof String && returnValue != 0) {
TraceDataFlowNativeCallbacks.traceStrcmp(
thisObject, (String) arguments[0], returnValue, hookId);
}
}
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contentEquals")
public static void
contentEquals(
MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (arguments[0] instanceof CharSequence && !returnValue) {
TraceDataFlowNativeCallbacks.traceStrcmp(
thisObject, ((CharSequence) arguments[0]).toString(), 1, hookId);
}
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
targetMethod = "regionMatches", targetMethodDescriptor = "(ZILjava/lang/String;II)Z")
public static void
regionsMatches5(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (!returnValue) {
int toffset = (int) arguments[1];
String other = (String) arguments[2];
int ooffset = (int) arguments[3];
int len = (int) arguments[4];
regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId);
}
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
targetMethod = "regionMatches", targetMethodDescriptor = "(ILjava/lang/String;II)Z")
public static void
regionMatches4(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (!returnValue) {
int toffset = (int) arguments[0];
String other = (String) arguments[1];
int ooffset = (int) arguments[2];
int len = (int) arguments[3];
regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId);
}
}
private static void regionMatchesInternal(
String thisString, int toffset, String other, int ooffset, int len, int hookId) {
if (toffset < 0 || ooffset < 0)
return;
int cappedThisStringEnd = Math.min(toffset + len, thisString.length());
int cappedOtherStringEnd = Math.min(ooffset + len, other.length());
String thisPart = thisString.substring(toffset, cappedThisStringEnd);
String otherPart = other.substring(ooffset, cappedOtherStringEnd);
TraceDataFlowNativeCallbacks.traceStrcmp(thisPart, otherPart, 1, hookId);
}
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contains")
public static void
contains(
MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (arguments[0] instanceof CharSequence && !returnValue) {
TraceDataFlowNativeCallbacks.traceStrstr(
thisObject, ((CharSequence) arguments[0]).toString(), hookId);
}
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "indexOf")
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "lastIndexOf")
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.StringBuffer", targetMethod = "indexOf")
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuffer",
targetMethod = "lastIndexOf")
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", targetMethod = "indexOf")
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuilder",
targetMethod = "lastIndexOf")
public static void
indexOf(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
if (arguments[0] instanceof String && returnValue == -1) {
TraceDataFlowNativeCallbacks.traceStrstr(
thisObject.toString(), (String) arguments[0], hookId);
}
}
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "startsWith")
@MethodHook(
type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "endsWith")
public static void
startsWith(
MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (!returnValue) {
TraceDataFlowNativeCallbacks.traceStrstr(thisObject, (String) arguments[0], hookId);
}
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "replace",
targetMethodDescriptor =
"(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;")
public static void
replace(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) {
String original = (String) thisObject;
// Report only if the replacement was not successful.
if (original.equals(returnValue)) {
String target = arguments[0].toString();
TraceDataFlowNativeCallbacks.traceStrstr(original, target, hookId);
}
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals",
targetMethodDescriptor = "([B[B)Z")
public static void
arraysEquals(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (returnValue)
return;
byte[] first = (byte[]) arguments[0];
byte[] second = (byte[]) arguments[1];
TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId);
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals",
targetMethodDescriptor = "([BII[BII)Z")
public static void
arraysEqualsRange(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
if (returnValue)
return;
byte[] first =
Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]);
byte[] second =
Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]);
TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId);
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare",
targetMethodDescriptor = "([B[B)I")
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays",
targetMethod = "compareUnsigned", targetMethodDescriptor = "([B[B)I")
public static void
arraysCompare(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
if (returnValue == 0)
return;
byte[] first = (byte[]) arguments[0];
byte[] second = (byte[]) arguments[1];
TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId);
}
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare",
targetMethodDescriptor = "([BII[BII)I")
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays",
targetMethod = "compareUnsigned", targetMethodDescriptor = "([BII[BII)I")
public static void
arraysCompareRange(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
if (returnValue == 0)
return;
byte[] first =
Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]);
byte[] second =
Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]);
TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId);
}
// The maximal number of elements of a non-TreeMap Map that will be sorted and searched for the
// key closest to the current lookup key in the mapGet hook.
private static final int MAX_NUM_KEYS_TO_ENUMERATE = 100;
@SuppressWarnings({"rawtypes", "unchecked"})
@MethodHook(type = HookType.AFTER, targetClassName = "java.util.Map", targetMethod = "get")
public static void mapGet(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
if (returnValue != null)
return;
if (thisObject == null)
return;
final Map map = (Map) thisObject;
if (map.size() == 0)
return;
final Object currentKey = arguments[0];
if (currentKey == null)
return;
// Find two valid map keys that bracket currentKey.
// This is a generalization of libFuzzer's __sanitizer_cov_trace_switch:
// https://github.com/llvm/llvm-project/blob/318942de229beb3b2587df09e776a50327b5cef0/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L564
Object lowerBoundKey = null;
Object upperBoundKey = null;
try {
if (map instanceof TreeMap) {
final TreeMap treeMap = (TreeMap) map;
try {
lowerBoundKey = treeMap.floorKey(currentKey);
upperBoundKey = treeMap.ceilingKey(currentKey);
} catch (ClassCastException ignored) {
// Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be
// compared to the maps keys.
}
} else if (currentKey instanceof Comparable) {
final Comparable comparableCurrentKey = (Comparable) currentKey;
// Find two keys that bracket currentKey.
// Note: This is not deterministic if map.size() > MAX_NUM_KEYS_TO_ENUMERATE.
int enumeratedKeys = 0;
for (Object validKey : map.keySet()) {
if (!(validKey instanceof Comparable))
continue;
final Comparable comparableValidKey = (Comparable) validKey;
// If the key sorts lower than the non-existing key, but higher than the current lower
// bound, update the lower bound and vice versa for the upper bound.
try {
if (comparableValidKey.compareTo(comparableCurrentKey) < 0
&& (lowerBoundKey == null || comparableValidKey.compareTo(lowerBoundKey) > 0)) {
lowerBoundKey = validKey;
}
if (comparableValidKey.compareTo(comparableCurrentKey) > 0
&& (upperBoundKey == null || comparableValidKey.compareTo(upperBoundKey) < 0)) {
upperBoundKey = validKey;
}
} catch (ClassCastException ignored) {
// Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be
// compared to the maps keys.
}
if (enumeratedKeys++ > MAX_NUM_KEYS_TO_ENUMERATE)
break;
}
}
} catch (ConcurrentModificationException ignored) {
// map was modified by another thread, skip this invocation
return;
}
// Modify the hook ID so that compares against distinct valid keys are traced separately.
if (lowerBoundKey != null) {
TraceDataFlowNativeCallbacks.traceGenericCmp(
currentKey, lowerBoundKey, hookId + lowerBoundKey.hashCode());
}
if (upperBoundKey != null) {
TraceDataFlowNativeCallbacks.traceGenericCmp(
currentKey, upperBoundKey, hookId + upperBoundKey.hashCode());
}
}
}