Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 0 additions & 4 deletions config.defpy
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 1 addition & 36 deletions diff_fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)))

Expand All @@ -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:
Expand Down
28 changes: 28 additions & 0 deletions mutations.py
Original file line number Diff line number Diff line change
@@ -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,
]