diff --git a/README.md b/README.md index 71ffb6f..b5c02a0 100644 --- a/README.md +++ b/README.md @@ -86,13 +86,16 @@ Coverage: (3, 23, 28) - `Mutation Candidates` is the number of generated inputs in this generation that were interesting enough such that they can be used as the basis for further mutation. - `Coverage` is the number of unique edges hit by this run of fuzzer for each target by the end of this generation. -# Grammar-Based Fuzzing -To use grammar-based mutation, you need to supply a file `grammar.py` with the following symbol defined: -- `GRAMMAR_MUTATORS`: A list of functions which take bytes and apply a mutation to those bytes. +# Custom Mutations +To use custom mutations, you need to edit the file `mutations.py` with the following symbol defined: +- `MUTATORS`: A list of functions which take bytes and apply a mutation to those bytes. - We suggest that the mutators should not introduce new bytes which are further from each other than the deletion lengths specified in the config file. Doing so will increase the chance that bugs are misclassfied during minimization. - ```python - GRAMMAR_MUTATORS: list[Callable[[bytes], bytes]] = [grammar_delete, grammar_insert, grammar_replace] + MUTATORS: list[Callable[[bytes], bytes]] = [byte_delete, byte_insert, byte_replace, grammar_delete, grammar_insert, grammar_replace] ``` + - To add your own mutators simply add the mutation functions to the `MUTATORS` list. + - If a mutation cannot be applied to a given bytes object then return `ValueError`. + - When mutating a random mutation that doesn't return `ValueError` from the `MUTATORS` list will be applied. # Acknowledgements: This work made possible by the DARPA GAPS program and the GAPS teams at GE Research and Dartmouth College. diff --git a/config.defpy b/config.defpy index 883523e..8b553ae 100644 --- a/config.defpy +++ b/config.defpy @@ -27,10 +27,6 @@ TIMEOUT_TIME: int = 10000 # (i.e. the programs you're testing aren't expected to have identical output on stdout) DETECT_OUTPUT_DIFFERENTIALS: bool = False -# Set this to True if you want to use grammar mutations. -# (Requires a grammar.py with the appropriate interface) -USE_GRAMMAR_MUTATIONS: bool = False - # When this is True, a differential is registered if two targets exit with different status codes. # When it's False, a differential is registered only when one target exits with status 0 and another # exits with nonzero status. diff --git a/diff_fuzz.py b/diff_fuzz.py index 13df860..54a0be2 100644 --- a/diff_fuzz.py +++ b/diff_fuzz.py @@ -21,10 +21,9 @@ import time from pathlib import PosixPath from typing import Callable - - from tqdm import tqdm # type: ignore +from mutations import MUTATORS from config import ( compare_parse_trees, get_replacement_byte, @@ -39,18 +38,8 @@ DELETION_LENGTHS, RESULTS_DIR, REPORTS_DIR, - USE_GRAMMAR_MUTATIONS, ) -if USE_GRAMMAR_MUTATIONS: - try: - from grammar import GRAMMAR_MUTATORS # type: ignore[import] - except ModuleNotFoundError: - print( - "`grammar.py` not found. Either make one or set USE_GRAMMAR_MUTATIONS to False", file=sys.stderr - ) - sys.exit(1) - assert SEED_DIR.is_dir() SEED_INPUTS: list[PosixPath] = list(map(lambda s: SEED_DIR.joinpath(PosixPath(s)), os.listdir(SEED_DIR))) @@ -64,30 +53,6 @@ json_t = None | bool | str | int | float | dict[str, "json_t"] | list["json_t"] -def byte_replace(b: bytes) -> bytes: - if len(b) == 0: - raise ValueError("Mutation precondition didn't hold.") - index: int = random.randint(0, len(b) - 1) - return b[:index] + bytes([random.randint(0, 255)]) + b[index + 1 :] - - -def byte_insert(b: bytes) -> bytes: - index: int = random.randint(0, len(b)) - return b[:index] + bytes([random.randint(0, 255)]) + b[index:] - - -def byte_delete(b: bytes) -> bytes: - if len(b) <= 1: - raise ValueError("Mutation precondition didn't hold.") - index: int = random.randint(0, len(b) - 1) - return b[:index] + b[index + 1 :] - - -MUTATORS: list[Callable[[bytes], bytes]] = [byte_replace, byte_insert, byte_delete] + ( - GRAMMAR_MUTATORS if USE_GRAMMAR_MUTATIONS else [] -) - - def mutate(b: bytes) -> bytes: mutators: list[Callable[[bytes], bytes]] = MUTATORS.copy() while len(mutators) != 0: diff --git a/mutations.py b/mutations.py new file mode 100644 index 0000000..2bf4d28 --- /dev/null +++ b/mutations.py @@ -0,0 +1,28 @@ +import random +from typing import Callable + + +def byte_replace(b: bytes) -> bytes: + if len(b) == 0: + raise ValueError("Mutation precondition didn't hold.") + index: int = random.randint(0, len(b) - 1) + return b[:index] + bytes([random.randint(0, 255)]) + b[index + 1 :] + + +def byte_insert(b: bytes) -> bytes: + index: int = random.randint(0, len(b)) + return b[:index] + bytes([random.randint(0, 255)]) + b[index:] + + +def byte_delete(b: bytes) -> bytes: + if len(b) <= 1: + raise ValueError("Mutation precondition didn't hold.") + index: int = random.randint(0, len(b) - 1) + return b[:index] + b[index + 1 :] + + +MUTATORS: list[Callable[[bytes], bytes]] = [ + byte_delete, + byte_insert, + byte_replace, +]