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 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    */
     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    */
    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    */
    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 
    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    */
    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    */
    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