From c7eff8134fd07c941884cd206d004904ad5fce05 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 11 Dec 2019 17:06:39 -0600 Subject: [PATCH 01/10] added fuzzer.py, first commit --- ace/fuzzer.py | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100755 ace/fuzzer.py diff --git a/ace/fuzzer.py b/ace/fuzzer.py new file mode 100755 index 00000000..f9d42e61 --- /dev/null +++ b/ace/fuzzer.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +# TODO: put some usage instructions here + +import os +import sys +import argparse +import itertools +import ntpath +from shutil import copyfile + +import common + +def log(msg, flush=False): + sys.stdout.write(msg) + if flush: sys.stdout.flush() + +def parse_jlang(filename): + run_starts = False + + ignore = ["checkpoint", "close", "none"] + instructions = [] + + for line in open(filename, "r"): + if run_starts: + if not any(line.startswith(i) for i in ignore): + instructions.append(line.strip()) + elif "# run" in line: + run_starts = True + + return instructions + +def get_permutations(instruction): + parts = instruction.split() + perms = [] + + if parts[0] == "open": + pass + elif parts[0] == "opendir" or parts[0] == "mkdir": + pass + elif parts[0] == "link": + pass + elif parts[0] == "symlink": + pass + elif parts[0] == "remove" or parts[0] == "unlink": + pass + elif parts[0] == "fsetxattr": + pass + elif parts[0] == "removexattr": + pass + elif parts[0] == "truncate": + pass + elif parts[0] == "dwrite": + pass + elif parts[0] == "write": + pass + elif parts[0] == "mmapwrite": + pass + elif parts[0] == "fsync": + pass + elif parts[0] == "fdatasync": + pass + elif parts[0] == "sync": + pass + elif parts[0] == "rename": + pass + elif parts[0] == "rmdir": + pass + elif parts[0] == "falloc": + filename, mode, offset, length = parts[1:] + + for mode_i in common.FallocOptions: + if mode != mode_i: + perms.append("falloc {} {} {} {}".format(filename, mode_i, offset, length)) + else: + print("Unknown instruction: {}".format(instruction)) + sys.exit(1) + + return perms + +def write_jlang_file(lines, filename, base_jlang): + copyfile(base_jlang, filename) + + log("Writing to file {}\n".format(filename)) + with open(filename, "a") as f: + f.write("\n# run\n") + + add_newline = lambda f: f + "\n" + lines = list(map(add_newline, lines)) + f.writelines(lines) + +def fuzz(instructions, test_file, output_dir): + basename = ntpath.basename(test_file) + base_jlang = os.path.join(output_dir, "base-j-lang") + num_created = 0 + + def make_name(): + nonlocal num_created + + num_created += 1 + base = "{}-fuzz{}".format(basename, num_created) + return os.path.join(output_dir, base) + + options = [get_permutations(i) for i in instructions] + + modified_instructions = instructions[:] + for i, instr in enumerate(instructions): + for option_i in options[i]: + modified_instructions[i] = option_i + + write_jlang_file(modified_instructions, make_name(), base_jlang) + + modified_instructions[i] = instr + +def build_parser(): + parser = argparse.ArgumentParser(description='Workload Fuzzer for CrashMonkey v1.0') + + parser.add_argument('--test_file', '-t', + required=True, help='J-lang test file to use as seed for fuzzer') + parser.add_argument('--output_dir', '-o', + required=True, help='Directory to save the generated test files') + + return parser + +def main(): + parsed_args = build_parser().parse_args() + + # Ensure J-lang file exists + if not os.path.isfile(parsed_args.test_file): + print("{} : File not found".format(parsed_args.test_file)) + sys.exit(1) + + # Create output directory if it does not exist + if not os.path.isdir(parsed_args.output_dir): + if os.path.exists(parsed_args.output_dir): + print("{} already exists but is not directory".format(parsed_args.output_dir)) + sys.exit(1) + os.makedirs(parsed_args.output_dir) + + # Copy base J-lang file to output directory + cwd = os.path.dirname(os.path.realpath(__file__)) + base_j_lang = os.path.join(cwd, "../code/tests/ace-base/base-j-lang") + copyfile(base_j_lang, os.path.join(parsed_args.output_dir, "base-j-lang")) + + instructions = parse_jlang(parsed_args.test_file) + fuzz(instructions, parsed_args.test_file, parsed_args.output_dir) + + +if __name__ == '__main__': + main() From 1487d2b0bb64df782004f209b13d7c2c5ca21e73 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 11 Dec 2019 17:12:20 -0600 Subject: [PATCH 02/10] updated gitignore --- ace/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ace/.gitignore b/ace/.gitignore index 64cefc3d..28c69aa2 100644 --- a/ace/.gitignore +++ b/ace/.gitignore @@ -1,4 +1,4 @@ Makefile -j-lang10 +j-lang* *.pyc __pycache__/ From 38442e99befc3d05319726315d8a47c8eb690e64 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 11 Dec 2019 17:35:57 -0600 Subject: [PATCH 03/10] fuzzer generates modified j-lang files and uses crashmonkey adapter --- ace/fuzzer.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/ace/fuzzer.py b/ace/fuzzer.py index f9d42e61..895a5a2f 100755 --- a/ace/fuzzer.py +++ b/ace/fuzzer.py @@ -4,15 +4,16 @@ import os import sys +import ntpath import argparse import itertools -import ntpath +import subprocess from shutil import copyfile import common def log(msg, flush=False): - sys.stdout.write(msg) + sys.stdout.write(str(msg)) if flush: sys.stdout.flush() def parse_jlang(filename): @@ -90,28 +91,46 @@ def write_jlang_file(lines, filename, base_jlang): f.writelines(lines) def fuzz(instructions, test_file, output_dir): - basename = ntpath.basename(test_file) + test_name = ntpath.basename(test_file) base_jlang = os.path.join(output_dir, "base-j-lang") - num_created = 0 + base_cm = os.path.join(output_dir, "base.cpp") + # Function to generate numbered filenames of the format: + # -fuzz + num_created = 1 def make_name(): nonlocal num_created + name = "{}-fuzz{}".format(test_name, num_created) num_created += 1 - base = "{}-fuzz{}".format(basename, num_created) - return os.path.join(output_dir, base) + return name + # TODO: improve this, for now just try manipulating the + # options of each command. options = [get_permutations(i) for i in instructions] - modified_instructions = instructions[:] for i, instr in enumerate(instructions): for option_i in options[i]: modified_instructions[i] = option_i + # Write the list of modified instructions to a j-lang file write_jlang_file(modified_instructions, make_name(), base_jlang) modified_instructions[i] = instr + # Convert the J-lang files to CrashMonkey files using the cmAdapter. + for i in range(1, num_created): + jlang_name = "{}-fuzz{}".format(test_name, i) + + if not output_dir.endswith("/"): + output_dir += "/" + cmd = "python3 cmAdapter.py -b {} -t {} -p {}\n".format(base_cm, jlang_name, output_dir) + subprocess.call(cmd, shell=True) + + # Move J-lang file to output folder. + os.rename(jlang_name, os.path.join(output_dir, jlang_name)) + + def build_parser(): parser = argparse.ArgumentParser(description='Workload Fuzzer for CrashMonkey v1.0') @@ -142,6 +161,10 @@ def main(): base_j_lang = os.path.join(cwd, "../code/tests/ace-base/base-j-lang") copyfile(base_j_lang, os.path.join(parsed_args.output_dir, "base-j-lang")) + # Copy base Crashmonkey file to outpt directory + base_cm = os.path.join(cwd, "../code/tests/ace-base/base.cpp") + copyfile(base_cm, os.path.join(parsed_args.output_dir, "base.cpp")) + instructions = parse_jlang(parsed_args.test_file) fuzz(instructions, parsed_args.test_file, parsed_args.output_dir) From 95784ab35298838b84556b292d4ac657805642a1 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Fri, 13 Dec 2019 15:00:44 -0600 Subject: [PATCH 04/10] fuzzer now interchanges syncs and writes --- ace/fuzzer.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/ace/fuzzer.py b/ace/fuzzer.py index 895a5a2f..2432a709 100755 --- a/ace/fuzzer.py +++ b/ace/fuzzer.py @@ -40,9 +40,13 @@ def get_permutations(instruction): elif parts[0] == "opendir" or parts[0] == "mkdir": pass elif parts[0] == "link": - pass + r = parts[:] + r[0] = "symlink" + perms.append(" ".join(r)) elif parts[0] == "symlink": - pass + r = parts[:] + r[0] = "link" + perms.append(" ".join(r)) elif parts[0] == "remove" or parts[0] == "unlink": pass elif parts[0] == "fsetxattr": @@ -52,15 +56,27 @@ def get_permutations(instruction): elif parts[0] == "truncate": pass elif parts[0] == "dwrite": - pass + r = parts[:] + for replace in ['write', 'mmapwrite']: + r[0] = replace + perms.append(" ".join(r)) elif parts[0] == "write": - pass + r = parts[:] + for replace in ['dwrite', 'mmapwrite']: + r[0] = replace + perms.append(" ".join(r)) elif parts[0] == "mmapwrite": - pass + r = parts[:] + for replace in ['dwrite', 'write']: + r[0] = replace + perms.append(" ".join(r)) elif parts[0] == "fsync": - pass + perms.append("sync") + + perms.append("fdatasync " + parts[1]) elif parts[0] == "fdatasync": - pass + perms.append("sync") + perms.append("fsync " + parts[1]) elif parts[0] == "sync": pass elif parts[0] == "rename": From f19eeaaaa028a480c52515ad26cadcefb72920cc Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 18 Dec 2019 14:45:22 -0600 Subject: [PATCH 05/10] also convert base j-lang file --- ace/fuzzer.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ace/fuzzer.py b/ace/fuzzer.py index 2432a709..8df67d20 100755 --- a/ace/fuzzer.py +++ b/ace/fuzzer.py @@ -111,6 +111,9 @@ def fuzz(instructions, test_file, output_dir): base_jlang = os.path.join(output_dir, "base-j-lang") base_cm = os.path.join(output_dir, "base.cpp") + if not output_dir.endswith("/"): + output_dir += "/" + # Function to generate numbered filenames of the format: # -fuzz num_created = 1 @@ -134,12 +137,14 @@ def make_name(): modified_instructions[i] = instr - # Convert the J-lang files to CrashMonkey files using the cmAdapter. + # Convert the original J-lang file using the Crashmonkey Adapter. + cmd = "python3 cmAdapter.py -b {} -t {} -p {}\n".format(base_cm, test_name, output_dir) + subprocess.call(cmd, shell=True) + + # Convert the generated J-lang files. for i in range(1, num_created): jlang_name = "{}-fuzz{}".format(test_name, i) - if not output_dir.endswith("/"): - output_dir += "/" cmd = "python3 cmAdapter.py -b {} -t {} -p {}\n".format(base_cm, jlang_name, output_dir) subprocess.call(cmd, shell=True) From 3b13f71b3858c4f7ea14b196e4404d2170439263 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 18 Dec 2019 15:14:49 -0600 Subject: [PATCH 06/10] keep checkpoint and close --- ace/fuzzer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ace/fuzzer.py b/ace/fuzzer.py index 8df67d20..2c861cb8 100755 --- a/ace/fuzzer.py +++ b/ace/fuzzer.py @@ -19,7 +19,7 @@ def log(msg, flush=False): def parse_jlang(filename): run_starts = False - ignore = ["checkpoint", "close", "none"] + ignore = ["none"] instructions = [] for line in open(filename, "r"): @@ -89,6 +89,8 @@ def get_permutations(instruction): for mode_i in common.FallocOptions: if mode != mode_i: perms.append("falloc {} {} {} {}".format(filename, mode_i, offset, length)) + elif parts[0] == "checkpoint" or parts[0] == "close": + pass else: print("Unknown instruction: {}".format(instruction)) sys.exit(1) From 3788f509d519db10d696fb71bb24ecb9318f5609 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 12 Feb 2020 17:47:20 -0600 Subject: [PATCH 07/10] python3 --- ace/fuzzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ace/fuzzer.py b/ace/fuzzer.py index 2c861cb8..6b1d6007 100755 --- a/ace/fuzzer.py +++ b/ace/fuzzer.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # TODO: put some usage instructions here From a9ee1844953cc77c629690df53c6698468b28720 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Thu, 13 Feb 2020 14:08:20 -0600 Subject: [PATCH 08/10] xfsMonkey - use os.path.join --- xfsMonkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfsMonkey.py b/xfsMonkey.py index b0e87b1b..607c4d43 100755 --- a/xfsMonkey.py +++ b/xfsMonkey.py @@ -117,7 +117,7 @@ def main(): snapshot = filename.replace('.so', '') + '_' + parsed_args.fs_type #Get full test file path - test_file = xfsMonkeyTestPath.replace('./build/', '') + filename + test_file = os.path.join(xfsMonkeyTestPath.replace('./build/', ''), filename) #Build command to run c_harness command = ('cd build; ./c_harness -v -c -P -f '+ parsed_args.flag_dev +' -d '+ From 62636a45583d998ef32e47bbeb7c7444cc534272 Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 22 Apr 2020 16:25:34 -0500 Subject: [PATCH 09/10] added fuzzer docs --- ace/cmAdapter.py | 3 ++- ace/fuzzer.py | 27 ++++++++++++++++++++------- docs/Fuzzer.md | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 docs/Fuzzer.md diff --git a/ace/cmAdapter.py b/ace/cmAdapter.py index b1919d27..d0518c57 100755 --- a/ace/cmAdapter.py +++ b/ace/cmAdapter.py @@ -4,6 +4,7 @@ import os import re import sys +import ntpath import stat import subprocess import argparse @@ -650,7 +651,7 @@ def main(): val = 0 - new_file = test_file + ".cpp" + new_file = ntpath.basename(test_file) + ".cpp" new_file = os.path.join(parsed_args.target_path, new_file) copyfile(base_file, new_file) diff --git a/ace/fuzzer.py b/ace/fuzzer.py index 6b1d6007..f3fabb47 100755 --- a/ace/fuzzer.py +++ b/ace/fuzzer.py @@ -1,6 +1,15 @@ #!/usr/bin/env python3 -# TODO: put some usage instructions here +# Usage: fuzzer.py [-h] --test_file TEST_FILE --output_dir OUTPUT_DIR +# +# Workload Fuzzer for CrashMonkey v1.0 + +# Optional arguments: +# -h, --help show this help message and exit +# --test_file TEST_FILE, -t TEST_FILE +# J-lang test file to use as seed for fuzzer +# --output_dir OUTPUT_DIR, -o OUTPUT_DIR +# Directory to save the generated test files import os import sys @@ -126,8 +135,8 @@ def make_name(): num_created += 1 return name - # TODO: improve this, for now just try manipulating the - # options of each command. + # Single-operation mutation. For each operation, try all + # possible modifications. options = [get_permutations(i) for i in instructions] modified_instructions = instructions[:] for i, instr in enumerate(instructions): @@ -140,14 +149,18 @@ def make_name(): modified_instructions[i] = instr # Convert the original J-lang file using the Crashmonkey Adapter. - cmd = "python3 cmAdapter.py -b {} -t {} -p {}\n".format(base_cm, test_name, output_dir) + cmd = "python3 cmAdapter.py -b {} -t {} -p {}\n".format(base_cm, test_file, output_dir) subprocess.call(cmd, shell=True) + copyfile(test_file, os.path.join(output_dir, test_name)) # Convert the generated J-lang files. for i in range(1, num_created): jlang_name = "{}-fuzz{}".format(test_name, i) - cmd = "python3 cmAdapter.py -b {} -t {} -p {}\n".format(base_cm, jlang_name, output_dir) + cmd = "python3 cmAdapter.py -b {} -t {} -p {}\n".format( + base_cm, + jlang_name, + output_dir) subprocess.call(cmd, shell=True) # Move J-lang file to output folder. @@ -157,9 +170,9 @@ def make_name(): def build_parser(): parser = argparse.ArgumentParser(description='Workload Fuzzer for CrashMonkey v1.0') - parser.add_argument('--test_file', '-t', + parser.add_argument('--test_file', '-t', required=True, help='J-lang test file to use as seed for fuzzer') - parser.add_argument('--output_dir', '-o', + parser.add_argument('--output_dir', '-o', required=True, help='Directory to save the generated test files') return parser diff --git a/docs/Fuzzer.md b/docs/Fuzzer.md new file mode 100644 index 00000000..66512c20 --- /dev/null +++ b/docs/Fuzzer.md @@ -0,0 +1,16 @@ +# Fuzzer + +### Overview +The [ACE](Ace.md) framework also supports a fuzzer that can search the space around given J-lang workloads. Specifically, it takes high-level "J-lang" workloads as inputs and exhaustively searches the space of *single-operation modifications* around that workload. For example, given a workload with a write operation, the fuzzer will generate two additional test cases that swap the write operation and for dwrite and mmapwrite respectively. It will do this swap/generation for each operation in the test case. + +This fuzzer is useful for searching around longer, n-length workloads, for which exhaustively searching the space of n-length workloads with ACE is not feasible. If we have a longer workload that triggers a bug, or triggered a bug at some point in time, this fuzzer may be able to find similar bugs. + +### Usage +The fuzzer can be found [here](../ace/fuzzer.py). Running `./fuzzer.py -h` will give detailed usage instrutions. The main arguments are shown below: + + *Flags:* + + * `-t FILE`, `--test_file FILE` - Use the given `FILE` as the starting seed for the fuzzer. + * `-o DIR`, `--output_dir DIR` - Output files into `DIR`. + +For each generated test, the fuzzer will create a high level, J-lang file and Crashmonkey workload (using the Crashmonkey adapter [here](../code/cmAdapter.py) in the output directory. From 6ba98ca219f55be09624726347d07314a871411b Mon Sep 17 00:00:00 2001 From: Arvind Raghavan Date: Wed, 22 Apr 2020 16:28:09 -0500 Subject: [PATCH 10/10] added docs for the fuzzer --- docs/Fuzzer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Fuzzer.md b/docs/Fuzzer.md index 66512c20..de1d11bb 100644 --- a/docs/Fuzzer.md +++ b/docs/Fuzzer.md @@ -3,7 +3,7 @@ ### Overview The [ACE](Ace.md) framework also supports a fuzzer that can search the space around given J-lang workloads. Specifically, it takes high-level "J-lang" workloads as inputs and exhaustively searches the space of *single-operation modifications* around that workload. For example, given a workload with a write operation, the fuzzer will generate two additional test cases that swap the write operation and for dwrite and mmapwrite respectively. It will do this swap/generation for each operation in the test case. -This fuzzer is useful for searching around longer, n-length workloads, for which exhaustively searching the space of n-length workloads with ACE is not feasible. If we have a longer workload that triggers a bug, or triggered a bug at some point in time, this fuzzer may be able to find similar bugs. +This fuzzer is useful for searching around longer, n-length workloads, for which exhaustively searching the space of n-length workloads with ACE is not feasible. When given a longer workload that triggers a bug, or triggered a bug at some point in time, this fuzzer may be able to find similar bugs around it. ### Usage The fuzzer can be found [here](../ace/fuzzer.py). Running `./fuzzer.py -h` will give detailed usage instrutions. The main arguments are shown below: @@ -13,4 +13,4 @@ The fuzzer can be found [here](../ace/fuzzer.py). Running `./fuzzer.py -h` will * `-t FILE`, `--test_file FILE` - Use the given `FILE` as the starting seed for the fuzzer. * `-o DIR`, `--output_dir DIR` - Output files into `DIR`. -For each generated test, the fuzzer will create a high level, J-lang file and Crashmonkey workload (using the Crashmonkey adapter [here](../code/cmAdapter.py) in the output directory. +For each generated test, the fuzzer will create a high level, J-lang file and a Crashmonkey workload (using the Crashmonkey adapter [here](../code/cmAdapter.py)) in the output directory.