def __init__(self, instr): # init attributes self.opcode_str = "invalid" self.opcode = TLULOpcode.invalid self.address = 0 self.data = 0 self.direct_in = None self.repeat = 1 # Validate/Decode opcode string if _YAMLTags.opcode not in instr: print( red("ERROR: all YAML lines require an opcode field. ABORTING!") ) self.opcode_str = str(instr[_YAMLTags.opcode]) self.opcode = self._decode_opcode_str() # Check ADDRESS and DATA fields exist in YAML and convert to int if self.opcode == TLULOpcode.read: if _YAMLTags.address not in instr: print( red("ERROR: read opcodes require an ADDRESS field. ABORTING!" )) sys.exit(1) self.address = int(instr[_YAMLTags.address]) elif self.opcode == TLULOpcode.write: if _YAMLTags.address not in instr: print( red("ERROR: write opcodes require an ADDRESS field. ABORTING!" )) sys.exit(1) if _YAMLTags.data not in instr: print( red("ERROR: write opcodes require an DATA field. ABORTING!" )) sys.exit(1) self.address = int(instr[_YAMLTags.address]) self.data = int(instr[_YAMLTags.data]) # Validata address and data fields self._validate_instr_field_size(_YAMLTags.address, self.address, TLULFuzzInstr.address_size) self._validate_instr_field_size(_YAMLTags.data, self.data, TLULFuzzInstr.data_size) # check if DIRECT_IN should exist in YAML and convert to int if TLULFuzzInstr.direct_in_size > 0: if _YAMLTags.direct_in not in instr: print( red("ERROR: direct_in field required if size > 0. ABORTING!" )) else: self.direct_in = int(instr[_YAMLTags.direct_in]) self._validate_instr_field_size(_YAMLTags.direct_in, self.direct_in, TLULFuzzInstr.direct_in_size) # check if REPEAT field exists in YAML and convert to int if _YAMLTags.repeat in instr: self.repeat = int(instr[_YAMLTags.repeat])
def _load_cov_data(self, cov_type): cov_data_path = "%s/logs/%s_cum.csv" % (self.cov_data_path, cov_type) if not os.path.exists(cov_data_path): print( red("ERROR: coverage data (%s) does not exist." % cov_data_path)) sys.exit(1) # Load data into Pandas DataFrame cov_df = self._load_csv_data(cov_data_path) if cov_df.shape[0] < int(self.afl_data.iloc[-1, 2]): print( red("ERROR: coverage data is missing (%s). Aborting!" % cov_type)) sys.exit(1) # TODO(ttrippel): remove this hack after fixing run_cov_local.sh if cov_type == "vlt_cov": cov_df.drop(AFL_TEST_ID_LABEL, axis=1, inplace=True) cov_df.insert(0, AFL_TEST_ID_LABEL, list(range(cov_df.shape[0]))) else: # Convert Test-ID labels to ints cov_df.loc[:, AFL_TEST_ID_LABEL] = cov_df.loc[:, AFL_TEST_ID_LABEL].apply( FuzzingData. _id_str_to_int) # Set ID column as the row indicies cov_df = cov_df.set_index(AFL_TEST_ID_LABEL) return cov_df
def _load_rfuzz_vlt_cov_data(self, cov_data_path): if not os.path.exists(cov_data_path): print( red("ERROR: coverage data (%s) does not exist." % cov_data_path)) sys.exit(1) # Load data into Pandas DataFrame cov_df = self._load_csv_data(cov_data_path) # Check dimensions match, i.e., no data is missing if cov_df.shape[0] != self.rfuzz_data.shape[0]: print(red("ERROR: coverage data is missing. Aborting!")) sys.exit(1) # Set ID column as the row indicies cov_df = cov_df.set_index(TEST_ID_LABEL) return cov_df
def _check_simulation_results(encrypt_results, decrypt_results, test_pair, verbose=False): error = False for i in range(len(test_pair.encrypt.data_in_line_starts)): # Extract plaintexts/ciphertexts with open(encrypt_results, "r") as fp: log_lines = fp.readlines() encrypt_in_data = _get_crypt_io_data_blocks( log_lines, test_pair.encrypt.data_in_line_starts[i], test_pair.encrypt.data_block_size) encrypt_out_data = _get_crypt_io_data_blocks( log_lines, test_pair.encrypt.data_out_line_starts[i], test_pair.encrypt.data_block_size) with open(decrypt_results, "r") as fp: log_lines = fp.readlines() decrypt_in_data = _get_crypt_io_data_blocks( log_lines, test_pair.decrypt.data_in_line_starts[i], test_pair.decrypt.data_block_size) decrypt_out_data = _get_crypt_io_data_blocks( log_lines, test_pair.decrypt.data_out_line_starts[i], test_pair.decrypt.data_block_size) # Check all data blocks are the same length if (len(encrypt_in_data) != len(encrypt_out_data) or len(encrypt_in_data) != len(decrypt_in_data) or len(encrypt_in_data) != len(decrypt_out_data)): error = True error_msg = "data LENGTH MISMATCH for encrypt/decrypt data blocks." if not error: for i in range(len(encrypt_out_data)): if verbose: print( "\n Encrypt Out = 0x{:0>8X}; Decrypt In = 0x{:0>8X}" .format(encrypt_out_data[i], decrypt_in_data[i])) if encrypt_out_data[i] != decrypt_in_data[i]: error_msg = "encryption OUTPUT does not match decryption INPUT." error = True break if verbose: print(" --------------------------------------------") if not error: for i in range(len(encrypt_in_data)): if verbose: print( " Encrypt In = 0x{:0>8X}; Decrypt Out = 0x{:0>8X}". format(encrypt_in_data[i], decrypt_out_data[i])) if encrypt_in_data[i] != decrypt_out_data[i]: error_msg = "encryption INPUT does not match decryption OUTPUT." break if verbose: print(" --------------------------------------------") # If an error occured during any test, terminate if error: break # Print a color-coded message with results if not error: print(green("PASS")) else: print("".join([red("ERROR"), ": ", error_msg]))
def check_num_active_vm_instances(config): """Checks number of active VM instances on GCE as a $$$ safety measure.""" if not config.args.silent: print(LINE_SEP) print("Checking number of active VMs on GCE ...") print(LINE_SEP) cmd = [ "gcloud", "compute", "instances", "list", "--zones=%s" % config.gcp_params["zone"] ] proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) num_active_vm_instances = -1 # first line is header while True: line = proc.stdout.readline() if not line: break num_active_vm_instances += 1 if num_active_vm_instances < config.args.max_vm_instances: if not config.args.silent: print(green("%d active VM(s)" % num_active_vm_instances)) else: if not config.args.silent: print(red("%d active VM(s)" % num_active_vm_instances)) print( yellow("waiting %d seconds and trying again ..." % config.args.vm_launch_wait_time_s)) return num_active_vm_instances
def _extract_crypt_io_log_lines(yaml_seeds_dir, yaml_seed): num_crypts = 0 data_block_size = 0 data_in_line_starts = [] data_out_line_starts = [] with open(os.path.join(yaml_seeds_dir, yaml_seed), "r") as ys: for line in ys: line = line.rstrip().lstrip("# ") if line == "TEST VARIABLES": ys.readline() # subtract three to account for other text in log num_crypts = _get_next_test_param(yaml_seed, ys, "NUM_CRYPTS") data_block_size = _get_next_test_param(yaml_seed, ys, "DATA_BLOCK_SIZE") for crypt_num in range(1, num_crypts + 1): data_in_line_starts.append( _get_next_test_param( yaml_seed, ys, "DATA_IN_LINE_START_" + str(crypt_num)) - 3) data_out_line_starts.append( _get_next_test_param( yaml_seed, ys, "DATA_OUT_LINE_START_" + str(crypt_num)) - 3) if (num_crypts == 0 or data_block_size == 0 or len(data_in_line_starts) == 0 or len(data_out_line_starts) == 0): print( red("ERROR: could not retrieve test params from YAML seed (%s)." % yaml_seed), file=sys.stderr) sys.exit(1) return AESTestParams(num_crypts, data_block_size, data_in_line_starts, data_out_line_starts)
def dump_seed_file_to_stdin(output_file_name): """Dumps generated seed file in hex format to STDIN.""" print(output_file_name + ":") cmd = ["xxd", output_file_name] try: subprocess.check_call(cmd) except subprocess.CalledProcessError: print(red("ERROR: cannot dump generated seed file.")) sys.exit(1)
def _load_bb_data(self): bb_csv_file = os.path.join(self.data_path, "logs/bb_complexity.csv") if not os.path.exists(bb_csv_file): print(red("ERROR: BB CSV file (%s) not found." % bb_csv_file)) sys.exit(1) # Load data into Pandas DataFrame df = self._load_csv_data(bb_csv_file) # Remove leading/trailing white space from column names df = df.rename(columns=lambda x: x.strip()) return df
def _load_hwf_vlt_cov_data(self, cov_data_path): if not os.path.exists(cov_data_path): print( red("ERROR: coverage data (%s) does not exist." % cov_data_path)) sys.exit(1) # Load data into Pandas DataFrame cov_df = self._load_csv_data(cov_data_path) if cov_df.shape[0] < int(self.hwf_afl_data.iloc[-1, 2]): # print(cov_df.shape[0], int(self.hwf_afl_data.iloc[-1, 2])) print( red("WARNING: some coverage data is missing for %s" % cov_data_path)) # Convert Test-ID labels to ints cov_df.loc[:, TEST_ID_LABEL] = cov_df.loc[:, TEST_ID_LABEL].apply( FuzzingData._id_str_to_int) # Set ID column as the row indicies cov_df = cov_df.set_index(TEST_ID_LABEL) return cov_df
def _get_next_test_param(yaml_seed, yaml_seed_file, expected_key): line = yaml_seed_file.readline() line_list = line.lstrip("# ").rstrip().split("=") key = line_list[0] if key != expected_key: print( red("ERROR: expected test parameter (%s) not in YAML seed (%s)." % (expected_key, yaml_seed)), file=sys.stderr) sys.exit(1) return int(line_list[1])
def run_cmd(cmd, error_str, silent=False, fail_silent=False): """Runs the provided command (list of strings) in a separate process.""" try: if not silent: print("Running command:") print(yellow(subprocess.list2cmdline(cmd))) subprocess.check_call(cmd) else: subprocess.check_call(cmd, stdout=subprocess.DEVNULL) except subprocess.CalledProcessError: if not fail_silent: print(red(error_str), file=sys.stderr) sys.exit(1)
def _load_run_time(self): run_time_path = "%s/logs/fuzz_time.log" % self.data_path if not os.path.exists(run_time_path): print(red("ERROR: run time data (%s) does not exist." % run_time_path)) sys.exit(1) with open(run_time_path, "r") as lf: for line in lf: line = line.strip() if line.startswith("real"): line_list = line.split() rt_min = float(line_list[1].split("m")[0]) rt_sec = float(line_list[1].split("m")[1].rstrip("s")) return ((rt_min * 60) + rt_sec)
def _extract_test_pairs(yaml_seeds_dir, testcase=None): test_pairs = [] encrypt_seeds = glob.glob(os.path.join(yaml_seeds_dir, "*encrypt*.yml")) decrypt_seeds = glob.glob(os.path.join(yaml_seeds_dir, "*decrypt*.yml")) encrypt_seeds = list(map(os.path.basename, encrypt_seeds)) decrypt_seeds = list(map(os.path.basename, decrypt_seeds)) encrypt_seeds.sort() decrypt_seeds.sort() if len(encrypt_seeds) != len(decrypt_seeds): print(red("ERROR: non-symmetric number of encrypt/decrypt seeds."), file=sys.stderr) sys.exit(1) for i in range(len(encrypt_seeds)): if testcase is not None: prefix = testcase.split("x")[0] suffix = "".join([testcase.split("x")[-1], ".yml"]) if (not encrypt_seeds[i].startswith(prefix) or not encrypt_seeds[i].endswith(suffix)): continue # Extract test details from comments in yaml file encrypt_test_params = _extract_crypt_io_log_lines( yaml_seeds_dir, encrypt_seeds[i]) decrypt_test_params = _extract_crypt_io_log_lines( yaml_seeds_dir, decrypt_seeds[i]) # Check if the test params are equivalent if encrypt_test_params != decrypt_test_params: print(red( "ERROR: test parameters do not match for seeds (%s and %s)." % (encrypt_seeds[i], decrypt_seeds[i])), file=sys.stderr) sys.exit(1) # Create test objects and pair them together encrypt_test = AESTest("encrypt", encrypt_seeds[i], encrypt_test_params) decrypt_test = AESTest("decrypt", decrypt_seeds[i], decrypt_test_params) test_pairs.append(AESTestPair(encrypt_test, decrypt_test)) return test_pairs
def _load_afl_data(self): afl_glob_path = os.path.join(self.data_path, "out", "afl_*_interactive", "plot_data") afl_plot_data_files = glob.glob(afl_glob_path) if len(afl_plot_data_files) != 1: print(red("ERROR: AFL plot_data file no found.")) sys.exit(1) # Load data into Pandas DataFrame afl_df = self._load_csv_data(afl_plot_data_files[0]) # Remove leading/trailing white space from column names afl_df = afl_df.rename(columns=lambda x: x.strip()) # Adjust time stamps to be relative to start time afl_df.loc[:, "# unix_time"] -= afl_df.loc[0, "# unix_time"] return afl_df
def create_local_experiment_data_dir(config): """Creates local directories to store fuzzing experiment data.""" if not config.args.silent: print(LINE_SEP) print("Creating local directories for fuzzing data ...") print(LINE_SEP) exp_data_path = "%s/data/%s" % \ (config.root_path, config.experiment_name) # Create directories os.makedirs(exp_data_path) os.chmod(exp_data_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH) os.mkdir(os.path.join(exp_data_path, "out")) os.chmod(os.path.join(exp_data_path, "out"), stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH) os.mkdir(os.path.join(exp_data_path, "logs")) os.chmod(os.path.join(exp_data_path, "logs"), stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH) # Copy over seeds that were used in this experiment seeds_dir = "%s/hw/%s/%s/seeds" % (config.root_path, config.soc, config.toplevel) seed_descripts_dir = "%s/hw/%s/%s/seed_descriptions" % ( config.root_path, config.soc, config.toplevel) if os.path.isdir(seeds_dir): shutil.copytree(seeds_dir, os.path.join(exp_data_path, "seeds")) os.chmod(os.path.join(exp_data_path, "seeds"), stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH) elif os.path.isdir(seed_descripts_dir): shutil.copytree(seed_descripts_dir, os.path.join(exp_data_path, "seed_descriptions")) os.chmod(os.path.join(exp_data_path, "seed_descriptions"), stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH) else: print(red("ERROR: no seeds found. Terminating experiment!"), file=sys.stderr) # Copy over HJSON config file that was used shutil.copy2(config.config_filename, exp_data_path) if not config.args.silent: print(green("DIRECTORY CREATION SUCCESSFUL -- Done!")) return exp_data_path
def dump_to_csv(self, csv_file_name): """Dump coverage dictionary to a CSV file for future analysis.""" # Check that all columns of dict are the same length col_headers = list(self.coverage_dict.keys()) num_rows = len(self.coverage_dict[col_headers[0]]) for col_header in col_headers: if num_rows != len(self.coverage_dict[col_header]): print(red("ERROR: table dimension mismatch. Aborting!")) # Fail silently, we don't want to throw away fuzzing results for a # post-processing error. sys.exit() with open(csv_file_name, "w", newline="") as csv_file: csv_writer = csv.writer(csv_file) # Write CSV column headers col_headers = self.coverage_dict.keys() csv_writer.writerow(col_headers) for row_index in range(num_rows): # craft row list row_list = [] for col in col_headers: row_list.append(self.coverage_dict[col][row_index]) # write row list to file csv_writer.writerow(row_list)
def _main(): num_experiments = len(NUM_STATES) * len(COMP_WIDTHS) * len(RUNS) * len( EXPERIMENT_BASE_NAMES) print(LINE_SEP) print(LINE_SEP) print(LINE_SEP) print(green("LAUNCHING %d EXPERIMENTS ..." % num_experiments)) print(LINE_SEP) print(LINE_SEP) print(LINE_SEP) # create a temp dir to store config files try: tmp_dir = os.path.join(os.getcwd(), "tmp%d") i = 0 while os.path.isdir(tmp_dir % i): i += 1 tmp_dir = tmp_dir % i os.mkdir(tmp_dir) # create config files on the fly and launch experiments for experiment_base_name in EXPERIMENT_BASE_NAMES: for states in NUM_STATES: for width in COMP_WIDTHS: for run in RUNS: # craft config dictionary cdict = copy.deepcopy(CONFIG_DICT) # Set experiment name experiment_name = experiment_base_name % (states, width, run) cdict["experiment_name"] = experiment_name # Set test bench if "wopt" in experiment_name: cdict["tb"] = "afl_opt" else: cdict["tb"] = "afl" # Set instrumentation amount if "full-instr" in experiment_name: cdict["instrument_dut"] = 1 cdict["instrument_tb"] = 1 cdict["instrument_vltrt"] = 1 elif "duttb-instr" in experiment_name: cdict["instrument_dut"] = 1 cdict["instrument_tb"] = 1 cdict["instrument_vltrt"] = 0 elif "dut" in experiment_name: cdict["instrument_dut"] = 1 cdict["instrument_tb"] = 0 cdict["instrument_vltrt"] = 0 else: print(red("ERROR: invalid instrumentation config. ABORTING!")) sys.exit(1) # Set lock size cdict["hdl_gen_params"]["num_lock_states"] = states cdict["hdl_gen_params"]["lock_comp_width"] = width # write to HJSON file hjson_filename = experiment_name + ".hjson" hjson_file_path = os.path.join(tmp_dir, hjson_filename) with open(hjson_file_path, "w") as fp: hjson.dump(cdict, fp) # launch fuzz the DUT fuzz(["--fail-silently", hjson_file_path]) # cleanup config file os.remove(hjson_file_path) finally: # remove temp dir for tmp_dir in glob.glob("tmp*"): shutil.rmtree(tmp_dir, ignore_errors=True) print(LINE_SEP) print(LINE_SEP) print(LINE_SEP) print(green("DONE!")) print(LINE_SEP) print(LINE_SEP) print(LINE_SEP)
def _validate_instr_field_size(self, field, value, size): if value >= 2**(size * 8): print( red("ERROR: instruction field (%s) larger than size. ABORTING!" % field)) sys.exit(1)
def _sigint_handler(sig, frame): print(red("\nTERMINATING EXPERIMENT!")) sys.exit(0)
def _abort(abort_msg): print(red(abort_msg), file=sys.stderr) sys.exit(1)