1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dexfuzz.fuzzers; 18 19 import dexfuzz.Log; 20 import dexfuzz.Options; 21 import dexfuzz.Timer; 22 import dexfuzz.executors.Architecture; 23 import dexfuzz.executors.Arm64InterpreterExecutor; 24 import dexfuzz.executors.Arm64OptimizingBackendExecutor; 25 import dexfuzz.executors.Arm64QuickBackendExecutor; 26 import dexfuzz.executors.ArmInterpreterExecutor; 27 import dexfuzz.executors.ArmOptimizingBackendExecutor; 28 import dexfuzz.executors.ArmQuickBackendExecutor; 29 import dexfuzz.executors.Device; 30 import dexfuzz.executors.Executor; 31 import dexfuzz.executors.Mips64InterpreterExecutor; 32 import dexfuzz.executors.Mips64OptimizingBackendExecutor; 33 import dexfuzz.executors.Mips64QuickBackendExecutor; 34 import dexfuzz.executors.MipsInterpreterExecutor; 35 import dexfuzz.executors.MipsOptimizingBackendExecutor; 36 import dexfuzz.executors.MipsQuickBackendExecutor; 37 import dexfuzz.executors.X86InterpreterExecutor; 38 import dexfuzz.executors.X86OptimizingBackendExecutor; 39 import dexfuzz.executors.X86QuickBackendExecutor; 40 import dexfuzz.executors.X86_64InterpreterExecutor; 41 import dexfuzz.executors.X86_64OptimizingBackendExecutor; 42 import dexfuzz.executors.X86_64QuickBackendExecutor; 43 import dexfuzz.listeners.BaseListener; 44 import dexfuzz.program.Mutation; 45 import dexfuzz.program.Program; 46 import dexfuzz.rawdex.DexRandomAccessFile; 47 import dexfuzz.rawdex.OffsetTracker; 48 import dexfuzz.rawdex.RawDexFile; 49 50 import java.io.FileNotFoundException; 51 import java.io.IOException; 52 import java.lang.reflect.Constructor; 53 import java.lang.reflect.InvocationTargetException; 54 import java.util.ArrayList; 55 import java.util.HashMap; 56 import java.util.List; 57 import java.util.Map; 58 59 /** 60 * A particular fuzzing strategy, this class provides the common methods 61 * most fuzzing will involve, and subclasses override the run() method, to 62 * employ a particular strategy. 63 */ 64 public abstract class Fuzzer { 65 private List<Executor> executors; 66 private OffsetTracker offsetTracker; 67 68 /** 69 * This is the executor that we use to test for self-divergent programs. 70 */ 71 private Executor goldenExecutor; 72 73 /* 74 * These two flags are set during fuzz(), and then cleared at the end of execute(). 75 */ 76 private boolean mutatedSuccessfully; 77 private boolean savedSuccessfully; 78 79 private Timer totalTimer = new Timer("Total Time"); 80 private Timer timerDexInput = new Timer("DEX Input"); 81 private Timer timerProgGen = new Timer("Program Generation"); 82 private Timer timerMutation = new Timer("Mutation Time"); 83 private Timer timerDexOutput = new Timer("DEX Output"); 84 private Timer timerChecksumCalc = new Timer("Checksum Calculation"); 85 86 protected BaseListener listener; 87 Fuzzer(BaseListener listener)88 protected Fuzzer(BaseListener listener) { 89 totalTimer.start(); 90 executors = new ArrayList<Executor>(); 91 this.listener = listener; 92 } 93 run()94 public abstract void run(); 95 getNextInputFilename()96 protected abstract String getNextInputFilename(); 97 getNextOutputFilename()98 protected abstract String getNextOutputFilename(); 99 100 /** 101 * Call this after fuzzer execution to print out timing results. 102 */ printTimingInfo()103 public void printTimingInfo() { 104 totalTimer.stop(); 105 timerDexInput.printTime(listener); 106 timerProgGen.printTime(listener); 107 timerMutation.printTime(listener); 108 timerDexOutput.printTime(listener); 109 timerChecksumCalc.printTime(listener); 110 totalTimer.printTime(listener); 111 } 112 113 /** 114 * Make sure this is called to correctly shutdown each Executor's StreamConsumers. 115 */ shutdown()116 public void shutdown() { 117 if (executors != null) { 118 for (Executor executor : executors) { 119 executor.shutdown(); 120 } 121 } 122 } 123 addExecutorsForArchitecture(Device device, Class<? extends Executor> quick, Class<? extends Executor> optimizing, Class<? extends Executor> interpreter)124 private void addExecutorsForArchitecture(Device device, Class<? extends Executor> quick, 125 Class<? extends Executor> optimizing, Class<? extends Executor> interpreter) { 126 // NB: Currently QuickBackend MUST come immediately before same arch's Interpreter. 127 // This is because intepreter execution relies on there being an OAT file already 128 // created to produce correct debug information. Otherwise we will see 129 // false-positive divergences. 130 try { 131 if (Options.useQuick) { 132 Constructor<? extends Executor> constructor = 133 quick.getConstructor(BaseListener.class, Device.class); 134 executors.add(constructor.newInstance(listener, device)); 135 } 136 if (Options.useOptimizing) { 137 Constructor<? extends Executor> constructor = 138 optimizing.getConstructor(BaseListener.class, Device.class); 139 executors.add(constructor.newInstance(listener, device)); 140 } 141 if (Options.useInterpreter) { 142 Constructor<? extends Executor> constructor = 143 interpreter.getConstructor(BaseListener.class, Device.class); 144 executors.add(constructor.newInstance(listener, device)); 145 } 146 } catch (NoSuchMethodException e) { 147 Log.errorAndQuit("Executor doesn't have correct constructor."); 148 } catch (InstantiationException e) { 149 Log.errorAndQuit("Executor couldn't be instantiated."); 150 } catch (IllegalAccessException e) { 151 Log.errorAndQuit("Executor couldn't be accessed."); 152 } catch (IllegalArgumentException e) { 153 Log.errorAndQuit("Invalid arguments to instantiation of Executor."); 154 } catch (InvocationTargetException e) { 155 Log.errorAndQuit("Instantiation of Executor threw an Exception!"); 156 } 157 } 158 addExecutors()159 protected void addExecutors() { 160 Device device = null; 161 if (Options.executeOnHost) { 162 device = new Device(); 163 } else { 164 device = new Device(Options.deviceName, Options.noBootImage); 165 } 166 167 if (Options.useArchArm64) { 168 addExecutorsForArchitecture(device, Arm64QuickBackendExecutor.class, 169 Arm64OptimizingBackendExecutor.class, Arm64InterpreterExecutor.class); 170 } 171 172 if (Options.useArchArm) { 173 addExecutorsForArchitecture(device, ArmQuickBackendExecutor.class, 174 ArmOptimizingBackendExecutor.class, ArmInterpreterExecutor.class); 175 } 176 177 if (Options.useArchX86_64) { 178 addExecutorsForArchitecture(device, X86_64QuickBackendExecutor.class, 179 X86_64OptimizingBackendExecutor.class, X86_64InterpreterExecutor.class); 180 } 181 182 if (Options.useArchX86) { 183 addExecutorsForArchitecture(device, X86QuickBackendExecutor.class, 184 X86OptimizingBackendExecutor.class, X86InterpreterExecutor.class); 185 } 186 187 if (Options.useArchMips64) { 188 addExecutorsForArchitecture(device, Mips64QuickBackendExecutor.class, 189 Mips64OptimizingBackendExecutor.class, Mips64InterpreterExecutor.class); 190 } 191 192 if (Options.useArchMips) { 193 addExecutorsForArchitecture(device, MipsQuickBackendExecutor.class, 194 MipsOptimizingBackendExecutor.class, MipsInterpreterExecutor.class); 195 } 196 197 // Add the first backend as the golden executor for self-divergence tests. 198 goldenExecutor = executors.get(0); 199 } 200 201 /** 202 * Called from each Fuzzer subclass that we can instantiate. Parses the program, fuzzes it, 203 * and then saves it, if mutation was successful. We can use --skip-mutation to bypass 204 * the mutation phase, if we wanted to verify that a test program itself works. 205 */ fuzz()206 protected Program fuzz() { 207 String inputFile = getNextInputFilename(); 208 Program program = loadProgram(inputFile, null); 209 if (program == null) { 210 Log.errorAndQuit("Problem loading seed file."); 211 } 212 // Mutate the program. 213 if (!Options.skipMutation) { 214 timerMutation.start(); 215 program.mutateTheProgram(); 216 217 mutatedSuccessfully = program.updateRawDexFile(); 218 timerMutation.stop(); 219 if (!mutatedSuccessfully) { 220 listener.handleMutationFail(); 221 } 222 } else { 223 Log.info("Skipping mutation stage as requested."); 224 mutatedSuccessfully = true; 225 } 226 if (mutatedSuccessfully) { 227 savedSuccessfully = saveProgram(program, getNextOutputFilename()); 228 } 229 return program; 230 } 231 safeToExecute()232 protected boolean safeToExecute() { 233 return mutatedSuccessfully && savedSuccessfully; 234 } 235 execute(Program program)236 protected void execute(Program program) { 237 if (!safeToExecute()) { 238 Log.errorAndQuit("Your Fuzzer subclass called execute() " 239 + "without checking safeToExecute()!"); 240 } 241 242 String programName = getNextOutputFilename(); 243 boolean verified = true; 244 245 if (!Options.skipHostVerify && !Options.executeOnHost) { 246 verified = goldenExecutor.verifyOnHost(programName); 247 if (verified) { 248 listener.handleSuccessfulHostVerification(); 249 } 250 } 251 252 if (verified) { 253 boolean skipAnalysis = false; 254 255 for (Executor executor : executors) { 256 executor.reset(); 257 executor.prepareProgramForExecution(programName); 258 executor.execute(programName); 259 if (!executor.didTargetVerify()) { 260 listener.handleFailedTargetVerification(); 261 skipAnalysis = true; 262 break; 263 } 264 // Results are saved in the executors until they reset, usually at the 265 // next iteration. 266 } 267 268 if (!skipAnalysis) { 269 listener.handleSuccessfullyFuzzedFile(programName); 270 analyseResults(program, programName); 271 } 272 } 273 274 goldenExecutor.finishedWithProgramOnDevice(); 275 mutatedSuccessfully = false; 276 savedSuccessfully = false; 277 } 278 279 /** 280 * Checks if the different outputs we observed align with different architectures. 281 */ checkForArchitectureSplit(Map<String, List<Executor>> outputMap)282 private boolean checkForArchitectureSplit(Map<String, List<Executor>> outputMap) { 283 if (outputMap.size() != 2) { 284 // Cannot have a two-way split if we don't have 2 kinds of output. 285 return false; 286 } 287 288 Architecture[] architectures = new Architecture[2]; 289 int archIdx = 0; 290 291 // For each kind of output we saw, make sure they all 292 // came from the same architecture. 293 for (List<Executor> executorList : outputMap.values()) { 294 architectures[archIdx] = executorList.get(0).getArchitecture(); 295 for (int execIdx = 1; execIdx < executorList.size(); execIdx++) { 296 if (executorList.get(execIdx).getArchitecture() != architectures[archIdx]) { 297 // Not every executor with this output shared the same architecture. 298 return false; 299 } 300 } 301 archIdx++; 302 } 303 304 // Now make sure that the two outputs we saw were different architectures. 305 if (architectures[0] == architectures[1]) { 306 return false; 307 } 308 return true; 309 } 310 checkGoldenExecutorForSelfDivergence(String programName)311 private boolean checkGoldenExecutorForSelfDivergence(String programName) { 312 // Run golden executor 5 times, make sure it always produces 313 // the same output, otherwise report that it is self-divergent. 314 315 // TODO: Instead, produce a list of acceptable outputs, and see if the divergent 316 // outputs of the backends fall within this set of outputs. 317 String seenOutput = null; 318 for (int i = 0; i < 5; i++) { 319 goldenExecutor.reset(); 320 goldenExecutor.execute(programName); 321 String output = goldenExecutor.getResult().getFlattenedOutput(); 322 if (seenOutput == null) { 323 seenOutput = output; 324 } else if (!seenOutput.equals(output)) { 325 return true; 326 } 327 } 328 return false; 329 } 330 analyseResults(Program program, String programName)331 private void analyseResults(Program program, String programName) { 332 // Check timeouts. 333 // Construct two lists of executors, those who timed out, and those who did not. 334 // Report if we had some timeouts. 335 List<Executor> timedOut = new ArrayList<Executor>(); 336 List<Executor> didNotTimeOut = new ArrayList<Executor>(); 337 for (Executor executor : executors) { 338 if (executor.getResult().isTimeout()) { 339 timedOut.add(executor); 340 } else { 341 didNotTimeOut.add(executor); 342 } 343 } 344 if (!timedOut.isEmpty()) { 345 listener.handleTimeouts(timedOut, didNotTimeOut); 346 // Do not bother reporting divergence information. 347 return; 348 } 349 350 // Check divergences. 351 // Construct a map {output1: [executor that produced output1, ...], output2: [...]} 352 // If the map has more than one output, we had divergence, report it. 353 Map<String, List<Executor>> outputMap = new HashMap<String, List<Executor>>(); 354 for (Executor executor : executors) { 355 String output = executor.getResult().getFlattenedOutput(); 356 if (Options.dumpOutput) { 357 listener.handleDumpOutput( 358 executor.getResult().getFlattenedOutputWithNewlines(), executor); 359 } 360 if (outputMap.containsKey(output)) { 361 outputMap.get(output).add(executor); 362 } else { 363 List<Executor> newList = new ArrayList<Executor>(); 364 newList.add(executor); 365 outputMap.put(output, newList); 366 } 367 } 368 369 if (outputMap.size() > 1) { 370 // Report that we had divergence. 371 listener.handleDivergences(outputMap); 372 listener.handleMutations(program.getMutations()); 373 // If we found divergences, try running the "golden executor" 374 // a few times in succession, to see if the output it produces is different 375 // from run to run. If so, then we're probably executing something with either: 376 // a) randomness 377 // b) timing-dependent code 378 // c) threads 379 // So we will not consider it a "true" divergence, but still useful? 380 if (checkGoldenExecutorForSelfDivergence(programName)) { 381 listener.handleSelfDivergence(); 382 return; 383 } 384 // If we found divergences, try checking if the differences 385 // in outputs align with differences in architectures. 386 // For example, if we have: {Output1: [ARM, ARM], Output2: [ARM64, ARM64]} 387 if (checkForArchitectureSplit(outputMap)) { 388 listener.handleArchitectureSplit(); 389 } 390 } else { 391 // No problems with execution. 392 listener.handleSuccess(outputMap); 393 } 394 } 395 loadProgram(String inputName, List<Mutation> mutations)396 private Program loadProgram(String inputName, List<Mutation> mutations) { 397 Program program = null; 398 try { 399 DexRandomAccessFile input = new DexRandomAccessFile(inputName, "r"); 400 offsetTracker = new OffsetTracker(); 401 input.setOffsetTracker(offsetTracker); 402 // Read the raw DexFile 403 RawDexFile rawDexFile = new RawDexFile(); 404 timerDexInput.start(); 405 rawDexFile.read(input); 406 timerDexInput.stop(); 407 input.close(); 408 // Create the program view. 409 timerProgGen.start(); 410 program = new Program(rawDexFile, mutations, listener); 411 timerProgGen.stop(); 412 } catch (FileNotFoundException e) { 413 Log.errorAndQuit("Couldn't open a file called " + inputName); 414 } catch (IOException e) { 415 Log.errorAndQuit("IOException when trying to load a DEX test file!"); 416 } 417 return program; 418 } 419 saveProgram(Program program, String outputName)420 private boolean saveProgram(Program program, String outputName) { 421 boolean success = false; 422 423 try { 424 // Write out the results of mutation. 425 DexRandomAccessFile output = new DexRandomAccessFile(outputName, "rw"); 426 output.setOffsetTracker(offsetTracker); 427 // Delete the contents of the file, in case it already existed. 428 output.setLength(0); 429 // Write out the file. 430 timerDexOutput.start(); 431 program.writeRawDexFile(output); 432 timerDexOutput.stop(); 433 // Recalculate the header info. 434 timerChecksumCalc.start(); 435 program.updateRawDexFileHeader(output); 436 timerChecksumCalc.stop(); 437 output.close(); 438 success = true; 439 } catch (FileNotFoundException e) { 440 Log.errorAndQuit("Couldn't open a file called " + outputName); 441 } catch (IOException e) { 442 Log.errorAndQuit("IOException when trying to save a DEX test file!"); 443 } 444 return success; 445 } 446 } 447