1 // 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.runtime;
16 
17 import com.github.fmeum.rules_jni.RulesJni;
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.Executable;
20 import java.lang.reflect.Method;
21 import java.nio.charset.Charset;
22 import java.util.Arrays;
23 import org.objectweb.asm.Type;
24 
25 @SuppressWarnings("unused")
26 final public class TraceDataFlowNativeCallbacks {
27   // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently
28   // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is more
29   // likely to insert literal null bytes, having both the fuzzer input and the reported string
30   // comparisons be CESU8 should perform even better than the current implementation using modified
31   // UTF-8.
32   private static final Charset FUZZED_DATA_CHARSET = Charset.forName("CESU8");
33 
34   static {
35     RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver");
36   }
37 
38   // It is possible for RulesJni#loadLibrary to trigger a hook even though it isn't instrumented if
39   // it uses regexes, which it does with at least some JDKs due to its use of String#format. This
40   // led to exceptions in the past when the hook ended up calling traceStrcmp or traceStrstr before
41   // the static initializer was run: FUZZED_DATA_CHARSET used to be initialized after the call and
42   // thus still had the value null when encodeForLibFuzzer was called, resulting in an NPE in
43   // String#getBytes(Charset). Just switching the order may actually make this bug worse: It could
44   // now lead to traceMemcmp being called before the native library has been loaded. We guard
45   // against this by making the hooks noops when static initialization of this class hasn't
46   // completed yet.
47   private static final boolean NATIVE_INITIALIZED = true;
48 
traceMemcmp(byte[] b1, byte[] b2, int result, int pc)49   public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc);
50 
traceStrcmp(String s1, String s2, int result, int pc)51   public static void traceStrcmp(String s1, String s2, int result, int pc) {
52     if (NATIVE_INITIALIZED) {
53       traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc);
54     }
55   }
56 
traceStrstr(String s1, String s2, int pc)57   public static void traceStrstr(String s1, String s2, int pc) {
58     if (NATIVE_INITIALIZED) {
59       traceStrstr0(encodeForLibFuzzer(s2), pc);
60     }
61   }
62 
traceReflectiveCall(Executable callee, int pc)63   public static void traceReflectiveCall(Executable callee, int pc) {
64     String className = callee.getDeclaringClass().getCanonicalName();
65     String executableName = callee.getName();
66     String descriptor;
67     if (callee instanceof Method) {
68       descriptor = Type.getMethodDescriptor((Method) callee);
69     } else {
70       descriptor = Type.getConstructorDescriptor((Constructor<?>) callee);
71     }
72     tracePcIndir(Arrays.hashCode(new String[] {className, executableName, descriptor}), pc);
73   }
74 
traceCmpLongWrapper(long arg1, long arg2, int pc)75   public static int traceCmpLongWrapper(long arg1, long arg2, int pc) {
76     traceCmpLong(arg1, arg2, pc);
77     // Long.compare serves as a substitute for the lcmp opcode, which can't be used directly
78     // as the stack layout required for the call can't be achieved without local variables.
79     return Long.compare(arg1, arg2);
80   }
81 
82   // The caller has to ensure that arg1 and arg2 have the same class.
traceGenericCmp(Object arg1, Object arg2, int pc)83   public static void traceGenericCmp(Object arg1, Object arg2, int pc) {
84     if (arg1 instanceof CharSequence) {
85       traceStrcmp(arg1.toString(), arg2.toString(), 1, pc);
86     } else if (arg1 instanceof Integer) {
87       traceCmpInt((int) arg1, (int) arg2, pc);
88     } else if (arg1 instanceof Long) {
89       traceCmpLong((long) arg1, (long) arg2, pc);
90     } else if (arg1 instanceof Short) {
91       traceCmpInt((short) arg1, (short) arg2, pc);
92     } else if (arg1 instanceof Byte) {
93       traceCmpInt((byte) arg1, (byte) arg2, pc);
94     } else if (arg1 instanceof Character) {
95       traceCmpInt((char) arg1, (char) arg2, pc);
96     } else if (arg1 instanceof Number) {
97       traceCmpLong(((Number) arg1).longValue(), ((Number) arg2).longValue(), pc);
98     } else if (arg1 instanceof byte[]) {
99       traceMemcmp((byte[]) arg1, (byte[]) arg2, 1, pc);
100     }
101   }
102 
103   /* trace-cmp */
traceCmpInt(int arg1, int arg2, int pc)104   public static native void traceCmpInt(int arg1, int arg2, int pc);
traceConstCmpInt(int arg1, int arg2, int pc)105   public static native void traceConstCmpInt(int arg1, int arg2, int pc);
traceCmpLong(long arg1, long arg2, int pc)106   public static native void traceCmpLong(long arg1, long arg2, int pc);
traceSwitch(long val, long[] cases, int pc)107   public static native void traceSwitch(long val, long[] cases, int pc);
108   /* trace-div */
traceDivInt(int val, int pc)109   public static native void traceDivInt(int val, int pc);
traceDivLong(long val, int pc)110   public static native void traceDivLong(long val, int pc);
111   /* trace-gep */
traceGep(long val, int pc)112   public static native void traceGep(long val, int pc);
113   /* indirect-calls */
tracePcIndir(int callee, int caller)114   public static native void tracePcIndir(int callee, int caller);
115 
handleLibraryLoad()116   public static native void handleLibraryLoad();
117 
encodeForLibFuzzer(String str)118   private static byte[] encodeForLibFuzzer(String str) {
119     // libFuzzer string hooks only ever consume the first 64 bytes, so we can definitely cut the
120     // string off after 64 characters.
121     return str.substring(0, Math.min(str.length(), 64)).getBytes(FUZZED_DATA_CHARSET);
122   }
123 
traceStrstr0(byte[] needle, int pc)124   private static native void traceStrstr0(byte[] needle, int pc);
125 }
126