-
Notifications
You must be signed in to change notification settings - Fork 167
Expand file tree
/
Copy pathTraceDataFlowNativeCallbacks.java
More file actions
157 lines (133 loc) · 6.58 KB
/
TraceDataFlowNativeCallbacks.java
File metadata and controls
157 lines (133 loc) · 6.58 KB
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
* Copyright 2024 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.github.fmeum.rules_jni.RulesJni;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.objectweb.asm.Type;
@SuppressWarnings("unused")
public final class TraceDataFlowNativeCallbacks {
// Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently
// converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is more
// likely to insert literal null bytes, having both the fuzzer input and the reported string
// comparisons be CESU8 should perform even better than the current implementation using modified
// UTF-8.
private static final Charset FUZZED_DATA_CHARSET = Charset.forName("CESU8");
static {
RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver");
}
// It is possible for RulesJni#loadLibrary to trigger a hook even though it isn't instrumented if
// it uses regexes, which it does with at least some JDKs due to its use of String#format. This
// led to exceptions in the past when the hook ended up calling traceStrcmp or traceStrstr before
// the static initializer was run: FUZZED_DATA_CHARSET used to be initialized after the call and
// thus still had the value null when encodeForLibFuzzer was called, resulting in an NPE in
// String#getBytes(Charset). Just switching the order may actually make this bug worse: It could
// now lead to traceMemcmp being called before the native library has been loaded. We guard
// against this by making the hooks noops when static initialization of this class hasn't
// completed yet.
private static final boolean NATIVE_INITIALIZED = true;
public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc);
public static void traceStrcmp(String s1, String s2, int result, int pc) {
if (NATIVE_INITIALIZED) {
traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc);
}
}
public static void traceStrstr(String s1, String s2, int pc) {
if (NATIVE_INITIALIZED) {
traceStrstr0(encodeForLibFuzzer(s2), pc);
}
}
public static void traceReflectiveCall(Executable callee, int pc) {
String className = callee.getDeclaringClass().getCanonicalName();
String executableName = callee.getName();
String descriptor;
if (callee instanceof Method) {
descriptor = Type.getMethodDescriptor((Method) callee);
} else {
descriptor = Type.getConstructorDescriptor((Constructor<?>) callee);
}
tracePcIndir(Arrays.hashCode(new String[] {className, executableName, descriptor}), pc);
}
public static int traceCmpLongWrapper(long arg1, long arg2, int pc) {
traceCmpLong(arg1, arg2, pc);
// Long.compare serves as a substitute for the lcmp opcode, which can't be used directly
// as the stack layout required for the call can't be achieved without local variables.
return Long.compare(arg1, arg2);
}
public static int traceCmpDoubleWrapper(double arg1, double arg2, int nanResult, int pc) {
traceCmpLong(Double.doubleToRawLongBits(arg1), Double.doubleToRawLongBits(arg2), pc);
if (Double.isNaN(arg1) || Double.isNaN(arg2)) return nanResult;
// Mirror DCMPG/DCMPL semantics: in particular, -0.0 == +0.0 must yield 0.
if (arg1 > arg2) return 1;
if (arg1 == arg2) return 0;
return -1;
}
public static int traceCmpFloatWrapper(float arg1, float arg2, int nanResult, int pc) {
traceCmpInt(Float.floatToRawIntBits(arg1), Float.floatToRawIntBits(arg2), pc);
if (Float.isNaN(arg1) || Float.isNaN(arg2)) return nanResult;
// Mirror FCMPG/FCMPL semantics: in particular, -0.0 == +0.0 must yield 0.
if (arg1 > arg2) return 1;
if (arg1 == arg2) return 0;
return -1;
}
// The caller has to ensure that arg1 and arg2 have the same class.
public static void traceGenericCmp(Object arg1, Object arg2, int pc) {
if (arg1 instanceof CharSequence) {
traceStrcmp(arg1.toString(), arg2.toString(), 1, pc);
} else if (arg1 instanceof Integer) {
traceCmpInt((int) arg1, (int) arg2, pc);
} else if (arg1 instanceof Long) {
traceCmpLong((long) arg1, (long) arg2, pc);
} else if (arg1 instanceof Float) {
traceCmpInt(Float.floatToRawIntBits((float) arg1), Float.floatToRawIntBits((float) arg2), pc);
} else if (arg1 instanceof Double) {
traceCmpLong(
Double.doubleToRawLongBits((double) arg1), Double.doubleToRawLongBits((double) arg2), pc);
} else if (arg1 instanceof Short) {
traceCmpInt((short) arg1, (short) arg2, pc);
} else if (arg1 instanceof Byte) {
traceCmpInt((byte) arg1, (byte) arg2, pc);
} else if (arg1 instanceof Character) {
traceCmpInt((char) arg1, (char) arg2, pc);
} else if (arg1 instanceof Number) {
traceCmpLong(((Number) arg1).longValue(), ((Number) arg2).longValue(), pc);
} else if (arg1 instanceof byte[]) {
traceMemcmp((byte[]) arg1, (byte[]) arg2, 1, pc);
}
}
/* trace-cmp */
public static native void traceCmpInt(int arg1, int arg2, int pc);
public static native void traceConstCmpInt(int arg1, int arg2, int pc);
public static native void traceCmpLong(long arg1, long arg2, int pc);
public static native void traceSwitch(long val, long[] cases, int pc);
/* trace-div */
public static native void traceDivInt(int val, int pc);
public static native void traceDivLong(long val, int pc);
/* trace-gep */
public static native void traceGep(long val, int pc);
/* indirect-calls */
public static native void tracePcIndir(int callee, int caller);
public static native void handleLibraryLoad();
private static byte[] encodeForLibFuzzer(String str) {
// libFuzzer string hooks only ever consume the first 64 bytes, so we can definitely cut the
// string off after 64 characters.
return str.substring(0, Math.min(str.length(), 64)).getBytes(FUZZED_DATA_CHARSET);
}
private static native void traceStrstr0(byte[] needle, int pc);
}