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