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