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.autofuzz; 16 17 import com.code_intelligence.jazzer.api.AutofuzzConstructionException; 18 import com.code_intelligence.jazzer.api.AutofuzzInvocationException; 19 import com.code_intelligence.jazzer.api.FuzzedDataProvider; 20 import com.code_intelligence.jazzer.utils.SimpleGlobMatcher; 21 import com.code_intelligence.jazzer.utils.Utils; 22 import java.io.Closeable; 23 import java.io.UnsupportedEncodingException; 24 import java.lang.reflect.Constructor; 25 import java.lang.reflect.Executable; 26 import java.lang.reflect.Method; 27 import java.net.URLDecoder; 28 import java.util.Arrays; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.Set; 32 import java.util.stream.Collectors; 33 import java.util.stream.Stream; 34 35 public class FuzzTarget { 36 private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100; 37 38 private static String methodReference; 39 private static Executable[] targetExecutables; 40 private static Map<Executable, Class<?>[]> throwsDeclarations; 41 private static Set<SimpleGlobMatcher> ignoredExceptionMatchers; 42 private static long executionsSinceLastInvocation = 0; 43 private static AutofuzzCodegenVisitor codegenVisitor; 44 fuzzerInitialize(String[] args)45 public static void fuzzerInitialize(String[] args) { 46 if (args.length == 0 || !args[0].contains("::")) { 47 System.err.println( 48 "Expected the argument to --autofuzz to be a method reference (e.g. System.out::println)"); 49 System.exit(1); 50 } 51 methodReference = args[0]; 52 String[] parts = methodReference.split("::", 2); 53 String className = parts[0]; 54 String methodNameAndOptionalDescriptor = parts[1]; 55 String methodName; 56 String descriptor; 57 int descriptorStart = methodNameAndOptionalDescriptor.indexOf('('); 58 if (descriptorStart != -1) { 59 methodName = methodNameAndOptionalDescriptor.substring(0, descriptorStart); 60 // URL decode the descriptor to allow copy-pasting from javadoc links such as: 61 // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#valueOf(char%5B%5D) 62 try { 63 descriptor = 64 URLDecoder.decode(methodNameAndOptionalDescriptor.substring(descriptorStart), "UTF-8"); 65 } catch (UnsupportedEncodingException e) { 66 // UTF-8 is always supported. 67 e.printStackTrace(); 68 System.exit(1); 69 return; 70 } 71 } else { 72 methodName = methodNameAndOptionalDescriptor; 73 descriptor = null; 74 } 75 76 Class<?> targetClass; 77 try { 78 // Explicitly invoking static initializers to trigger some coverage in the code. 79 targetClass = Class.forName(className, true, ClassLoader.getSystemClassLoader()); 80 } catch (ClassNotFoundException e) { 81 System.err.printf( 82 "Failed to find class %s for autofuzz, please ensure it is contained in the classpath " 83 + "specified with --cp and specify the full package name%n", 84 className); 85 e.printStackTrace(); 86 System.exit(1); 87 return; 88 } 89 90 boolean isConstructor = methodName.equals("new"); 91 if (isConstructor) { 92 targetExecutables = 93 Arrays.stream(targetClass.getConstructors()) 94 .filter(constructor 95 -> descriptor == null 96 || Utils.getReadableDescriptor(constructor).equals(descriptor)) 97 .toArray(Executable[] ::new); 98 } else { 99 targetExecutables = 100 Arrays.stream(targetClass.getMethods()) 101 .filter(method 102 -> method.getName().equals(methodName) 103 && (descriptor == null 104 || Utils.getReadableDescriptor(method).equals(descriptor))) 105 .toArray(Executable[] ::new); 106 } 107 if (targetExecutables.length == 0) { 108 if (isConstructor) { 109 if (descriptor == null) { 110 System.err.printf( 111 "Failed to find accessible constructors in class %s for autofuzz.%n", className); 112 } else { 113 System.err.printf( 114 "Failed to find accessible constructors with signature %s in class %s for autofuzz.%n" 115 + "Accessible constructors:%n%s", 116 descriptor, className, 117 Arrays.stream(targetClass.getConstructors()) 118 .map(method 119 -> String.format("%s::new%s", method.getDeclaringClass().getName(), 120 Utils.getReadableDescriptor(method))) 121 .distinct() 122 .collect(Collectors.joining(System.lineSeparator()))); 123 } 124 } else { 125 if (descriptor == null) { 126 System.err.printf("Failed to find accessible methods named %s in class %s for autofuzz.%n" 127 + "Accessible methods:%n%s", 128 methodName, className, 129 Arrays.stream(targetClass.getMethods()) 130 .map(method 131 -> String.format( 132 "%s::%s", method.getDeclaringClass().getName(), method.getName())) 133 .distinct() 134 .collect(Collectors.joining(System.lineSeparator()))); 135 } else { 136 System.err.printf( 137 "Failed to find accessible methods named %s with signature %s in class %s for autofuzz.%n" 138 + "Accessible methods with that name:%n%s", 139 methodName, descriptor, className, 140 Arrays.stream(targetClass.getMethods()) 141 .filter(method -> method.getName().equals(methodName)) 142 .map(method 143 -> String.format("%s::%s%s", method.getDeclaringClass().getName(), 144 method.getName(), Utils.getReadableDescriptor(method))) 145 .distinct() 146 .collect(Collectors.joining(System.lineSeparator()))); 147 } 148 } 149 System.exit(1); 150 } 151 152 ignoredExceptionMatchers = Arrays.stream(args) 153 .skip(1) 154 .filter(s -> s.contains("*")) 155 .map(SimpleGlobMatcher::new) 156 .collect(Collectors.toSet()); 157 158 List<Class<?>> alwaysIgnore = 159 Arrays.stream(args) 160 .skip(1) 161 .filter(s -> !s.contains("*")) 162 .map(name -> { 163 try { 164 return ClassLoader.getSystemClassLoader().loadClass(name); 165 } catch (ClassNotFoundException e) { 166 System.err.printf("Failed to find class '%s' specified in --autofuzz_ignore", name); 167 System.exit(1); 168 } 169 throw new Error("Not reached"); 170 }) 171 .collect(Collectors.toList()); 172 throwsDeclarations = 173 Arrays.stream(targetExecutables) 174 .collect(Collectors.toMap(method 175 -> method, 176 method 177 -> Stream.concat(Arrays.stream(method.getExceptionTypes()), alwaysIgnore.stream()) 178 .toArray(Class[] ::new))); 179 } 180 fuzzerTestOneInput(FuzzedDataProvider data)181 public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Throwable { 182 if (Meta.isDebug()) { 183 codegenVisitor = new AutofuzzCodegenVisitor(); 184 } 185 Executable targetExecutable; 186 if (FuzzTarget.targetExecutables.length == 1) { 187 targetExecutable = FuzzTarget.targetExecutables[0]; 188 } else { 189 targetExecutable = data.pickValue(FuzzTarget.targetExecutables); 190 } 191 Object returnValue = null; 192 try { 193 if (targetExecutable instanceof Method) { 194 returnValue = Meta.autofuzz(data, (Method) targetExecutable, codegenVisitor); 195 } else { 196 returnValue = Meta.autofuzz(data, (Constructor<?>) targetExecutable, codegenVisitor); 197 } 198 executionsSinceLastInvocation = 0; 199 if (codegenVisitor != null) { 200 System.err.println(codegenVisitor.generate()); 201 } 202 } catch (AutofuzzConstructionException e) { 203 if (Meta.isDebug()) { 204 e.printStackTrace(); 205 } 206 // Ignore exceptions thrown while constructing the parameters for the target method. We can 207 // only guess how to generate valid parameters and any exceptions thrown while doing so 208 // are most likely on us. However, if this happens too often, Autofuzz got stuck and we should 209 // let the user know. 210 executionsSinceLastInvocation++; 211 if (executionsSinceLastInvocation >= MAX_EXECUTIONS_WITHOUT_INVOCATION) { 212 System.err.printf("Failed to generate valid arguments to '%s' in %d attempts; giving up%n", 213 methodReference, executionsSinceLastInvocation); 214 System.exit(1); 215 } else if (executionsSinceLastInvocation == MAX_EXECUTIONS_WITHOUT_INVOCATION / 2) { 216 // The application under test might perform classpath modifications or create classes 217 // dynamically that implement interfaces or extend abstract classes. Rescanning the 218 // classpath might help with constructing objects. 219 Meta.rescanClasspath(); 220 } 221 } catch (AutofuzzInvocationException e) { 222 executionsSinceLastInvocation = 0; 223 Throwable cause = e.getCause(); 224 Class<?> causeClass = cause.getClass(); 225 // Do not report exceptions declared to be thrown by the method under test. 226 for (Class<?> declaredThrow : throwsDeclarations.get(targetExecutable)) { 227 if (declaredThrow.isAssignableFrom(causeClass)) { 228 return; 229 } 230 } 231 232 if (ignoredExceptionMatchers.stream().anyMatch(m -> m.matches(causeClass.getName()))) { 233 return; 234 } 235 cleanStackTraces(cause); 236 throw cause; 237 } catch (Throwable t) { 238 System.err.println("Unexpected exception encountered during autofuzz"); 239 t.printStackTrace(); 240 System.exit(1); 241 } finally { 242 if (returnValue instanceof Closeable) { 243 ((Closeable) returnValue).close(); 244 } 245 } 246 } 247 248 // Removes all stack trace elements that live in the Java standard library, internal JDK classes 249 // or the autofuzz package from the bottom of all stack frames. cleanStackTraces(Throwable t)250 private static void cleanStackTraces(Throwable t) { 251 Throwable cause = t; 252 while (cause != null) { 253 StackTraceElement[] elements = cause.getStackTrace(); 254 int firstInterestingPos; 255 for (firstInterestingPos = elements.length - 1; firstInterestingPos > 0; 256 firstInterestingPos--) { 257 String className = elements[firstInterestingPos].getClassName(); 258 if (!className.startsWith("com.code_intelligence.jazzer.autofuzz") 259 && !className.startsWith("java.") && !className.startsWith("jdk.")) { 260 break; 261 } 262 } 263 cause.setStackTrace(Arrays.copyOfRange(elements, 0, firstInterestingPos + 1)); 264 cause = cause.getCause(); 265 } 266 } 267 } 268