Home | History | Annotate | Download | only in dexfuzz
      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    */
     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    */
     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    */
    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    */
    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 
    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    */
    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    */
    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