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