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