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; 18 19 import dexfuzz.Log.LogTag; 20 21 import java.io.BufferedReader; 22 import java.io.File; 23 import java.io.FileNotFoundException; 24 import java.io.FileReader; 25 import java.io.IOException; 26 import java.util.ArrayList; 27 import java.util.HashMap; 28 import java.util.List; 29 import java.util.Map; 30 31 /** 32 * Stores options for dexfuzz. 33 */ 34 public class Options { 35 /** 36 * Constructor has been disabled for this class, which should only be used statically. 37 */ Options()38 private Options() { } 39 40 // KEY VALUE OPTIONS 41 public static final List<String> inputFileList = new ArrayList<String>(); 42 public static String outputFile = ""; 43 public static long rngSeed = -1; 44 public static boolean usingProvidedSeed = false; 45 public static int methodMutations = 3; 46 public static int minMethods = 2; 47 public static int maxMethods = 10; 48 public static final Map<String,Integer> mutationLikelihoods = new HashMap<String,Integer>(); 49 public static String executeClass = "Main"; 50 public static String deviceName = ""; 51 public static boolean usingSpecificDevice = false; 52 public static int repeat = 1; 53 public static int divergenceRetry = 10; 54 public static String executeDirectory = "/data/art-test"; 55 public static String androidRoot = ""; 56 public static String dumpMutationsFile = "mutations.dump"; 57 public static String loadMutationsFile = "mutations.dump"; 58 public static String reportLogFile = "report.log"; 59 public static String uniqueDatabaseFile = "unique_progs.db"; 60 61 // FLAG OPTIONS 62 public static boolean execute; 63 public static boolean executeOnHost; 64 public static boolean noBootImage; 65 public static boolean useInterpreter; 66 public static boolean useOptimizing; 67 public static boolean useArchArm; 68 public static boolean useArchArm64; 69 public static boolean useArchX86; 70 public static boolean useArchX86_64; 71 public static boolean useArchMips; 72 public static boolean useArchMips64; 73 public static boolean skipHostVerify; 74 public static boolean shortTimeouts; 75 public static boolean dumpOutput; 76 public static boolean dumpVerify; 77 public static boolean mutateLimit; 78 public static boolean reportUnique; 79 public static boolean skipMutation; 80 public static boolean dumpMutations; 81 public static boolean loadMutations; 82 public static boolean runBisectionSearch; 83 public static boolean quiet; 84 85 /** 86 * Print out usage information about dexfuzz, and then exit. 87 */ usage()88 public static void usage() { 89 Log.always("DexFuzz Usage:"); 90 Log.always(" --input=<file> : Seed DEX file to be fuzzed"); 91 Log.always(" (Can specify multiple times.)"); 92 Log.always(" --inputs=<file> : Directory containing DEX files to be fuzzed."); 93 Log.always(" --output=<file> : Output DEX file to be produced"); 94 Log.always(""); 95 Log.always(" --execute : Execute the resulting fuzzed program"); 96 Log.always(" --host : Execute on host"); 97 Log.always(" --device=<device> : Execute on an ADB-connected-device, where <device> is"); 98 Log.always(" the argument given to adb -s. Default execution mode."); 99 Log.always(" --execute-dir=<dir> : Push tests to this directory to execute them."); 100 Log.always(" (Default: /data/art-test)"); 101 Log.always(" --android-root=<dir> : Set path where dalvikvm should look for binaries."); 102 Log.always(" Use this when pushing binaries to a custom location."); 103 Log.always(" --no-boot-image : Use this flag when boot.art is not available."); 104 Log.always(" --skip-host-verify : When executing, skip host-verification stage"); 105 Log.always(" --execute-class=<c> : When executing, execute this class (default: Main)"); 106 Log.always(""); 107 Log.always(" --interpreter : Include the Interpreter in comparisons"); 108 Log.always(" --optimizing : Include the Optimizing Compiler in comparisons"); 109 Log.always(""); 110 Log.always(" --arm : Include ARM backends in comparisons"); 111 Log.always(" --arm64 : Include ARM64 backends in comparisons"); 112 Log.always(" --allarm : Short for --arm --arm64"); 113 Log.always(" --x86 : Include x86 backends in comparisons"); 114 Log.always(" --x86-64 : Include x86-64 backends in comparisons"); 115 Log.always(" --mips : Include MIPS backends in comparisons"); 116 Log.always(" --mips64 : Include MIPS64 backends in comparisons"); 117 Log.always(""); 118 Log.always(" --dump-output : Dump outputs of executed programs"); 119 Log.always(" --dump-verify : Dump outputs of verification"); 120 Log.always(" --repeat=<n> : Fuzz N programs, executing each one."); 121 Log.always(" --short-timeouts : Shorten timeouts (faster; use if"); 122 Log.always(" you want to focus on output divergences)"); 123 Log.always(" --divergence-retry=<n> : Number of retries when checking if test is"); 124 Log.always(" self-divergent. (Default: 10)"); 125 Log.always(" --seed=<seed> : RNG seed to use"); 126 Log.always(" --method-mutations=<n> : Maximum number of mutations to perform on each method."); 127 Log.always(" (Default: 3)"); 128 Log.always(" --min-methods=<n> : Minimum number of methods to mutate. (Default: 2)"); 129 Log.always(" --max-methods=<n> : Maximum number of methods to mutate. (Default: 10)"); 130 Log.always(" --one-mutation : Short for --method-mutations=1 "); 131 Log.always(" --min-methods=1 --max-methods=1"); 132 Log.always(" --likelihoods=<file> : A file containing a table of mutation likelihoods"); 133 Log.always(" --mutate-limit : Mutate only methods whose names end with _MUTATE"); 134 Log.always(" --skip-mutation : Do not actually mutate the input, just output it"); 135 Log.always(" after parsing"); 136 Log.always(""); 137 Log.always(" --dump-mutations[=<file>] : Dump an editable set of mutations applied"); 138 Log.always(" to <file> (default: mutations.dump)"); 139 Log.always(" --load-mutations[=<file>] : Load and apply a set of mutations"); 140 Log.always(" from <file> (default: mutations.dump)"); 141 Log.always(" --log=<tag> : Set more verbose logging level: DEBUG, INFO, WARN"); 142 Log.always(" --report=<file> : Use <file> to report results when using --repeat"); 143 Log.always(" (Default: report.log)"); 144 Log.always(" --report-unique : Print out information about unique programs generated"); 145 Log.always(" --unique-db=<file> : Use <file> store results about unique programs"); 146 Log.always(" (Default: unique_progs.db)"); 147 Log.always(" --bisection-search : Run bisection search for divergences"); 148 Log.always(" --quiet : Disables progress log"); 149 Log.always(""); 150 System.exit(0); 151 } 152 153 /** 154 * Given a flag option (one that does not feature an =), handle it 155 * accordingly. Report an error and print usage info if the flag is not 156 * recognised. 157 */ handleFlagOption(String flag)158 private static void handleFlagOption(String flag) { 159 if (flag.equals("execute")) { 160 execute = true; 161 } else if (flag.equals("host")) { 162 executeOnHost = true; 163 } else if (flag.equals("no-boot-image")) { 164 noBootImage = true; 165 } else if (flag.equals("skip-host-verify")) { 166 skipHostVerify = true; 167 } else if (flag.equals("interpreter")) { 168 useInterpreter = true; 169 } else if (flag.equals("optimizing")) { 170 useOptimizing = true; 171 } else if (flag.equals("arm")) { 172 useArchArm = true; 173 } else if (flag.equals("arm64")) { 174 useArchArm64 = true; 175 } else if (flag.equals("allarm")) { 176 useArchArm = true; 177 useArchArm64 = true; 178 } else if (flag.equals("x86")) { 179 useArchX86 = true; 180 } else if (flag.equals("x86-64")) { 181 useArchX86_64 = true; 182 } else if (flag.equals("mips")) { 183 useArchMips = true; 184 } else if (flag.equals("mips64")) { 185 useArchMips64 = true; 186 } else if (flag.equals("mutate-limit")) { 187 mutateLimit = true; 188 } else if (flag.equals("report-unique")) { 189 reportUnique = true; 190 } else if (flag.equals("dump-output")) { 191 dumpOutput = true; 192 } else if (flag.equals("dump-verify")) { 193 dumpVerify = true; 194 } else if (flag.equals("short-timeouts")) { 195 shortTimeouts = true; 196 } else if (flag.equals("skip-mutation")) { 197 skipMutation = true; 198 } else if (flag.equals("dump-mutations")) { 199 dumpMutations = true; 200 } else if (flag.equals("load-mutations")) { 201 loadMutations = true; 202 } else if (flag.equals("one-mutation")) { 203 methodMutations = 1; 204 minMethods = 1; 205 maxMethods = 1; 206 } else if (flag.equals("bisection-search")) { 207 runBisectionSearch = true; 208 } else if (flag.equals("quiet")) { 209 quiet = true; 210 } else if (flag.equals("help")) { 211 usage(); 212 } else { 213 Log.error("Unrecognised flag: --" + flag); 214 usage(); 215 } 216 } 217 218 /** 219 * Given a key-value option (one that features an =), handle it 220 * accordingly. Report an error and print usage info if the key is not 221 * recognised. 222 */ handleKeyValueOption(String key, String value)223 private static void handleKeyValueOption(String key, String value) { 224 if (key.equals("input")) { 225 inputFileList.add(value); 226 } else if (key.equals("inputs")) { 227 File folder = new File(value); 228 if (folder.listFiles() == null) { 229 Log.errorAndQuit("Specified argument to --inputs is not a directory!"); 230 } 231 for (File file : folder.listFiles()) { 232 String inputName = value + "/" + file.getName(); 233 Log.always("Adding " + inputName + " to input seed files."); 234 inputFileList.add(inputName); 235 } 236 } else if (key.equals("output")) { 237 outputFile = value; 238 } else if (key.equals("seed")) { 239 rngSeed = Long.parseLong(value); 240 usingProvidedSeed = true; 241 } else if (key.equals("method-mutations")) { 242 methodMutations = Integer.parseInt(value); 243 } else if (key.equals("min-methods")) { 244 minMethods = Integer.parseInt(value); 245 } else if (key.equals("max-methods")) { 246 maxMethods = Integer.parseInt(value); 247 } else if (key.equals("repeat")) { 248 repeat = Integer.parseInt(value); 249 } else if (key.equals("divergence-retry")) { 250 divergenceRetry = Integer.parseInt(value); 251 } else if (key.equals("log")) { 252 Log.setLoggingLevel(LogTag.valueOf(value.toUpperCase())); 253 } else if (key.equals("likelihoods")) { 254 setupMutationLikelihoodTable(value); 255 } else if (key.equals("dump-mutations")) { 256 dumpMutations = true; 257 dumpMutationsFile = value; 258 } else if (key.equals("load-mutations")) { 259 loadMutations = true; 260 loadMutationsFile = value; 261 } else if (key.equals("report")) { 262 reportLogFile = value; 263 } else if (key.equals("unique-db")) { 264 uniqueDatabaseFile = value; 265 } else if (key.equals("execute-class")) { 266 executeClass = value; 267 } else if (key.equals("device")) { 268 deviceName = value; 269 usingSpecificDevice = true; 270 } else if (key.equals("execute-dir")) { 271 executeDirectory = value; 272 } else if (key.equals("android-root")) { 273 androidRoot = value; 274 } else { 275 Log.error("Unrecognised key: --" + key); 276 usage(); 277 } 278 } 279 setupMutationLikelihoodTable(String tableFilename)280 private static void setupMutationLikelihoodTable(String tableFilename) { 281 try { 282 BufferedReader reader = new BufferedReader(new FileReader(tableFilename)); 283 String line = reader.readLine(); 284 while (line != null) { 285 line = line.replaceAll("\\s+", " "); 286 String[] entries = line.split(" "); 287 String name = entries[0].toLowerCase(); 288 int likelihood = Integer.parseInt(entries[1]); 289 if (likelihood > 100) { 290 likelihood = 100; 291 } 292 if (likelihood < 0) { 293 likelihood = 0; 294 } 295 mutationLikelihoods.put(name, likelihood); 296 line = reader.readLine(); 297 } 298 reader.close(); 299 } catch (FileNotFoundException e) { 300 Log.error("Unable to open mutation probability table file: " + tableFilename); 301 } catch (IOException e) { 302 Log.error("Unable to read mutation probability table file: " + tableFilename); 303 } 304 } 305 306 /** 307 * Called by the DexFuzz class during program initialisation to parse 308 * the program's command line arguments. 309 * @return If options were successfully read and validated. 310 */ readOptions(String[] args)311 public static boolean readOptions(String[] args) { 312 for (String arg : args) { 313 if (!(arg.startsWith("--"))) { 314 Log.error("Unrecognised option: " + arg); 315 usage(); 316 } 317 318 // cut off the -- 319 arg = arg.substring(2); 320 321 // choose between a --X=Y option (keyvalue) and a --X option (flag) 322 if (arg.contains("=")) { 323 String[] split = arg.split("="); 324 handleKeyValueOption(split[0], split[1]); 325 } else { 326 handleFlagOption(arg); 327 } 328 } 329 330 return validateOptions(); 331 } 332 333 /** 334 * Checks if the current options settings are valid, called after reading 335 * all options. 336 * @return If the options are valid or not. 337 */ validateOptions()338 private static boolean validateOptions() { 339 // Deal with option assumptions. 340 if (inputFileList.isEmpty()) { 341 File seedFile = new File("fuzzingseed.dex"); 342 if (seedFile.exists()) { 343 Log.always("Assuming --input=fuzzingseed.dex"); 344 inputFileList.add("fuzzingseed.dex"); 345 } else { 346 Log.errorAndQuit("No input given, and couldn't find fuzzingseed.dex!"); 347 return false; 348 } 349 } 350 351 if (outputFile.equals("")) { 352 Log.always("Assuming --output=fuzzingseed_fuzzed.dex"); 353 outputFile = "fuzzingseed_fuzzed.dex"; 354 } 355 356 357 if (mutationLikelihoods.isEmpty()) { 358 File likelihoodsFile = new File("likelihoods.txt"); 359 if (likelihoodsFile.exists()) { 360 Log.always("Assuming --likelihoods=likelihoods.txt "); 361 setupMutationLikelihoodTable("likelihoods.txt"); 362 } else { 363 Log.always("Using default likelihoods (see README for values)"); 364 } 365 } 366 367 // Now check for hard failures. 368 if (repeat < 1) { 369 Log.error("--repeat must be at least 1!"); 370 return false; 371 } 372 if (divergenceRetry < 0) { 373 Log.error("--divergence-retry cannot be negative!"); 374 return false; 375 } 376 if (usingProvidedSeed && repeat > 1) { 377 Log.error("Cannot use --repeat with --seed"); 378 return false; 379 } 380 if (loadMutations && dumpMutations) { 381 Log.error("Cannot both load and dump mutations"); 382 return false; 383 } 384 if (repeat == 1 && inputFileList.size() > 1) { 385 Log.error("Must use --repeat if you have provided more than one input"); 386 return false; 387 } 388 if (methodMutations < 0) { 389 Log.error("Cannot use --method-mutations with a negative value."); 390 return false; 391 } 392 if (minMethods < 0) { 393 Log.error("Cannot use --min-methods with a negative value."); 394 return false; 395 } 396 if (maxMethods < 0) { 397 Log.error("Cannot use --max-methods with a negative value."); 398 return false; 399 } 400 if (maxMethods < minMethods) { 401 Log.error("Cannot use --max-methods that's smaller than --min-methods"); 402 return false; 403 } 404 if (executeOnHost && usingSpecificDevice) { 405 Log.error("Cannot use --host and --device!"); 406 return false; 407 } 408 if (execute) { 409 // When host-execution mode is specified, we don't need to select an architecture. 410 if (!executeOnHost) { 411 if (!(useArchArm 412 || useArchArm64 413 || useArchX86 414 || useArchX86_64 415 || useArchMips 416 || useArchMips64)) { 417 Log.error("No architecture to execute on was specified!"); 418 return false; 419 } 420 } else { 421 // TODO: Select the correct architecture. For now, just assume x86. 422 useArchX86 = true; 423 } 424 if ((useArchArm || useArchArm64) && (useArchX86 || useArchX86_64)) { 425 Log.error("Did you mean to specify ARM and x86?"); 426 return false; 427 } 428 if ((useArchArm || useArchArm64) && (useArchMips || useArchMips64)) { 429 Log.error("Did you mean to specify ARM and MIPS?"); 430 return false; 431 } 432 if ((useArchX86 || useArchX86_64) && (useArchMips || useArchMips64)) { 433 Log.error("Did you mean to specify x86 and MIPS?"); 434 return false; 435 } 436 int backends = 0; 437 if (useInterpreter) { 438 backends++; 439 } 440 if (useOptimizing) { 441 backends++; 442 } 443 if (useArchArm && useArchArm64) { 444 // Could just be comparing optimizing-ARM versus optimizing-ARM64? 445 backends++; 446 } 447 if (backends < 2) { 448 Log.error("Not enough backends specified! Try --optimizing --interpreter!"); 449 return false; 450 } 451 } 452 453 return true; 454 } 455 } 456