diff --git a/.gitmodules b/.gitmodules index 1a7af28..709afb6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "unicorn/fuzzware-unicorn"] path = unicorn/fuzzware-unicorn - url = ../unicorn + url = https://github.com/fuzzware-fuzzer/unicorn diff --git a/harness/fuzzware_harness/__init__.py b/harness/fuzzware_harness/__init__.py index e69de29..61ee46d 100644 --- a/harness/fuzzware_harness/__init__.py +++ b/harness/fuzzware_harness/__init__.py @@ -0,0 +1,3 @@ +def main(): + from fuzzware_harness import harness + harness.main() \ No newline at end of file diff --git a/harness/fuzzware_harness/harness.py b/harness/fuzzware_harness/harness.py index 2f8f00f..7c1ba86 100644 --- a/harness/fuzzware_harness/harness.py +++ b/harness/fuzzware_harness/harness.py @@ -4,7 +4,8 @@ import sys import logging -from unicorn import (UC_ARCH_ARM, UC_MODE_MCLASS, UC_MODE_THUMB, Uc) +from icicle import Uc +from unicorn import (UC_ARCH_ARM, UC_MODE_MCLASS, UC_MODE_THUMB) from unicorn.arm_const import UC_ARM_REG_PC, UC_ARM_REG_SP from . import globs, interrupt_triggers, native, timer, user_hooks @@ -18,6 +19,7 @@ parse_symbols, resolve_region_file_paths) logger = logging.getLogger("emulator") +logging.basicConfig(level=logging.DEBUG) def unicorn_trace_syms(uc, address, size=0, user_data=None): if address in uc.syms_by_addr: @@ -51,8 +53,11 @@ def configure_unicorn(args): logger.error("Memory Configuration must be in config file") sys.exit(1) + # Icicle doesn't currently handle shadow stacks for context switches + disable_shadow_stack = any('Soldering_Iron' in region['file'] for rname, region in config['memory_map'].items() if 'file' in region) + # Create the unicorn - uc = Uc(UC_ARCH_ARM, UC_MODE_THUMB | UC_MODE_MCLASS) + uc = Uc(UC_ARCH_ARM, UC_MODE_THUMB | UC_MODE_MCLASS, disable_shadow_stack) uc.symbols, uc.syms_by_addr = parse_symbols(config) @@ -61,6 +66,10 @@ def configure_unicorn(args): entry_image_base = None resolve_region_file_paths(args.config, config) + debug_info = config.get("debug_info") + if debug_info: + uc.set_debug_file(debug_info) + # Entry region recovery file_backed_regions = {rname: region for rname, region in config['memory_map'].items() if 'file' in region} num_entry_regions = [region.get('is_entry', False) is True for region in file_backed_regions.values()].count(True) @@ -99,7 +108,7 @@ def configure_unicorn(args): sys.exit(1) start, size = parse_address_value(uc.symbols, region['base_addr']), region['size'] - logger.debug(f"Mapping region {str(rname)} at {hex(size)}, perms: {int(prot)}") + logger.debug(f"Mapping region {str(rname)} at {hex(start)} (size: {hex(size)}), perms: {int(prot)}") if size & (globs.PAGE_SIZE-1) != 0: logger.warning(f"Size 0x{size:x} of region '{rname}' not page aligned. Aligning to next page boundary size.") @@ -374,7 +383,7 @@ def main(): if any(debug_flags): args.debug = True - globs.debug_enabled = args.debug + globs.debug_enabled = True uc = configure_unicorn(args) globs.uc = uc diff --git a/harness/fuzzware_harness/mmio_models/passthrough.py b/harness/fuzzware_harness/mmio_models/passthrough.py index b1b024c..830ab76 100644 --- a/harness/fuzzware_harness/mmio_models/passthrough.py +++ b/harness/fuzzware_harness/mmio_models/passthrough.py @@ -10,7 +10,7 @@ def register_passthrough_handlers(uc, addrs, pcs, vals): ensure_rw_mapped(uc, address, address) - set_ignored_mmio_addresses(addrs, pcs) + set_ignored_mmio_addresses(uc, addrs, pcs) def parse_passthrough_handlers(symbols, declarations): addrs = [] diff --git a/harness/fuzzware_harness/native.py b/harness/fuzzware_harness/native.py index 93f044a..9361620 100644 --- a/harness/fuzzware_harness/native.py +++ b/harness/fuzzware_harness/native.py @@ -3,6 +3,7 @@ import sys import logging +import icicle from . import timer from .exit import has_exit_hooks, invoke_exit_callbacks @@ -60,12 +61,16 @@ def _setup_prototype(lib, fname, restype, *argtypes): mmio_user_data = None def add_mmio_region(uc, start, end): + raise ValueError("unimplemented in icicle") + global mmio_user_data if mmio_user_data is None: mmio_user_data = ctypes.cast(uc._callback_count, ctypes.c_void_p) assert native_lib.add_mmio_region(uc._uch, start, end, mmio_user_data)==0 def load_fuzz(file_path): + raise ValueError("unimplemented in icicle") + assert native_lib.load_fuzz(file_path.encode())==0 sys.stdout.flush() @@ -78,29 +83,32 @@ def emulate(uc, fuzz_file_path, prefix_input_file_path=None): # In case input path is an empty string, set it to None explicitly prefix_input_file_path = None - native_lib.emulate(uc._uch, fuzz_file_path.encode(), prefix_input_file_path) + uc.native_emulate(fuzz_file_path.encode(), prefix_input_file_path) def get_fuzz(uc, size): ptr = (ctypes.c_char * size).from_address(native_lib.get_fuzz_ptr(uc, size)) return ptr.raw def fuzz_consumed(): - return native_lib.fuzz_consumed() + return icicle.fuzz_consumed() def fuzz_remaining(): - return native_lib.fuzz_remaining() + return icicle.fuzz_remaining() def get_latest_mmio_fuzz_access_size(): - return native_lib.get_latest_mmio_fuzz_access_size() + return icicle.get_latest_mmio_fuzz_access_size() def get_latest_mmio_fuzz_access_index(): - return native_lib.get_latest_mmio_fuzz_access_index() + return icicle.get_latest_mmio_fuzz_access_index() def register_cond_py_handler_hook(uc, handler_locs): + raise ValueError("unimplemented in icicle") if not handler_locs: logger.warning("no function handler hooks registered, skipping registration") return + raise ValueError("Python hooks unimplemented in icicle") + arr = (ctypes.c_int64 * len(handler_locs))(*handler_locs) # hack: In order to keep a uc reference around for the high level callback, @@ -119,6 +127,7 @@ def register_cond_py_handler_hook(uc, handler_locs): def remove_function_handler_hook_address(uc, address): + raise ValueError("Python hooks unimplemented in icicle") assert native_lib.remove_function_handler_hook_address(uc._uch, address) == 0 @@ -135,6 +144,8 @@ def _create_and_inject_c_callable_mem_hook(uc, py_fn): def _create_and_inject_c_callable_central_timer_hook(uc, py_fn): + cb = uc.register_central_timer_hook(py_fn) + callback = py_fn # hack: In order to keep a uc reference around for the high level callback, # we sneak an additional callback into the uc object (as done in unicorn.py) @@ -152,6 +163,7 @@ def _create_and_inject_c_callable_central_timer_hook(uc, py_fn): def register_py_handled_mmio_ranges(uc, python_handled_range_starts, python_handled_range_ends): global mmio_cb_wrapper + raise ValueError("Python hooks unimplemented in icicle") assert mmio_cb_wrapper is not None assert len(python_handled_range_starts) == len(python_handled_range_ends) @@ -162,61 +174,68 @@ def register_py_handled_mmio_ranges(uc, python_handled_range_starts, python_hand assert native_lib.register_py_handled_mmio_ranges(uc._uch, mmio_cb_wrapper, starts_arr, ends_arr, len(python_handled_range_ends)) == 0 def register_linear_mmio_models(uc, starts, ends, pcs, init_vals, steps): - assert len(starts) == len(ends) == len(init_vals) == len(steps) - starts_arr = (ctypes.c_int64 * len(starts))(*starts) - ends_arr = (ctypes.c_int64 * len(ends))(*ends) - init_vals_arr = (ctypes.c_int32 * len(init_vals))(*init_vals) - steps_arr = (ctypes.c_int32 * len(steps))(*steps) - pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) + assert uc.native_register_linear_mmio_models(starts, ends, pcs, init_vals, steps) == 0 + # assert len(starts) == len(ends) == len(init_vals) == len(steps) + # starts_arr = (ctypes.c_int64 * len(starts))(*starts) + # ends_arr = (ctypes.c_int64 * len(ends))(*ends) + # init_vals_arr = (ctypes.c_int32 * len(init_vals))(*init_vals) + # steps_arr = (ctypes.c_int32 * len(steps))(*steps) + # pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) - assert native_lib.register_linear_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, init_vals_arr, steps_arr, len(starts)) == 0 + # assert native_lib.register_linear_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, init_vals_arr, steps_arr, len(starts)) == 0 def register_constant_mmio_models(uc, starts, ends, pcs, vals): - assert len(starts) == len(ends) == len(vals)==len(pcs) - starts_arr = (ctypes.c_int64 * len(starts))(*starts) - ends_arr = (ctypes.c_int64 * len(ends))(*ends) - vals_arr = (ctypes.c_int32 * len(vals))(*vals) - pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) + assert uc.native_register_constant_mmio_models(starts, ends, pcs, vals) == 0 + # assert len(starts) == len(ends) == len(vals)==len(pcs) + # starts_arr = (ctypes.c_int64 * len(starts))(*starts) + # ends_arr = (ctypes.c_int64 * len(ends))(*ends) + # vals_arr = (ctypes.c_int32 * len(vals))(*vals) + # pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) - assert native_lib.register_constant_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, vals_arr, len(starts)) == 0 + # assert native_lib.register_constant_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, vals_arr, len(starts)) == 0 def register_bitextract_mmio_models(uc, starts, ends, pcs, byte_sizes, left_shifts, masks): - assert len(starts) == len(ends) == len(byte_sizes) == len(left_shifts) == len(pcs) - starts_arr = (ctypes.c_int64 * len(starts))(*starts) - ends_arr = (ctypes.c_int64 * len(ends))(*ends) - byte_sizes_arr = (ctypes.c_int8 * len(byte_sizes))(*byte_sizes) - left_shifts_arr = (ctypes.c_int8 * len(left_shifts))(*left_shifts) - masks_arr = (ctypes.c_int32 * len(masks))(*masks) - pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) + assert uc.native_register_bitextract_mmio_models(starts, ends, pcs, byte_sizes, left_shifts, masks) == 0 + # assert len(starts) == len(ends) == len(byte_sizes) == len(left_shifts) == len(pcs) + # starts_arr = (ctypes.c_int64 * len(starts))(*starts) + # ends_arr = (ctypes.c_int64 * len(ends))(*ends) + # byte_sizes_arr = (ctypes.c_int8 * len(byte_sizes))(*byte_sizes) + # left_shifts_arr = (ctypes.c_int8 * len(left_shifts))(*left_shifts) + # masks_arr = (ctypes.c_int32 * len(masks))(*masks) + # pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) - assert native_lib.register_bitextract_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, byte_sizes_arr, left_shifts_arr, masks_arr, len(starts)) == 0 + # assert native_lib.register_bitextract_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, byte_sizes_arr, left_shifts_arr, masks_arr, len(starts)) == 0 def register_value_set_mmio_models(uc, starts, ends, pcs, value_sets): - assert len(starts) == len(ends) == len(value_sets) == len(value_sets) == len(pcs) - starts_arr = (ctypes.c_int64 * len(starts))(*starts) - ends_arr = (ctypes.c_int64 * len(ends))(*ends) - pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) + assert uc.native_register_value_set_mmio_models(starts, ends, pcs, value_sets) == 0 + # assert len(starts) == len(ends) == len(value_sets) == len(value_sets) == len(pcs) + # starts_arr = (ctypes.c_int64 * len(starts))(*starts) + # ends_arr = (ctypes.c_int64 * len(ends))(*ends) + # pcs_arr = (ctypes.c_int32 * len(pcs))(*pcs) - value_nums_arr = (ctypes.c_int32 * len(value_sets))(*[len(value_set) for value_set in value_sets]) + # value_nums_arr = (ctypes.c_int32 * len(value_sets))(*[len(value_set) for value_set in value_sets]) - value_set_arrs = [(ctypes.c_int32 * len(value_set))(*value_set) for value_set in value_sets] - value_sets_arr_ptrs = (ctypes.POINTER(ctypes.c_ulong) * len(value_set_arrs))(*[ctypes.cast(value_set_arr, ctypes.POINTER(ctypes.c_ulong)) for value_set_arr in value_set_arrs]) + # value_set_arrs = [(ctypes.c_int32 * len(value_set))(*value_set) for value_set in value_sets] + # value_sets_arr_ptrs = (ctypes.POINTER(ctypes.c_ulong) * len(value_set_arrs))(*[ctypes.cast(value_set_arr, ctypes.POINTER(ctypes.c_ulong)) for value_set_arr in value_set_arrs]) - assert native_lib.register_value_set_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, value_nums_arr, value_sets_arr_ptrs, len(starts)) == 0 + # assert native_lib.register_value_set_mmio_models(uc._uch, starts_arr, ends_arr, pcs_arr, value_nums_arr, value_sets_arr_ptrs, len(starts)) == 0 -def set_ignored_mmio_addresses(addresses, pcs): - addrs_arr = (ctypes.c_int64 * len(addresses))(*addresses) - pcs_arr = (ctypes.c_uint32 * len(pcs))(*pcs) - assert native_lib.set_ignored_mmio_addresses( - addrs_arr, pcs_arr, len(addrs_arr) - ) == 0 +def set_ignored_mmio_addresses(uc, addresses, pcs): + assert uc.native_set_ignored_mmio_addresses(addresses, pcs) == 0 + + # addrs_arr = (ctypes.c_int64 * len(addresses))(*addresses) + # pcs_arr = (ctypes.c_uint32 * len(pcs))(*pcs) + + # assert native_lib.set_ignored_mmio_addresses( + # addrs_arr, pcs_arr, len(addrs_arr) + # ) == 0 def init_nvic(uc, vtor, num_vecs, interrupt_limit=DEFAULT_MAX_INTERRUPTS, disabled_interrupts=()): global native_lib logger.debug("Calling init_nvic with vtor=0x{:08x}, num_vecs: {}".format(vtor, num_vecs)) - disabled_interrupts_arr = (ctypes.c_int32 * len(disabled_interrupts))(*disabled_interrupts) - assert native_lib.init_nvic(uc._uch, vtor, num_vecs, interrupt_limit, len(disabled_interrupts), disabled_interrupts_arr) == 0 + # disabled_interrupts_arr = (ctypes.c_int32 * len(disabled_interrupts))(*disabled_interrupts) + assert uc.native_init_nvic(vtor, num_vecs, interrupt_limit, disabled_interrupts) == 0 def init_native_tracing(uc, bbl_set_trace_path, bbl_hash_path, mmio_set_trace_path, mmio_ranges): global native_lib @@ -239,41 +258,41 @@ def init_native_tracing(uc, bbl_set_trace_path, bbl_hash_path, mmio_set_trace_pa else: bbl_hash_path = bbl_hash_path.encode() - assert(native_lib.init_tracing(uc._uch, bbl_set_trace_path, bbl_hash_path, mmio_set_trace_path, len(mmio_ranges), mmio_region_starts_arr, mmio_region_ends_arr) == 0) + # assert(native_lib.init_tracing(uc._uch, bbl_set_trace_path, bbl_hash_path, mmio_set_trace_path, len(mmio_ranges), mmio_region_starts_arr, mmio_region_ends_arr) == 0) + assert(uc.native_init_tracing(bbl_set_trace_path, bbl_hash_path, mmio_set_trace_path, mmio_ranges) == 0) def nvic_set_pending(vec_num): global native_lib + raise ValueError("unimplemented in icicle") native_lib.nvic_set_pending(vec_num) def init_timer_hook(uc, global_timer_scale): - global native_lib - global timer_cb_user_data - global timer_cb_wrapper - - cb, user_data = _create_and_inject_c_callable_central_timer_hook(uc, timer.central_timer_hook) - timer_cb_wrapper = cb - timer_cb_user_data = user_data + # global native_lib + # global timer_cb_user_data + # global timer_cb_wrapper - assert native_lib.init_timer_hook(uc._uch, global_timer_scale) == 0 + # cb, user_data = _create_and_inject_c_callable_central_timer_hook(uc, timer.central_timer_hook) + # timer_cb_wrapper = cb + # timer_cb_user_data = user_data + assert uc.native_init_timer_hook(global_timer_scale) == 0 def init_systick(uc, reload_val): - global native_lib - - assert native_lib.init_systick(uc._uch, reload_val) == 0 + assert uc.native_init_systick(reload_val) == 0 IRQ_NOT_USED=0xffffffff def add_timer(reload_val, callback=None, isr_num=IRQ_NOT_USED): - global timer_cb_wrapper - global timer_cb_user_data - global native_lib + # global timer_cb_wrapper + # global timer_cb_user_data + # global native_lib - assert timer_cb_wrapper is not None and timer_cb_user_data is not None + # assert timer_cb_wrapper is not None and timer_cb_user_data is not None # While technically allowed in the C code, invoking a callback and pending an interrupt at the same time is nothing we would like to support assert not (callback is not None and isr_num != IRQ_NOT_USED) - passed_cb = timer_cb_wrapper if callback is not None else 0 + # passed_cb = timer_cb_wrapper if callback is not None else 0 - return native_lib.add_timer(reload_val, passed_cb, timer_cb_user_data, isr_num) + return uc.add_timer(reload_val, callback, isr_num) + # return native_lib.add_timer(reload_val, passed_cb, timer_cb_user_data, isr_num) def is_running(timer_id): @@ -282,30 +301,36 @@ def is_running(timer_id): def get_global_ticker(): global native_lib + raise ValueError("unimplemented in icicle") return native_lib.get_global_ticker() def rem_timer(uc, timer_id): global native_lib + raise ValueError("unimplemented in icicle") assert native_lib.rem_timer(uc, timer_id) == 0 def reload_timer(timer_id): global native_lib + raise ValueError("unimplemented in icicle") assert native_lib.reload_timer(timer_id) == 0 def start_timer(uc, timer_id): global native_lib + raise ValueError("unimplemented in icicle") assert native_lib.start_timer(uc, timer_id) == 0 def stop_timer(uc, timer_id): global native_lib + raise ValueError("unimplemented in icicle") assert native_lib.stop_timer(uc, timer_id) == 0 # uc_hook add_interrupt_trigger(uc_engine *uc, uint64_t addr, uint32_t irq, uint32_t num_skips, uint32_t num_pends, uint32_t do_fuzz); def add_interrupt_trigger(uc, addr, irq, num_skips, num_pends, fuzz_mode, trigger_mode, every_nth_tick): assert fuzz_mode < len(FUZZ_MODES) and trigger_mode < len(TRIGGER_MODES) - assert native_lib.add_interrupt_trigger(uc._uch, addr, irq, num_skips, num_pends, fuzz_mode, trigger_mode, every_nth_tick) == 0 + assert uc.native_add_interrupt_trigger(addr, irq, num_skips, num_pends, fuzz_mode, trigger_mode, every_nth_tick) == 0 def register_native_debug_hooks(uc): + raise ValueError("unimplemented in icicle") assert(native_lib.add_debug_hooks(uc._uch) == 0) def load_native_lib(native_lib_path): @@ -317,6 +342,7 @@ def load_native_lib(native_lib_path): def do_exit(uc, status, sig=-1): global native_lib + raise ValueError("unimplemented in icicle") native_lib.do_exit(uc, status, sig) def init(uc, mmio_regions, exit_at_bbls, exit_at_hit_num, do_print_exit_info, fuzz_consumption_timeout=DEFAULT_FUZZ_CONSUMPTION_TIMEOUT, instr_limit=DEFAULT_BASIC_BLOCK_LIMIT): @@ -406,18 +432,19 @@ def init(uc, mmio_regions, exit_at_bbls, exit_at_hit_num, do_print_exit_info, fu # uc_err emulate(uc_engine *uc, char *input_path, char *prefix_input_path); _setup_prototype(native_lib, "emulate", ctypes.c_int, uc_engine, ctypes.c_char_p, ctypes.c_char_p) - mmio_region_starts, mmio_region_ends = zip(*mmio_regions) - mmio_region_starts_arr = (ctypes.c_int64 * len(mmio_region_starts))(*mmio_region_starts) - mmio_region_ends_arr = (ctypes.c_int64 * len(mmio_region_ends))(*mmio_region_ends) + # mmio_region_starts, mmio_region_ends = zip(*mmio_regions) + # mmio_region_starts_arr = (ctypes.c_int64 * len(mmio_region_starts))(*mmio_region_starts) + # mmio_region_ends_arr = (ctypes.c_int64 * len(mmio_region_ends))(*mmio_region_ends) - mmio_cb_wrapper, user_data = _create_and_inject_c_callable_mem_hook(uc, mmio_access_handler_wrapper_hook) + # mmio_cb_wrapper, user_data = _create_and_inject_c_callable_mem_hook(uc, mmio_access_handler_wrapper_hook) + user_data = None if has_exit_hooks(): - exit_cb = ctypes.cast(EXIT_CB(invoke_exit_callbacks), EXIT_CB) + exit_cb = invoke_exit_callbacks # ctypes.cast(EXIT_CB(invoke_exit_callbacks), EXIT_CB) obj_refs.append(exit_cb) else: - exit_cb = 0 + exit_cb = None - num_exit_at_bbls = len(exit_at_bbls) - exit_at_bbls_arr = (ctypes.c_int64 * len(exit_at_bbls))(*exit_at_bbls) - assert native_lib.init(uc._uch, exit_cb, len(mmio_regions), mmio_region_starts_arr, mmio_region_ends_arr, user_data, num_exit_at_bbls, exit_at_bbls_arr, exit_at_hit_num, do_print_exit_info, fuzz_consumption_timeout, instr_limit) == 0 + # num_exit_at_bbls = len(exit_at_bbls) + # exit_at_bbls_arr = (ctypes.c_int64 * len(exit_at_bbls))(*exit_at_bbls) + assert uc.native_init(exit_cb, mmio_regions, user_data, exit_at_bbls, exit_at_hit_num, do_print_exit_info, fuzz_consumption_timeout, instr_limit) == 0 diff --git a/harness/fuzzware_harness/native/Makefile b/harness/fuzzware_harness/native/Makefile index c7b5113..3a9b6fe 100644 --- a/harness/fuzzware_harness/native/Makefile +++ b/harness/fuzzware_harness/native/Makefile @@ -2,12 +2,13 @@ OWN_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) UC_DIRNAME := fuzzware-unicorn LIBDIR = $(OWN_DIR)/../../../unicorn/$(UC_DIRNAME)/ -INC=-I$(OWN_DIR)/../../../unicorn/$(UC_DIRNAME)/include +# INC=-I$(OWN_DIR)/../../../unicorn/$(UC_DIRNAME)/include +INC= BIN_EXT = .so CC = clang CFLAGS += -fpic -Wall -Werror $(INC) -g -O3 -LDFLAGS += -shared -L$(LIBDIR) -lunicorn +LDFLAGS += -shared .PHONY: all clean diff --git a/harness/fuzzware_harness/native/arm.h b/harness/fuzzware_harness/native/arm.h new file mode 100644 index 0000000..bfdc650 --- /dev/null +++ b/harness/fuzzware_harness/native/arm.h @@ -0,0 +1,165 @@ +/* Unicorn Engine */ +/* By Nguyen Anh Quynh , 2015-2017 */ +/* This file is released under LGPL2. + See COPYING.LGPL2 in root directory for more details +*/ + +#ifndef UNICORN_ARM_H +#define UNICORN_ARM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _MSC_VER +#pragma warning(disable:4201) +#endif + +//> ARM registers +typedef enum uc_arm_reg { + UC_ARM_REG_INVALID = 0, + UC_ARM_REG_APSR, + UC_ARM_REG_APSR_NZCV, + UC_ARM_REG_CPSR, + UC_ARM_REG_FPEXC, + UC_ARM_REG_FPINST, + UC_ARM_REG_FPSCR, + UC_ARM_REG_FPSCR_NZCV, + UC_ARM_REG_FPSID, + UC_ARM_REG_ITSTATE, + UC_ARM_REG_LR, + UC_ARM_REG_PC, + UC_ARM_REG_SP, + UC_ARM_REG_SPSR, + UC_ARM_REG_D0, + UC_ARM_REG_D1, + UC_ARM_REG_D2, + UC_ARM_REG_D3, + UC_ARM_REG_D4, + UC_ARM_REG_D5, + UC_ARM_REG_D6, + UC_ARM_REG_D7, + UC_ARM_REG_D8, + UC_ARM_REG_D9, + UC_ARM_REG_D10, + UC_ARM_REG_D11, + UC_ARM_REG_D12, + UC_ARM_REG_D13, + UC_ARM_REG_D14, + UC_ARM_REG_D15, + UC_ARM_REG_D16, + UC_ARM_REG_D17, + UC_ARM_REG_D18, + UC_ARM_REG_D19, + UC_ARM_REG_D20, + UC_ARM_REG_D21, + UC_ARM_REG_D22, + UC_ARM_REG_D23, + UC_ARM_REG_D24, + UC_ARM_REG_D25, + UC_ARM_REG_D26, + UC_ARM_REG_D27, + UC_ARM_REG_D28, + UC_ARM_REG_D29, + UC_ARM_REG_D30, + UC_ARM_REG_D31, + UC_ARM_REG_FPINST2, + UC_ARM_REG_MVFR0, + UC_ARM_REG_MVFR1, + UC_ARM_REG_MVFR2, + UC_ARM_REG_Q0, + UC_ARM_REG_Q1, + UC_ARM_REG_Q2, + UC_ARM_REG_Q3, + UC_ARM_REG_Q4, + UC_ARM_REG_Q5, + UC_ARM_REG_Q6, + UC_ARM_REG_Q7, + UC_ARM_REG_Q8, + UC_ARM_REG_Q9, + UC_ARM_REG_Q10, + UC_ARM_REG_Q11, + UC_ARM_REG_Q12, + UC_ARM_REG_Q13, + UC_ARM_REG_Q14, + UC_ARM_REG_Q15, + UC_ARM_REG_R0, + UC_ARM_REG_R1, + UC_ARM_REG_R2, + UC_ARM_REG_R3, + UC_ARM_REG_R4, + UC_ARM_REG_R5, + UC_ARM_REG_R6, + UC_ARM_REG_R7, + UC_ARM_REG_R8, + UC_ARM_REG_R9, + UC_ARM_REG_R10, + UC_ARM_REG_R11, + UC_ARM_REG_R12, + UC_ARM_REG_S0, + UC_ARM_REG_S1, + UC_ARM_REG_S2, + UC_ARM_REG_S3, + UC_ARM_REG_S4, + UC_ARM_REG_S5, + UC_ARM_REG_S6, + UC_ARM_REG_S7, + UC_ARM_REG_S8, + UC_ARM_REG_S9, + UC_ARM_REG_S10, + UC_ARM_REG_S11, + UC_ARM_REG_S12, + UC_ARM_REG_S13, + UC_ARM_REG_S14, + UC_ARM_REG_S15, + UC_ARM_REG_S16, + UC_ARM_REG_S17, + UC_ARM_REG_S18, + UC_ARM_REG_S19, + UC_ARM_REG_S20, + UC_ARM_REG_S21, + UC_ARM_REG_S22, + UC_ARM_REG_S23, + UC_ARM_REG_S24, + UC_ARM_REG_S25, + UC_ARM_REG_S26, + UC_ARM_REG_S27, + UC_ARM_REG_S28, + UC_ARM_REG_S29, + UC_ARM_REG_S30, + UC_ARM_REG_S31, + + UC_ARM_REG_C1_C0_2, + UC_ARM_REG_C13_C0_2, + UC_ARM_REG_C13_C0_3, + + UC_ARM_REG_IPSR, + UC_ARM_REG_MSP, + UC_ARM_REG_PSP, + UC_ARM_REG_CONTROL, + + // Fuzzware registers + UC_ARM_REG_XPSR, + UC_ARM_REG_OTHER_SP, + UC_ARM_REG_CURR_SP_MODE_IS_PSP, + UC_ARM_REG_SPSEL, + UC_ARM_REG_BASEPRI, + UC_ARM_REG_PRIMASK, + UC_ARM_REG_ENDING, // <-- mark the end of the list or registers + + //> alias registers + UC_ARM_REG_R13 = UC_ARM_REG_SP, + UC_ARM_REG_R14 = UC_ARM_REG_LR, + UC_ARM_REG_R15 = UC_ARM_REG_PC, + + UC_ARM_REG_SB = UC_ARM_REG_R9, + UC_ARM_REG_SL = UC_ARM_REG_R10, + UC_ARM_REG_FP = UC_ARM_REG_R11, + UC_ARM_REG_IP = UC_ARM_REG_R12, +} uc_arm_reg; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.c b/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.c index aba4004..7c9e4f9 100644 --- a/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.c +++ b/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.c @@ -1,4 +1,6 @@ #include "cortexm_nvic.h" +#include +#include // We implement recalculating states lazily, but can disable that behavior // #define DISABLE_LAZY_RECALCS @@ -266,7 +268,7 @@ void hook_nvic_mmio_read(uc_engine *uc, uc_mem_type type, out_val <<= 1; out_val |= nvic.ExceptionEnabled[base_ind + i]; } - uc_mem_write(uc, addr, &out_val, size); + uc->mem_write(uc->ctx, addr, &out_val, size); break; case NVIC_IREG_RANGE(NVIC_ISPR): // Interrupt Set-Pending Registers // Both NVIC_ISPR and NVIC_ICPR reads yield pending flags. @@ -275,14 +277,14 @@ void hook_nvic_mmio_read(uc_engine *uc, uc_mem_type type, out_val <<= 1; out_val |= nvic.ExceptionPending[base_ind + i]; } - uc_mem_write(uc, addr, &out_val, size); + uc->mem_write(uc->ctx, addr, &out_val, size); break; case NVIC_IREG_RANGE(NVIC_IABR): // Interrupt Active Bit Registers+ for(int i = size * 8 - 1; i >= 0; --i) { out_val <<= 1; out_val |= nvic.ExceptionActive[base_ind * 4 + i]; } - uc_mem_write(uc, addr, &out_val, size); + uc->mem_write(uc->ctx, addr, &out_val, size); break; case NVIC_IPR_RANGE(NVIC_IPR): // Interrupt Priority Registers base_ind = EXCEPTION_NO_EXTERNAL_START + ((access_offset - NVIC_IPR) & 0x1ff); @@ -291,7 +293,7 @@ void hook_nvic_mmio_read(uc_engine *uc, uc_mem_type type, out_val <<= 8; out_val |= nvic.ExceptionPriority[base_ind + i]; } - uc_mem_write(uc, addr, &out_val, size); + uc->mem_write(uc->ctx, addr, &out_val, size); } break; default: @@ -555,11 +557,11 @@ void hook_sysctl_mmio_read(uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *user_data) { #ifdef DEBUG_NVIC value = 0; - uc_mem_read(uc, addr, &value, size); + uc->mem_read(uc->ctx, addr, &value, size); printf("[NVIC] hook_sysctl_mmio_read: Read from %08lx, raw value: %08lx\n", addr, value); fflush(stdout); #endif - + if(addr >= SYSTICK_BASE && addr <= SYSTICK_END) { hook_syst_mmio_read(uc, type, addr, size, value, user_data); return; @@ -573,11 +575,11 @@ void hook_sysctl_mmio_read(uc_engine *uc, uc_mem_type type, switch(addr & ~3) { case SYSCTL_ICTR: // Interrupt Controller Type Register // number of supported interrupts - uc_mem_write(uc, addr, &intlinesnum, sizeof(intlinesnum)); + uc->mem_write(uc->ctx, addr, &intlinesnum, sizeof(intlinesnum)); break; case SYSCTL_ICSR: // Interrupt Control and State Register out_val = calc_icsr(); - uc_mem_write(uc, addr, &out_val, sizeof(out_val)); + uc->mem_write(uc->ctx, addr, &out_val, sizeof(out_val)); break; case SYSCTL_VTOR: // Vector Table Offset Register // NOP: fall through to normal read @@ -590,7 +592,7 @@ void hook_sysctl_mmio_read(uc_engine *uc, uc_mem_type type, #ifdef DEBUG_NVIC printf("Generated out_val for SYSCTL_AIRCR: %#010x\n", out_val); fflush(stdout); #endif - uc_mem_write(uc, addr, &out_val, sizeof(out_val)); + uc->mem_write(uc->ctx, addr, &out_val, sizeof(out_val)); break; case SYSCTL_STIR: // Software Triggered Interrupt Register // Ignore, write-only @@ -610,7 +612,7 @@ void hook_sysctl_mmio_read(uc_engine *uc, uc_mem_type type, out_val |= nvic.ExceptionPriority[base_ind + i]; } - uc_mem_write(uc, addr, &out_val, size); + uc->mem_write(uc->ctx, addr, &out_val, size); break; default: break; @@ -658,14 +660,15 @@ static void handle_aircr_write(uc_engine *uc, uint32_t value) { if(do_print_exit_info) { puts("SYSCTL_AIRCR write indicated system reset, stopping emulation"); } - do_exit(uc, UC_ERR_EXCEPTION); + // do_exit(uc, UC_ERR_EXCEPTION); + do_exit(uc, UC_ERR_OK); } // PRIGROUP uint32_t new_prigroup = (value & SCB_AIRCR_PRIGROUP_Msk) >> SCB_AIRCR_PRIGROUP_Pos; if(new_prigroup != nvic.prigroup_shift) { #ifdef DEBUG_NVIC - printf("[NVIC] SYSCTL_AIRCR write: Setting prigroup to new value. Old value: %#04x, new value: %#04x\n", new_prigroup, nvic.prigroup_shift); + printf("[NVIC] SYSCTL_AIRCR write: Setting prigroup to new value. Old value: %#04x, new value: %#04x\n", nvic.prigroup_shift, new_prigroup); fflush(stdout); #endif set_prigroup(new_prigroup); @@ -682,7 +685,9 @@ static void handle_aircr_write(uc_engine *uc, uint32_t value) { void hook_sysctl_mmio_write(uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *user_data) { #ifdef DEBUG_NVIC - printf("[NVIC] hook_sysctl_mmio_write: Write to %08lx, value: %08lx\n", addr, value); + uint32_t pc = 0; + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); + printf("[NVIC] hook_sysctl_mmio_write: 0x%x Write to %08lx, value: %08lx\n", pc, addr, value); fflush(stdout); #endif @@ -753,7 +758,7 @@ void hook_sysctl_mmio_write(uc_engine *uc, uc_mem_type type, // Armv7-M ARM B1.5.8 void PopStack(uc_engine *uc) { uint32_t frameptr; - uc_reg_read(uc, UC_ARM_REG_SP, &frameptr); + uc->reg_read(uc->ctx, UC_ARM_REG_SP, &frameptr); uc_err err; #ifdef DEBUG_NVIC @@ -761,7 +766,7 @@ void PopStack(uc_engine *uc) { print_state(uc); #endif - if((err = uc_mem_read(uc, frameptr, &saved_regs, FRAME_SIZE)) != UC_ERR_OK) { + if((err = uc->mem_read(uc->ctx, frameptr, &saved_regs, FRAME_SIZE)) != UC_ERR_OK) { if(do_print_exit_info) { printf("[NVIC] PopStack: reading saved context frame during interrupt exit failed for frameptr= 0x%08x: (%s)\n", frameptr, uc_strerror(err)); fflush(stdout); @@ -776,9 +781,10 @@ void PopStack(uc_engine *uc) { { saved_regs.sp += 4; } + saved_regs.xpsr_retspr &= ~(1 << 9); // Here we restore all registers in one go, including sp - if((err = uc_reg_write_batch(uc, &saved_reg_ids[0], (void **)(&saved_reg_ptrs[0]), NUM_SAVED_REGS)) != UC_ERR_OK){ + if((err = uc->reg_write_batch(uc->ctx, &saved_reg_ids[0], (void **)(&saved_reg_ptrs[0]), NUM_SAVED_REGS)) != UC_ERR_OK){ if(do_print_exit_info) { puts("[NVIC ERROR] PopStack: restoring registers failed\n"); print_state(uc); @@ -820,7 +826,7 @@ void PushStack(uc_engine *uc, bool skip_instruction) { uint32_t spmask = ~(1 << 2); // Read the registers which are to be pushed afterwards - if((err = uc_reg_read_batch(uc, &saved_reg_ids[0], (void **)(&saved_reg_ptrs[0]), NUM_SAVED_REGS)) != UC_ERR_OK) { + if((err = uc->reg_read_batch(uc->ctx, &saved_reg_ids[0], (void **)(&saved_reg_ptrs[0]), NUM_SAVED_REGS)) != UC_ERR_OK) { if(do_print_exit_info) { puts("[NVIC ERROR] PushStack: Failed reading registers\n"); fflush(stdout); @@ -833,7 +839,7 @@ void PushStack(uc_engine *uc, bool skip_instruction) { #ifdef DEBUG_NVIC uint32_t prev_pc = saved_regs.pc_retaddr; #endif - uc_mem_read(uc, saved_regs.pc_retaddr, &insn, 2); + uc->mem_read(uc->ctx, saved_regs.pc_retaddr, &insn, 2); saved_regs.pc_retaddr += get_instruction_size(insn, true); #ifdef DEBUG_NVIC @@ -846,13 +852,16 @@ void PushStack(uc_engine *uc, bool skip_instruction) { frameptr = (saved_regs.sp - FRAME_SIZE) & spmask; // Save the stack pointer with additional space - uc_reg_write(uc, UC_ARM_REG_SP, &frameptr); + uc->reg_write(uc->ctx, UC_ARM_REG_SP, &frameptr); + #ifdef DEBUG_NVIC + printf("[PushStack] adjusted sp from 0x%x to 0x%x\n", saved_regs.sp, frameptr); fflush(stdout); + #endif // Adjust xpsr with alignment info saved_regs.xpsr_retspr |= (frameptralign << 9); // Push the context frame itself - if((err = uc_mem_write(uc, frameptr, &saved_regs, (NUM_SAVED_REGS - 1)*sizeof(saved_regs.r0))) != UC_ERR_OK){ + if((err = uc->mem_write(uc->ctx, frameptr, &saved_regs, (NUM_SAVED_REGS - 1)*sizeof(saved_regs.r0))) != UC_ERR_OK){ if(do_print_exit_info) { printf("[NVIC] PopStack: writing saved context frame during interrupt entry failed (INVALID WRITE, frameptr= 0x%08x)\n", frameptr); print_state(uc); @@ -890,13 +899,13 @@ void ExceptionReturn(uc_engine *uc, uint32_t ret_pc) { #ifdef DEBUG_NVIC uint32_t sp_mode, other_sp, sp, lr; sp_mode = GET_CURR_SP_MODE_IS_PSP(); - uc_reg_read(uc, UC_ARM_REG_OTHER_SP, &other_sp); - uc_reg_read(uc, UC_ARM_REG_SP, &sp); - uc_reg_read(uc, UC_ARM_REG_LR, &lr); + uc->reg_read(uc->ctx, UC_ARM_REG_OTHER_SP, &other_sp); + uc->reg_read(uc->ctx, UC_ARM_REG_SP, &sp); + uc->reg_read(uc->ctx, UC_ARM_REG_LR, &lr); printf("[ExceptionReturn] UC_ARM_REG_CURR_SP_MODE_IS_PSP=%d, UC_ARM_REG_OTHER_SP=%08x, UC_ARM_REG_SP=%08x, lr=%08x\n", sp_mode, other_sp, sp, lr); fflush(stdout); #endif - /* + /* * After deactivating the exception, re-calc to see if a * pending exception can now be taken. */ @@ -920,18 +929,18 @@ void ExceptionReturn(uc_engine *uc, uint32_t ret_pc) { // return to Thread Mode which uses SP_process. Switch to SP_process uint32_t new_SPSEL_now_psp = 1; uint32_t SP_process, SP_main; - uc_reg_read(uc, UC_ARM_REG_SP, &SP_main); - uc_reg_read(uc, UC_ARM_REG_OTHER_SP, &SP_process); + uc->reg_read(uc->ctx, UC_ARM_REG_SP, &SP_main); + uc->reg_read(uc->ctx, UC_ARM_REG_OTHER_SP, &SP_process); // Back up SP_main - uc_reg_write(uc, UC_ARM_REG_OTHER_SP, &SP_main); - uc_reg_write(uc, UC_ARM_REG_SP, &SP_process); + uc->reg_write(uc->ctx, UC_ARM_REG_OTHER_SP, &SP_main); + uc->reg_write(uc->ctx, UC_ARM_REG_SP, &SP_process); // Switch the CPU state to indicate the new SPSEL state // 1. In pstate register - uc_reg_write(uc, UC_ARM_REG_SPSEL, &new_SPSEL_now_psp); + uc->reg_write(uc->ctx, UC_ARM_REG_SPSEL, &new_SPSEL_now_psp); // 2. In cached spsel field - uc_reg_write(uc, UC_ARM_REG_CURR_SP_MODE_IS_PSP, &new_SPSEL_now_psp); + uc->reg_write(uc->ctx, UC_ARM_REG_CURR_SP_MODE_IS_PSP, &new_SPSEL_now_psp); } } @@ -948,7 +957,7 @@ void ExceptionReturn(uc_engine *uc, uint32_t ret_pc) { static void nvic_exception_return_hook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { #ifdef DEBUG_NVIC uint32_t lr; - uc_reg_read(uc, UC_ARM_REG_LR, &lr); + uc->reg_read(uc->ctx, UC_ARM_REG_LR, &lr); printf("#################### Returning from interrupt (addr: 0x%lx, lr: 0x%08x)...\n", address, lr); fflush(stdout); #endif @@ -956,7 +965,7 @@ static void nvic_exception_return_hook(uc_engine *uc, uint64_t address, uint32_t #ifdef DEBUG_NVIC uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("############## Returned from interrupt. From: 0x%08lx to 0x%08x\n", address, pc); fflush(stdout); fflush(stdout); #endif @@ -965,7 +974,7 @@ static void nvic_exception_return_hook(uc_engine *uc, uint64_t address, uint32_t static void handler_svc(uc_engine *uc, uint32_t intno, void *user_data) { #ifdef DEBUG_NVIC uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("[SVC HOOK %08x] native SVC hook called, intno: %d\n", pc, intno); fflush(stdout); #endif @@ -975,7 +984,7 @@ static void handler_svc(uc_engine *uc, uint32_t intno, void *user_data) { if(nvic.active_group_prio <= nvic.ExceptionPriority[EXCEPTION_NO_SVC]) { if(do_print_exit_info) { uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("[SVC HOOK %08x] primask is set, so interrupts are masked. SVC prio: %d. As this would escalate to hardfault, exiting\n", pc, nvic.ExceptionPriority[EXCEPTION_NO_SVC]); fflush(stdout); } do_exit(uc, UC_ERR_EXCEPTION); @@ -989,7 +998,7 @@ static void handler_svc(uc_engine *uc, uint32_t intno, void *user_data) { // Alternatives could be breakpoints and the like, which we do not handle. if(do_print_exit_info) { uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("[SVC HOOK %08x] %d is NOT an SVC, exiting\n", pc, intno); fflush(stdout); } do_exit(uc, UC_ERR_OK); @@ -1036,18 +1045,22 @@ static void ExceptionEntry(uc_engine *uc, bool is_tail_chained, bool skip_instru // We are coming from Thread Mode which uses SP_process. Switch it to SP_main uint32_t new_SPSEL_not_psp = 0; uint32_t SP_process, SP_main; - uc_reg_read(uc, UC_ARM_REG_SP, &SP_process); - uc_reg_read(uc, UC_ARM_REG_OTHER_SP, &SP_main); + uc->reg_read(uc->ctx, UC_ARM_REG_SP, &SP_process); + uc->reg_read(uc->ctx, UC_ARM_REG_OTHER_SP, &SP_main); + + #ifdef DEBUG_NVIC + printf("[NVIC] switching from SP_process: %x to SP_main: %x\n", SP_process, SP_main); fflush(stdout); + #endif // Back up SP_process - uc_reg_write(uc, UC_ARM_REG_OTHER_SP, &SP_process); - uc_reg_write(uc, UC_ARM_REG_SP, &SP_main); + uc->reg_write(uc->ctx, UC_ARM_REG_OTHER_SP, &SP_process); + uc->reg_write(uc->ctx, UC_ARM_REG_SP, &SP_main); // Switch the CPU state to indicate the new SPSEL state // 1. In pstate register - uc_reg_write(uc, UC_ARM_REG_SPSEL, &new_SPSEL_not_psp); + uc->reg_write(uc->ctx, UC_ARM_REG_SPSEL, &new_SPSEL_not_psp); // 2. In cached spsel field - uc_reg_write(uc, UC_ARM_REG_CURR_SP_MODE_IS_PSP, &new_SPSEL_not_psp); + uc->reg_write(uc->ctx, UC_ARM_REG_CURR_SP_MODE_IS_PSP, &new_SPSEL_not_psp); // Finally: Indicate that we switched in the LR value new_lr |= NVIC_INTERRUPT_ENTRY_LR_PSPSWITCH_FLAG; @@ -1057,23 +1070,23 @@ static void ExceptionEntry(uc_engine *uc, bool is_tail_chained, bool skip_instru // Tail Chaining: going from handler mode to handler mode. No stack switching required uint32_t prev_lr; // If we are chained, maintain the previous lr's SP switch and thread mode bits - uc_reg_read(uc, UC_ARM_REG_PC, &prev_lr); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &prev_lr); new_lr |= (prev_lr & (NVIC_INTERRUPT_ENTRY_LR_PSPSWITCH_FLAG | NVIC_INTERRUPT_ENTRY_LR_THREADMODE_FLAG)); } // In any case we need to set our new LR - uc_reg_write(uc, UC_ARM_REG_LR, &new_lr); + uc->reg_write(uc->ctx, UC_ARM_REG_LR, &new_lr); // We inline ExceptionTaken here // Find the ISR entry point and set it uint32_t ExceptionNumber = nvic.pending_irq; uint32_t isr_entry; - uc_mem_read(uc, nvic.vtor + 4 * ExceptionNumber, &isr_entry, sizeof(isr_entry)); - uc_reg_write(uc, UC_ARM_REG_PC, &isr_entry); + uc->mem_read(uc->ctx, nvic.vtor + 4 * ExceptionNumber, &isr_entry, sizeof(isr_entry)); + uc->reg_write(uc->ctx, UC_ARM_REG_PC, &isr_entry); #ifdef DEBUG_NVIC - printf("Redirecting irq %d to isr: %08x\n", ExceptionNumber, isr_entry); + printf("Redirecting irq %d to isr: %08x lr: %08x\n", ExceptionNumber, isr_entry, new_lr); #endif // Prepare new XPSR state @@ -1083,7 +1096,7 @@ static void ExceptionEntry(uc_engine *uc, bool is_tail_chained, bool skip_instru // Set active interrupt isr_xpsr &= ~xPSR_ISR_Msk; isr_xpsr |= ExceptionNumber; - uc_reg_write(uc, UC_ARM_REG_XPSR, &isr_xpsr); + uc->reg_write(uc->ctx, UC_ARM_REG_XPSR, &isr_xpsr); // Update nvic state with new active interrupt nvic.ExceptionActive[ExceptionNumber] = 1; @@ -1124,7 +1137,7 @@ static void nvic_block_hook(uc_engine *uc, uint64_t address, uint32_t size, stru uint32_t basepri = GET_BASEPRI_NVIC(arg_nvic); #ifdef DEBUG_NVIC - printf("basepri == %d, primask == 0\n", basepri); fflush(stdout); + // printf("basepri == %d, primask == 0\n", basepri); fflush(stdout); #endif // Interrupts have previously been entirely disabled @@ -1228,7 +1241,7 @@ void *nvic_take_snapshot(uc_engine *uc) { // NVIC snapshot: save the sysreg mem page char *result = malloc(size + PAGE_SIZE); memcpy(result, &nvic, size); - uc_mem_read(uc, SYSCTL_START, result + size, PAGE_SIZE); + uc->mem_read(uc->ctx, SYSCTL_START, result + size, PAGE_SIZE); return result; } @@ -1237,7 +1250,7 @@ void nvic_restore_snapshot(uc_engine *uc, void *snapshot) { // Restore the nvic memcpy(&nvic, snapshot, sizeof(nvic)); // Restore the sysreg mem page - uc_mem_write(uc, SYSCTL_START, ((char *) snapshot) + sizeof(nvic), PAGE_SIZE); + uc->mem_write(uc->ctx, SYSCTL_START, ((char *) snapshot) + sizeof(nvic), PAGE_SIZE); } void nvic_discard_snapshot(uc_engine *uc, void *snapshot) { @@ -1281,35 +1294,35 @@ uc_err init_nvic(uc_engine *uc, uint32_t vtor, uint32_t num_irq, uint32_t p_inte config_disabled_interrupts[i] = EXCEPTION_NO_EXTERNAL_START + disabled_interrupts[i]; // Get pointers to commonly used registers - if(uc_reg_ptr(uc, UC_ARM_REG_PRIMASK, (void **) &nvic.reg_daif_ptr)) { + if(uc->reg_ptr(uc->ctx, UC_ARM_REG_PRIMASK, (void **) &nvic.reg_daif_ptr)) { puts("[init_nvic] ERROR: uc_reg_tr"); exit(-1); } - if(uc_reg_ptr(uc, UC_ARM_REG_BASEPRI, (void **) &nvic.reg_basepri_ptr)) { + if(uc->reg_ptr(uc->ctx, UC_ARM_REG_BASEPRI, (void **) &nvic.reg_basepri_ptr)) { puts("[init_nvic] ERROR: uc_reg_tr"); exit(-1); } - if(uc_reg_ptr(uc, UC_ARM_REG_CURR_SP_MODE_IS_PSP, (void **) ®_curr_sp_mode_is_psp_ptr)) { + if(uc->reg_ptr(uc->ctx, UC_ARM_REG_CURR_SP_MODE_IS_PSP, (void **) ®_curr_sp_mode_is_psp_ptr)) { puts("[init_nvic] ERROR: uc_reg_tr"); exit(-1); } // Set the vtor. If it is uninitialized, read it from actual (restored) process memory if(vtor == NVIC_VTOR_NONE) { - uc_mem_read(uc, SYSCTL_VTOR, &nvic.vtor, sizeof(nvic.vtor)); + uc->mem_read(uc->ctx, SYSCTL_VTOR, &nvic.vtor, sizeof(nvic.vtor)); printf("[NVIC] Recovered vtor base: %x\n", nvic.vtor); fflush(stdout); } else { // We have MMIO vtor read fall through, so put vtor value in emulated memory - uc_mem_write(uc, SYSCTL_VTOR, &nvic.vtor, sizeof(nvic.vtor)); + uc->mem_write(uc->ctx, SYSCTL_VTOR, &nvic.vtor, sizeof(nvic.vtor)); nvic.vtor = vtor; } - uc_hook_add(uc, &nvic_exception_return_hook_handle, UC_HOOK_BLOCK, nvic_exception_return_hook, NULL, EXCEPT_MAGIC_RET_MASK, EXCEPT_MAGIC_RET_MASK | 0xf); + uc->block_hook_add(uc->ctx, &nvic_exception_return_hook_handle, UC_HOOK_BLOCK, nvic_exception_return_hook, NULL, EXCEPT_MAGIC_RET_MASK, EXCEPT_MAGIC_RET_MASK | 0xf); - uc_hook_add(uc, &nvic_block_hook_handle, UC_HOOK_BLOCK_UNCONDITIONAL, nvic_block_hook, &nvic, 1, 0); + uc->block_hook_add(uc->ctx, &nvic_block_hook_handle, UC_HOOK_BLOCK_UNCONDITIONAL, nvic_block_hook, &nvic, 1, 0); // 3. nvic MMIO range read/write handler - uc_hook_add(uc, &hook_mmio_write_handle, UC_HOOK_MEM_WRITE, hook_sysctl_mmio_write, NULL, SYSCTL_MMIO_BASE, SYSCTL_MMIO_END); - uc_hook_add(uc, &hook_mmio_read_handle, UC_HOOK_MEM_READ, hook_sysctl_mmio_read, NULL, SYSCTL_MMIO_BASE, SYSCTL_MMIO_END); + uc->mem_hook_add(uc->ctx, &hook_mmio_write_handle, UC_HOOK_MEM_WRITE, hook_sysctl_mmio_write, NULL, SYSCTL_MMIO_BASE, SYSCTL_MMIO_END); + uc->mem_hook_add(uc->ctx, &hook_mmio_read_handle, UC_HOOK_MEM_READ, hook_sysctl_mmio_read, NULL, SYSCTL_MMIO_BASE, SYSCTL_MMIO_END); - uc_hook_add(uc, &hook_svc_handle, UC_HOOK_INTR, handler_svc, NULL, 1, 0); + uc->int_hook_add(uc->ctx, &hook_svc_handle, UC_HOOK_INTR, handler_svc, NULL, 1, 0); subscribe_state_snapshotting(uc, nvic_take_snapshot, nvic_restore_snapshot, nvic_discard_snapshot); diff --git a/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.h b/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.h index a41e0aa..ea301f8 100644 --- a/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.h +++ b/harness/fuzzware_harness/native/core_peripherals/cortexm_nvic.h @@ -4,7 +4,7 @@ #include #include -#include "unicorn/unicorn.h" +#include "../unicorn.h" #include "cortexm_exception_nums.h" #include "cortexm_systick.h" @@ -79,7 +79,7 @@ struct CortexmNVIC { // We put some members to the front as they are required in the basic block hot path - + // Direct access pointers for interrupt disable / base priority flags uint8_t *reg_daif_ptr; int32_t *reg_basepri_ptr; diff --git a/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.c b/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.c index d017c2f..e0e070e 100644 --- a/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.c +++ b/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.c @@ -1,4 +1,5 @@ #include "cortexm_systick.h" +#include // 0. Constants uint32_t calibration_val = SYSTICK_TICKS_10_MS; @@ -23,9 +24,9 @@ void hook_syst_mmio_read(uc_engine *uc, uc_mem_type type, switch (access_offset) { case REG_OFF_SYST_CSR: // Simply return value here - uc_mem_write(uc, addr, &systick.csr, sizeof(systick.csr)); + uc->mem_write(uc->ctx, addr, &systick.csr, sizeof(systick.csr)); /* - * HACK (non-standard behavior): + * HACK (non-standard behavior): * In case firmware explicitly asks whether time has passed * multiple times within one systick period, indicate that it has. * This makes time go faster for firmware waiting in busy loops via @@ -36,15 +37,15 @@ void hook_syst_mmio_read(uc_engine *uc, uc_mem_type type, case REG_OFF_SYST_RVR: // Strictly speaking only 24 bits are used for the reload val out_val = get_timer_reload_val(systick.timer_ind) & SYST_RELOAD_VAL_MASK; - uc_mem_write(uc, addr, &out_val, sizeof(out_val)); + uc->mem_write(uc->ctx, addr, &out_val, sizeof(out_val)); break; case REG_OFF_SYST_CVR: // Strictly speaking only 24 bits are used for the reload val out_val = get_timer_ticker_val(systick.timer_ind) & SYST_RELOAD_VAL_MASK; - uc_mem_write(uc, addr, &out_val, sizeof(out_val)); + uc->mem_write(uc->ctx, addr, &out_val, sizeof(out_val)); break; case REG_OFF_SYST_CALIB: - uc_mem_write(uc, addr, &calibration_val, sizeof(calibration_val)); + uc->mem_write(uc->ctx, addr, &calibration_val, sizeof(calibration_val)); break; default: break; @@ -113,7 +114,7 @@ void hook_syst_mmio_write(uc_engine *uc, uc_mem_type type, /* * https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/system-timer--systick/systick-control-and-status-register?lang=en - * + * * When ENABLE is set to 1, the counter loads the RELOAD value from the SYST_RVR register and then counts down. * On reaching 0, it sets the COUNTFLAG to 1 and optionally asserts the SysTick depending on the value of TICKINT. * It then loads the RELOAD value again, and begins counting. diff --git a/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.h b/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.h index 0e1aca7..83be058 100644 --- a/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.h +++ b/harness/fuzzware_harness/native/core_peripherals/cortexm_systick.h @@ -1,7 +1,7 @@ #ifndef CORTEXM_SYSTICK_H #define CORTEXM_SYSTICK_H -#include "unicorn/unicorn.h" +#include "../unicorn.h" #include "cortexm_exception_nums.h" #include "cmsis/core_cm3.h" #include "cortexm_nvic.h" @@ -29,7 +29,7 @@ #define SYSTICK_TICKS_10_MS 500 struct CortexmSysTick { - /* + /* * We treat SysTick as a timer. From that abstraction we will also query * data such as reload values. */ diff --git a/harness/fuzzware_harness/native/interrupt_triggers.c b/harness/fuzzware_harness/native/interrupt_triggers.c index c16c5d9..8a3c472 100644 --- a/harness/fuzzware_harness/native/interrupt_triggers.c +++ b/harness/fuzzware_harness/native/interrupt_triggers.c @@ -3,6 +3,8 @@ #include "core_peripherals/cortexm_nvic.h" #include "timer.h" #include +#include +#include // 0. Constants #define MAX_INTERRUPT_TRIGGERS 256 @@ -160,7 +162,7 @@ uc_hook add_interrupt_trigger(uc_engine *uc, uint64_t addr, uint32_t irq, uint32 trigger->trigger_mode = trigger_mode; if(trigger_mode == IRQ_TRIGGER_MODE_ADDRESS) { - if (uc_hook_add(uc, &trigger->hook_handle, UC_HOOK_BLOCK, (void *)interrupt_trigger_tick_block_hook, trigger, addr, addr) != UC_ERR_OK) { + if (uc->block_hook_add(uc->ctx, &trigger->hook_handle, UC_HOOK_BLOCK, (void *)interrupt_trigger_tick_block_hook, trigger, addr, addr) != UC_ERR_OK) { perror("[INTERRUPT_TRIGGERS ERROR] Failed adding block hook.\n"); exit(-1); } diff --git a/harness/fuzzware_harness/native/interrupt_triggers.h b/harness/fuzzware_harness/native/interrupt_triggers.h index 9eb22b6..7456435 100644 --- a/harness/fuzzware_harness/native/interrupt_triggers.h +++ b/harness/fuzzware_harness/native/interrupt_triggers.h @@ -1,7 +1,7 @@ #ifndef INTERRUPT_TRIGGERS_H #define INTERRUPT_TRIGGERS_H -#include +#include "unicorn.h" #define IRQ_FUZZ_MODE_FIXED 0 #define IRQ_FUZZ_MODE_FUZZ_ENABLED_IRQ_INDEX 1 diff --git a/harness/fuzzware_harness/native/native_hooks.c b/harness/fuzzware_harness/native/native_hooks.c index 12edd5d..4f18dd1 100644 --- a/harness/fuzzware_harness/native/native_hooks.c +++ b/harness/fuzzware_harness/native/native_hooks.c @@ -14,8 +14,10 @@ #include "state_snapshotting.h" #include "uc_snapshot.h" -#include +#include "unicorn.h" +#include +#include #include #include #include @@ -46,6 +48,8 @@ #define SHM_FUZZ_ENV_VAR "__AFL_SHM_FUZZ_ID" #define FS_OPT_SHDMEM_FUZZ 0x01000000 #define FS_OPT_ENABLED 0x80000001 +#define FS_OPT_NEWCMPLOG 0x02000000 + #define CPUID_ADDR 0xE000ED00 const int CPUID_CORTEX_M4=0x410fc240; @@ -138,13 +142,13 @@ void do_exit(uc_engine *uc, uc_err err) { if(!duplicate_exit) { custom_exit_reason = err; duplicate_exit = true; - uc_emu_stop(uc); + uc->emu_stop(uc->ctx); } } void hook_block_debug(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { uint32_t lr; - uc_reg_read(uc, UC_ARM_REG_LR, &lr); + uc->reg_read(uc->ctx, UC_ARM_REG_LR, &lr); printf("Basic Block: addr= 0x%016lx (lr=0x%x)\n", address, lr); fflush(stdout); @@ -153,8 +157,8 @@ void hook_block_debug(uc_engine *uc, uint64_t address, uint32_t size, void *user void hook_debug_mem_access(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { uint32_t pc, sp; - uc_reg_read(uc, UC_ARM_REG_SP, &sp); - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_SP, &sp); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); int64_t sp_offset = sp - address; if(sp_offset > -0x1000 && sp_offset < 0x2000) { @@ -162,7 +166,7 @@ void hook_debug_mem_access(uc_engine *uc, uc_mem_type type, printf(" >>> Write: addr= 0x%08lx[SP:%c%04lx] size=%d data=0x%08lx (pc 0x%08x)\n", address, sp_offset >= 0 ? '+' : '-', sp_offset >= 0 ? sp_offset : -sp_offset, size, value, pc); } else { uint32_t read_value = 0; - uc_mem_read(uc, address, &read_value, size); + uc->mem_read(uc->ctx, address, &read_value, size); printf(" >>> Read: addr= 0x%08lx[SP:%c%04lx] size=%d data=0x%08x (pc 0x%08x)\n", address, sp_offset >= 0 ? '+' : '-', sp_offset >= 0 ? sp_offset : -sp_offset, size, read_value, pc); } } else { @@ -170,7 +174,7 @@ void hook_debug_mem_access(uc_engine *uc, uc_mem_type type, printf(" >>> Write: addr= 0x%016lx size=%d data=0x%08lx (pc 0x%08x)\n", address, size, value, pc); } else { uint32_t read_value = 0; - uc_mem_read(uc, address, &read_value, size); + uc->mem_read(uc->ctx, address, &read_value, size); printf(" >>> Read: addr= 0x%016lx size=%d data=0x%08x (pc 0x%08x)\n", address, size, read_value, pc); } } @@ -181,15 +185,15 @@ uc_err add_debug_hooks(uc_engine *uc) { uc_hook tmp; uc_err res = UC_ERR_OK; // Register unconditional hook for checking for handler presence - res |= uc_hook_add(uc, &tmp, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_debug, NULL, 1, 0); - res |= uc_hook_add(uc, &tmp, UC_HOOK_MEM_WRITE | UC_HOOK_MEM_READ, hook_debug_mem_access, 0, 1, 0); + res |= uc->block_hook_add(uc->ctx, &tmp, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_debug, NULL, 1, 0); + res |= uc->mem_hook_add(uc->ctx, &tmp, UC_HOOK_MEM_WRITE | UC_HOOK_MEM_READ, hook_debug_mem_access, 0, 1, 0); return res; } bool hook_debug_mem_invalid_access(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { uint64_t pc = 0; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); if(type == UC_MEM_WRITE_UNMAPPED || type == UC_MEM_WRITE_PROT) { printf(" >>> [ 0x%08lx ] INVALID Write: addr= 0x%016lx size=%d data=0x%016lx\n", pc, address, size, value); } else if (type == UC_MEM_READ_UNMAPPED || type == UC_MEM_READ_PROT){ @@ -355,7 +359,7 @@ void hook_mmio_access(uc_engine *uc, uc_mem_type type, uint32_t pc = 0; latest_mmio_fuzz_access_index = fuzz_cursor; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); // TODO: optimize this lookup for (int i = 0; i < num_ignored_addresses; ++i) @@ -393,7 +397,7 @@ void hook_mmio_access(uc_engine *uc, uc_mem_type type, #ifdef DEBUG printf(", value: 0x%lx\n", val); fflush(stdout); #endif - uc_mem_write(uc, addr, (uint8_t *)&val, size); + uc->mem_write(uc->ctx, addr, (uint8_t *)&val, size); out: @@ -417,7 +421,7 @@ uc_err add_mmio_region(uc_engine *uc, uint64_t begin, uint64_t end) { uc_hook tmp; printf("add_mmio_region called! hooking 0x%08lx - 0x%08lx\n", begin, end); - return uc_hook_add(uc, &tmp, UC_HOOK_MEM_READ, hook_mmio_access, py_default_mmio_user_data, begin, end); + return uc->mem_hook_add(uc->ctx, &tmp, UC_HOOK_MEM_READ, hook_mmio_access, py_default_mmio_user_data, begin, end); } void hook_block_cond_py_handlers(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { @@ -461,7 +465,7 @@ uc_err register_cond_py_handler_hook(uc_engine *uc, uc_cb_hookcode_t py_mmio_cal } // Register unconditional hook for checking for handler presence - return uc_hook_add(uc, &hook_block_cond_py_handlers_handle, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_cond_py_handlers, user_data, 1, 0); + return uc->block_hook_add(uc->ctx, &hook_block_cond_py_handlers_handle, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_cond_py_handlers, user_data, 1, 0); } uc_err remove_function_handler_hook_address(uc_engine *uc, uint64_t address) { @@ -475,7 +479,7 @@ uc_err remove_function_handler_hook_address(uc_engine *uc, uint64_t address) { --num_handlers; // Now fully remove the (unconditional) hook if we can if(!num_handlers) { - uc_hook_del(uc, hook_block_cond_py_handlers_handle); + uc->hook_del(uc->ctx, hook_block_cond_py_handlers_handle); } return UC_ERR_OK; } @@ -511,11 +515,11 @@ void linear_mmio_model_handler(uc_engine *uc, uc_mem_type type, uint64_t addr, i #ifdef DEBUG uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("[0x%08x] Native Linear MMIO handler: [0x%08lx] = [0x%x]\n", pc, addr, model_state->val); fflush(stdout); #endif - uc_mem_write(uc, addr, &model_state->val, sizeof(model_state->val)); + uc->mem_write(uc->ctx, addr, &model_state->val, sizeof(model_state->val)); } void constant_mmio_model_handler(uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *user_data) { @@ -524,12 +528,12 @@ void constant_mmio_model_handler(uc_engine *uc, uc_mem_type type, uint64_t addr, #ifdef DEBUG uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("[0x%08x] Native Constant MMIO handler: [0x%08lx] = [0x%lx]\n", pc, addr, val); fflush(stdout); #endif // TODO: This assumes shared endianness between host and target - uc_mem_write(uc, addr, &val, size); + uc->mem_write(uc->ctx, addr, &val, size); } void bitextract_mmio_model_handler(uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *user_data) @@ -544,11 +548,11 @@ void bitextract_mmio_model_handler(uc_engine *uc, uc_mem_type type, uint64_t add } result_val = fuzzer_val << config->left_shift; - uc_mem_write(uc, addr, &result_val, size); + uc->mem_write(uc->ctx, addr, &result_val, size); #ifdef DEBUG uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("[0x%08x] Native Bitextract MMIO handler: [0x%08lx] = [0x%lx] from %d byte input: %lx\n", pc, addr, result_val, config->byte_size, fuzzer_val); fflush(stdout); #endif } @@ -560,7 +564,7 @@ void value_set_mmio_model_handler(uc_engine *uc, uc_mem_type type, uint64_t addr uint8_t fuzzer_val = 0; #ifdef DEBUG uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); #endif if(config->num_vals > 1) { @@ -585,7 +589,7 @@ void value_set_mmio_model_handler(uc_engine *uc, uc_mem_type type, uint64_t addr fflush(stdout); #endif - uc_mem_write(uc, addr, (uint8_t *)&result_val, size); + uc->mem_write(uc->ctx, addr, (uint8_t *)&result_val, size); } uc_err register_constant_mmio_models(uc_engine *uc, uint64_t *starts, uint64_t *ends, uint32_t *pcs, uint32_t *vals, int num_ranges) { @@ -663,7 +667,7 @@ uc_err register_value_set_mmio_models(uc_engine *uc, uint64_t *starts, uint64_t for (int i = 0; i < num_ranges; ++i) { #ifdef DEBUG uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("Registering value set model: [%x] %lx - %lx with numvalues, value_set: %d, [", pcs[i], starts[i], ends[i], value_nums[i]); for (uint32_t j = 0; j < value_nums[i]; ++j) { if(j) { @@ -789,6 +793,9 @@ static void *init_bitmap(uc_engine *uc) { char *id_str; int shm_id; + // Indicate to AFL++ that we support `NEWCMPLOG` mode. + tmp |= FS_OPT_NEWCMPLOG; + /* Tell AFL once that we are here */ id_str = getenv(SHM_ENV_VAR); if (id_str) { @@ -803,6 +810,24 @@ static void *init_bitmap(uc_engine *uc) { if(write(FORKSRV_FD + 1, &tmp, 4) == 4) { do_fuzz = 1; + + id_str = getenv(SHM_FUZZ_ENV_VAR); + if (id_str) { + // When shared memory mode is enabled, AFL++ expects us to read the status code back + // from the fuzzer. + if (read(FORKSRV_FD, &tmp, 4) != 4) { + puts("[FORKSERVER SETUP] Failed to read status code from FORKSRV_FD"); + exit(-1); + } + + uint32_t expected_status = FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ; + if (tmp != expected_status) { + printf("[FORKSERVER SETUP] Unexpected status code: %x (expected %x)\n", + tmp, expected_status); + } + } + + } else { puts("[FORKSERVER SETUP] Got shared memory region, but no pipe. going for single input"); do_fuzz = 0; @@ -812,7 +837,7 @@ static void *init_bitmap(uc_engine *uc) { do_fuzz = 0; } - uc_fuzzer_init_cov(uc, bitmap, MAP_SIZE); + uc->fuzzer_init_cov(uc->ctx, bitmap, MAP_SIZE); return bitmap; } @@ -822,9 +847,9 @@ static inline int run_single(uc_engine *uc) { uint64_t pc = 0; int sig = -1; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); - status = uc_emu_start(uc, pc | 1, 0, 0, 0); + status = uc->emu_start(uc->ctx, pc | 1, 0, 0, 0); if(custom_exit_reason != UC_ERR_OK) { status = custom_exit_reason; @@ -893,7 +918,7 @@ void fuzz_consumption_timeout_cb(uc_engine *uc, uint32_t id, void *user_data) { void test_timeout_cb(uc_engine *uc, uint32_t id, void *user_data) { if(!is_discovery_child) { uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("Test timer triggered at pc 0x%08x\n", pc); fflush(NULL); } @@ -903,7 +928,7 @@ void test_timeout_cb(uc_engine *uc, uint32_t id, void *user_data) { void instr_limit_timeout_cb(uc_engine *uc, uint32_t id, void *user_data) { if(do_print_exit_info) { uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("Ran into instruction limit of %lu at 0x%08x - exiting\n", get_timer_reload_val(instr_limit_timer_id), pc); } do_exit(uc, UC_ERR_OK); @@ -914,7 +939,7 @@ void *mmio_models_take_snapshot(uc_engine *uc) { uint32_t *passthrough_init_vals = malloc(size); for(int i = 0; i < num_ignored_addresses; ++i) { - uc_mem_read(uc, ignored_addresses[i], &passthrough_init_vals[i], sizeof(*passthrough_init_vals)); + uc->mem_read(uc->ctx, ignored_addresses[i], &passthrough_init_vals[i], sizeof(*passthrough_init_vals)); } return passthrough_init_vals; @@ -925,7 +950,7 @@ void mmio_models_restore_snapshot(uc_engine *uc, void *snapshot) { // Restore the initial passthrough MMIO values for(int i = 0; i < num_ignored_addresses; ++i) { - uc_mem_write(uc, ignored_addresses[i], &passthrough_init_vals[i], sizeof(*passthrough_init_vals)); + uc->mem_write(uc->ctx, ignored_addresses[i], &passthrough_init_vals[i], sizeof(*passthrough_init_vals)); } } @@ -935,7 +960,7 @@ void mmio_models_discard_snapshot(uc_engine *uc, void *snapshot) { uc_err init(uc_engine *uc, exit_hook_t p_exit_hook, int p_num_mmio_regions, uint64_t *p_mmio_starts, uint64_t *p_mmio_ends, void *p_py_default_mmio_user_data, uint32_t num_exit_at_bbls, uint64_t *exit_at_bbls, uint32_t p_exit_at_hit_limit, int p_do_print_exit_info, uint64_t p_fuzz_consumption_timeout, uint64_t p_instr_limit) { // TODO: assumes shared endianness - uc_mem_write(uc, CPUID_ADDR, &CPUID_CORTEX_M4, sizeof(CPUID_CORTEX_M4)); + uc->mem_write(uc->ctx, CPUID_ADDR, &CPUID_CORTEX_M4, sizeof(CPUID_CORTEX_M4)); if(p_exit_hook) { add_exit_hook(p_exit_hook); @@ -945,7 +970,7 @@ uc_err init(uc_engine *uc, exit_hook_t p_exit_hook, int p_num_mmio_regions, uint do_print_exit_info = p_do_print_exit_info; if(do_print_exit_info) { - uc_hook_add(uc, &invalid_mem_hook_handle, UC_HOOK_MEM_WRITE_INVALID | UC_HOOK_MEM_READ_INVALID | UC_HOOK_MEM_FETCH_INVALID, hook_debug_mem_invalid_access, 0, 1, 0); + uc->mem_hook_add(uc->ctx, &invalid_mem_hook_handle, UC_HOOK_MEM_WRITE_INVALID | UC_HOOK_MEM_READ_INVALID | UC_HOOK_MEM_FETCH_INVALID, hook_debug_mem_invalid_access, 0, 1, 0); } // Add fuzz consumption timeout as timer @@ -972,7 +997,7 @@ uc_err init(uc_engine *uc, exit_hook_t p_exit_hook, int p_num_mmio_regions, uint { uint64_t tmp; uint64_t bbl_addr = exit_at_bbls[i] & (~1LL); - if (uc_hook_add(uc, &tmp, UC_HOOK_BLOCK, hook_block_exit_at, 0, bbl_addr, bbl_addr) != UC_ERR_OK) + if (uc->block_hook_add(uc->ctx, &tmp, UC_HOOK_BLOCK, hook_block_exit_at, 0, bbl_addr, bbl_addr) != UC_ERR_OK) { perror("Could not register exit-at block hook...\n"); return -1; @@ -1023,7 +1048,7 @@ uc_err emulate(uc_engine *uc, char *p_input_path, char *prefix_input_path) { uint64_t pc = 0; fflush(stdout); - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); init_bitmap(uc); /* @@ -1078,7 +1103,7 @@ uc_err emulate(uc_engine *uc, char *p_input_path, char *prefix_input_path) { set_timer_reload_val(instr_limit_timer_id, required_ticks-2); // Execute the prefix - if(uc_emu_start(uc, pc | 1, 0, 0, 0)) { + if(uc->emu_start(uc->ctx, pc | 1, 0, 0, 0)) { puts("[ERROR] Could not execute the first some steps"); exit(-1); } @@ -1087,7 +1112,7 @@ uc_err emulate(uc_engine *uc, char *p_input_path, char *prefix_input_path) { } else { // child: Run until we hit an input consumption is_discovery_child = 1; - uc_err child_emu_status = uc_emu_start(uc, pc | 1, 0, 0, 0); + uc_err child_emu_status = uc->emu_start(uc->ctx, pc | 1, 0, 0, 0); // We do not expect to get here. The child should exit by itself in get_fuzz printf("[ERROR] Emulation stopped using just the prefix input (%d: %s)\n", child_emu_status, uc_strerror(child_emu_status)); @@ -1113,8 +1138,8 @@ uc_err emulate(uc_engine *uc, char *p_input_path, char *prefix_input_path) { // adjust_timers_for_unicorn_exit(); if(do_fuzz) { - uc_fuzzer_reset_cov(uc, 1); - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->fuzzer_reset_cov(uc->ctx, 1); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); trigger_snapshotting(uc); // AFL-compatible Forkserver loop @@ -1138,7 +1163,7 @@ uc_err emulate(uc_engine *uc, char *p_input_path, char *prefix_input_path) { } } - uc_fuzzer_reset_cov(uc, 0); + uc->fuzzer_reset_cov(uc->ctx, 0); /* Send AFL the child pid thus it can kill it on timeout */ if(write(FORKSRV_FD + 1, &child_pid, 4) != 4) { @@ -1169,7 +1194,7 @@ uc_err emulate(uc_engine *uc, char *p_input_path, char *prefix_input_path) { } else { // Non-crashing exit (includes different timeouts) uint32_t pc; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); printf("Exited without crash at 0x%08x - If no other reason, we ran into one of the limits\n", pc); } } diff --git a/harness/fuzzware_harness/native/native_hooks.h b/harness/fuzzware_harness/native/native_hooks.h index cc2afea..bfa6b50 100644 --- a/harness/fuzzware_harness/native/native_hooks.h +++ b/harness/fuzzware_harness/native/native_hooks.h @@ -1,7 +1,7 @@ #ifndef NATIVE_HOOKS_H #define NATIVE_HOOKS_H -#include "unicorn/unicorn.h" +#include "unicorn.h" #include "state_snapshotting.h" #include "uc_snapshot.h" diff --git a/harness/fuzzware_harness/native/native_tracing.c b/harness/fuzzware_harness/native/native_tracing.c index 1c83a7d..130bcba 100644 --- a/harness/fuzzware_harness/native/native_tracing.c +++ b/harness/fuzzware_harness/native/native_tracing.c @@ -1,4 +1,34 @@ +#include + #include "native_tracing.h" +#include "khash.h" +#include "unicorn.h" + + +KHASH_SET_INIT_INT64(64); +KHASH_SET_INIT_INT(32); + +/* + * To support snapshotting, keep one base set around as well as + * one scratch set. We store all contexts already collected upon + * taking a snapshot in the base sets, and add to the scratch set + * when running from a snapshot. Restoring the snapshot then means + * only wiping the scratch set. + * + * Note that nested snapshotting (which we are currently not using) + * is not supported by this. + */ +struct TraceState { + uint64_t bb_hash_base; + uint64_t bb_hash; + khash_t(32) *kh_basic_block_set; + khash_t(64) *kh_mmio_access_context_set_writes; + khash_t(64) *kh_mmio_access_context_set_reads; + khash_t(32) *kh_scratch_basic_block_set; + khash_t(64) *kh_scratch_mmio_access_context_set_writes; + khash_t(64) *kh_scratch_mmio_access_context_set_reads; +}; + // 0. Constants #define DEFAULT_INITIAL_MMIO_ACCESS_CONTEXT_TRACE_SET_CAPACITY 0x10000 @@ -47,7 +77,7 @@ void hook_mem_trace_mmio_access(uc_engine *uc, uc_mem_type type, uint32_t pc; int kh_res; uint64_t context; - uc_reg_read(uc, UC_ARM_REG_PC, &pc); + uc->reg_read(uc->ctx, UC_ARM_REG_PC, &pc); context = ENCODE_MMIO_ACCESS_CONTEXT(pc, addr); if(type == UC_MEM_WRITE) { @@ -188,7 +218,7 @@ uc_err init_tracing(uc_engine *uc, char *p_bbl_set_trace_path, char *p_bbl_hash_ trace_state.kh_scratch_basic_block_set = kh_init(32); // Tracing basic blocks is done via a single block hook - uc_hook_add(uc, &tmp_hook, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_trace_set, NULL, 1, 0); + uc->block_hook_add(uc->ctx, &tmp_hook, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_trace_set, NULL, 1, 0); } if(p_bbl_hash_path) { @@ -197,7 +227,7 @@ uc_err init_tracing(uc_engine *uc, char *p_bbl_set_trace_path, char *p_bbl_hash_ printf("logging basic block hash to %s\n", bbl_hash_path); // Tracing basic blocks is done via a single block hook - uc_hook_add(uc, &tmp_hook, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_trace_hash_bbs, NULL, 1, 0); + uc->block_hook_add(uc->ctx, &tmp_hook, UC_HOOK_BLOCK_UNCONDITIONAL, hook_block_trace_hash_bbs, NULL, 1, 0); } if(p_mmio_set_trace_path) { @@ -211,7 +241,7 @@ uc_err init_tracing(uc_engine *uc, char *p_bbl_set_trace_path, char *p_bbl_hash_ // For tracing MMIO, we need to register hooks for all MMIO regions for (int i = 0; i < num_mmio_ranges; ++i) { // We do not currently need writes for our purposes, otherwise, this would require | UC_HOOK_MEM_WRITE - uc_hook_add(uc, &tmp_hook, UC_HOOK_MEM_READ_AFTER, hook_mem_trace_mmio_access, NULL, mmio_starts[i], mmio_ends[i]); + uc->mem_hook_add(uc->ctx, &tmp_hook, UC_HOOK_MEM_READ_AFTER, hook_mem_trace_mmio_access, NULL, mmio_starts[i], mmio_ends[i]); } } diff --git a/harness/fuzzware_harness/native/native_tracing.h b/harness/fuzzware_harness/native/native_tracing.h index a8a9b1e..fe7bc10 100644 --- a/harness/fuzzware_harness/native/native_tracing.h +++ b/harness/fuzzware_harness/native/native_tracing.h @@ -1,38 +1,9 @@ #ifndef FUZZWARE_NATIVE_TRACING #define FUZZWARE_NATIVE_TRACING -#include -#include - #include "native_hooks.h" #include "state_snapshotting.h" -#include "khash.h" - -KHASH_SET_INIT_INT64(64); -KHASH_SET_INIT_INT(32); - -/* - * To support snapshotting, keep one base set around as well as - * one scratch set. We store all contexts already collected upon - * taking a snapshot in the base sets, and add to the scratch set - * when running from a snapshot. Restoring the snapshot then means - * only wiping the scratch set. - * - * Note that nested snapshotting (which we are currently not using) - * is not supported by this. - */ -struct TraceState { - uint64_t bb_hash_base; - uint64_t bb_hash; - khash_t(32) *kh_basic_block_set; - khash_t(64) *kh_mmio_access_context_set_writes; - khash_t(64) *kh_mmio_access_context_set_reads; - khash_t(32) *kh_scratch_basic_block_set; - khash_t(64) *kh_scratch_mmio_access_context_set_writes; - khash_t(64) *kh_scratch_mmio_access_context_set_reads; -}; - uc_err init_tracing(uc_engine *uc, char *p_bbl_set_trace_path, char *p_bbl_hash_path, char *p_mmio_set_trace_path, size_t num_mmio_ranges, uint64_t *mmio_starts, uint64_t *mmio_ends); #endif \ No newline at end of file diff --git a/harness/fuzzware_harness/native/state_snapshotting.c b/harness/fuzzware_harness/native/state_snapshotting.c index f85e5c9..6dd120c 100644 --- a/harness/fuzzware_harness/native/state_snapshotting.c +++ b/harness/fuzzware_harness/native/state_snapshotting.c @@ -1,5 +1,6 @@ #include "state_snapshotting.h" #include "native_hooks.h" +#include static struct snapshotting_state_t state = { .num_allocated = 0, diff --git a/harness/fuzzware_harness/native/state_snapshotting.h b/harness/fuzzware_harness/native/state_snapshotting.h index 6302e88..26a872e 100644 --- a/harness/fuzzware_harness/native/state_snapshotting.h +++ b/harness/fuzzware_harness/native/state_snapshotting.h @@ -4,7 +4,7 @@ #ifndef FUZZWARE_STATE_RESTORE #define FUZZWARE_STATE_RESTORE -#include "unicorn/unicorn.h" +#include "unicorn.h" #define INITIAL_NUM_STATE_SOURCES 8 diff --git a/harness/fuzzware_harness/native/timer.c b/harness/fuzzware_harness/native/timer.c index b6ebf54..4ddeec2 100644 --- a/harness/fuzzware_harness/native/timer.c +++ b/harness/fuzzware_harness/native/timer.c @@ -1,5 +1,7 @@ -#include +#include "unicorn.h" #include "timer.h" +#include +#include #include #include "native_hooks.h" #include "core_peripherals/cortexm_nvic.h" @@ -480,7 +482,7 @@ void timers_discard_snapshot(uc_engine *uc, void *snapshot) { uc_err init_timer_hook(uc_engine *uc, uint32_t global_timer_scale) { // Reset timer structs - if(uc_hook_add(uc, &timer_block_hook_handle, UC_HOOK_BLOCK_UNCONDITIONAL, (void *) timer_tick_block_hook, &timers.cur_countdown, 1, 0) != UC_ERR_OK) { + if(uc->block_hook_add(uc->ctx, &timer_block_hook_handle, UC_HOOK_BLOCK_UNCONDITIONAL, (void *) timer_tick_block_hook, &timers.cur_countdown, 1, 0) != UC_ERR_OK) { perror("[TIMER ERROR] init_timer_hook: Could not add timer block hook\n"); exit(-1); } diff --git a/harness/fuzzware_harness/native/timer.h b/harness/fuzzware_harness/native/timer.h index 84e55c1..0e55661 100644 --- a/harness/fuzzware_harness/native/timer.h +++ b/harness/fuzzware_harness/native/timer.h @@ -1,7 +1,7 @@ #ifndef NATIVE_TIMER_H #define NATIVE_TIMER_H -#include +#include "unicorn.h" typedef void (*timer_cb)(uc_engine *uc, uint32_t id, void *user_data); #define MAX_TIMERS 32 diff --git a/harness/fuzzware_harness/native/uc_snapshot.c b/harness/fuzzware_harness/native/uc_snapshot.c index 9522e39..3144f24 100644 --- a/harness/fuzzware_harness/native/uc_snapshot.c +++ b/harness/fuzzware_harness/native/uc_snapshot.c @@ -1,6 +1,8 @@ #include "uc_snapshot.h" #include "state_snapshotting.h" #include +#include +#include /* * Snapshotting of the unicorn engine state (registers and memory). @@ -242,11 +244,11 @@ bool hook_invalid_write_on_demand_page_restore_handle(uc_engine *uc, uc_mem_type if(old_perms & UC_PROT_WRITE) { int res; - if((res = uc_mem_protect(uc, aligned, PAGE_SIZE, old_perms)) != UC_ERR_OK) { + if((res = uc->mem_protect(uc->ctx, aligned, PAGE_SIZE, old_perms)) != UC_ERR_OK) { printf("[ERROR] uc_mem_protect failed for aligned=%08lx, perms=%x (err code: %d, msg: %s)\n", aligned, old_perms, res, uc_strerror(res)); fflush(stdout); exit(-1); } - if (uc_mem_write(uc, address, &value, size) != UC_ERR_OK) { + if (uc->mem_write(uc->ctx, address, &value, size) != UC_ERR_OK) { printf("[ERROR] uc_mem_write failed while trying to add page %#010lx. Write addr: %#010lx, size: %d, value: %08lx\n", aligned, address, size, value); exit(-1); } @@ -275,10 +277,10 @@ void *native_hooks_take_snapshot(uc_engine *uc) { result->curr_exit_at_hit_num = native_hooks_state.curr_exit_at_hit_num; // memcpy(result, native_hooks_state.curr_exit_at_hit_num, size); - uc_context_alloc(uc, &result->uc_saved_context); - uc_context_save(uc, result->uc_saved_context); + uc->context_alloc(uc->ctx, &result->uc_saved_context); + uc->context_save(uc->ctx, result->uc_saved_context); - uc_mem_regions(uc, ®ions, &num_regions); + uc->mem_regions(uc->ctx, ®ions, &num_regions); result->num_orig_regions = num_regions; result->orig_regions = regions; @@ -335,7 +337,7 @@ void *native_hooks_take_snapshot(uc_engine *uc) { int cursor = 0; int is_nullpage = -1, prev_is_nullpage = -1; uint8_t *contents = malloc(size); - uc_mem_read(uc, regions[i].begin, contents, size); + uc->mem_read(uc->ctx, regions[i].begin, contents, size); for(cursor = 0; cursor < size; cursor += PAGE_SIZE) { is_nullpage = 1; @@ -377,7 +379,7 @@ void *native_hooks_take_snapshot(uc_engine *uc) { #ifdef DEBUG_STATE_RESTORE printf("Setting new perms %x (prev: %x) for 0x%lx, size: 0x%lx\n", new_perms, permissions, regions[i].begin, size); #endif - if(uc_mem_protect(uc, regions[i].begin, size, new_perms) == UC_ERR_ARG) { + if(uc->mem_protect(uc->ctx, regions[i].begin, size, new_perms) == UC_ERR_ARG) { puts("ERROR: uc_mem_protect failed"); fflush(stdout); exit(1); } @@ -389,7 +391,7 @@ void *native_hooks_take_snapshot(uc_engine *uc) { result->num_restore_nullregions = 0; // Register mem protect handler for on-demand registration - if(uc_hook_add(uc, &result->on_demand_pages_handle, UC_HOOK_MEM_WRITE_PROT, hook_invalid_write_on_demand_page_restore_handle, result, 1, 0) != UC_ERR_OK) { + if(uc->mem_hook_add(uc->ctx, &result->on_demand_pages_handle, UC_HOOK_MEM_WRITE_PROT, hook_invalid_write_on_demand_page_restore_handle, result, 1, 0) != UC_ERR_OK) { puts("[ERROR] Could not add on-demand page restore hook"); fflush(stdout); exit(-1); } @@ -400,7 +402,7 @@ void *native_hooks_take_snapshot(uc_engine *uc) { void native_hooks_restore_snapshot(uc_engine *uc, void *snapshot) { struct NativeHooksState *snapshot_state = (struct NativeHooksState *) snapshot; - uc_context_restore(uc, snapshot_state->uc_saved_context); + uc->context_restore(uc->ctx, snapshot_state->uc_saved_context); native_hooks_state.curr_exit_at_hit_num = snapshot_state->curr_exit_at_hit_num; // memory restore @@ -408,7 +410,7 @@ void native_hooks_restore_snapshot(uc_engine *uc, void *snapshot) { #ifdef DEBUG_STATE_RESTORE printf("[] restoring 0x%lx bytes to 0x%lx\n", snapshot_state->restore_content_sizes[i], snapshot_state->restore_content_guest_addrs[i]); #endif - uc_mem_write(uc, snapshot_state->restore_content_guest_addrs[i], snapshot_state->restore_contents_ptrs[i], snapshot_state->restore_content_sizes[i]); + uc->mem_write(uc->ctx, snapshot_state->restore_content_guest_addrs[i], snapshot_state->restore_contents_ptrs[i], snapshot_state->restore_content_sizes[i]); } // nullpages @@ -416,21 +418,21 @@ void native_hooks_restore_snapshot(uc_engine *uc, void *snapshot) { #ifdef DEBUG_STATE_RESTORE printf("[] memsetting 0x%lx bytes at 0x%lx\n", snapshot_state->restore_nullregion_starts[i], snapshot_state->restore_nullregion_sizes[i]); #endif - uc_mem_set(uc, snapshot_state->restore_nullregion_starts[i], 0, snapshot_state->restore_nullregion_sizes[i]); + uc->mem_set(uc->ctx, snapshot_state->restore_nullregion_starts[i], 0, snapshot_state->restore_nullregion_sizes[i]); } } void native_hooks_discard_snapshot(uc_engine *uc, void *snapshot) { struct NativeHooksState *snapshot_state = (struct NativeHooksState *) snapshot; - uc_free(snapshot_state->uc_saved_context); + uc->free(snapshot_state->uc_saved_context); for(int i=0; i < snapshot_state->num_content_regions; ++i) { free(snapshot_state->contents_ptrs[i]); } - uc_hook_del(uc, snapshot_state->on_demand_pages_handle); + uc->hook_del(uc->ctx, snapshot_state->on_demand_pages_handle); - uc_free(snapshot_state->orig_regions); + uc->free(snapshot_state->orig_regions); free(snapshot); } diff --git a/harness/fuzzware_harness/native/uc_snapshot.h b/harness/fuzzware_harness/native/uc_snapshot.h index 7346de8..c5043d8 100644 --- a/harness/fuzzware_harness/native/uc_snapshot.h +++ b/harness/fuzzware_harness/native/uc_snapshot.h @@ -1,7 +1,7 @@ #ifndef UC_SNAPSHOT_H #define UC_SNAPSHOT_H -#include "unicorn/unicorn.h" +#include "unicorn.h" #include "native_hooks.h" #define PAGE_SIZE 0x1000 diff --git a/harness/fuzzware_harness/native/unicorn.h b/harness/fuzzware_harness/native/unicorn.h new file mode 100644 index 0000000..522a02f --- /dev/null +++ b/harness/fuzzware_harness/native/unicorn.h @@ -0,0 +1,169 @@ +#ifndef ICICLE_H +#define ICICLE_H + +#include +#include +#include + +#include "arm.h" + +typedef size_t uc_hook; + +typedef enum uc_err { + UC_ERR_OK = 0, // No error: everything was fine + UC_ERR_NOMEM, // Out-Of-Memory error: uc_open(), uc_emulate() + UC_ERR_ARCH, // Unsupported architecture: uc_open() + UC_ERR_HANDLE, // Invalid handle + UC_ERR_MODE, // Invalid/unsupported mode: uc_open() + UC_ERR_VERSION, // Unsupported version (bindings) + UC_ERR_READ_UNMAPPED, // Quit emulation due to READ on unmapped memory: uc_emu_start() + UC_ERR_WRITE_UNMAPPED, // Quit emulation due to WRITE on unmapped memory: uc_emu_start() + UC_ERR_FETCH_UNMAPPED, // Quit emulation due to FETCH on unmapped memory: uc_emu_start() + UC_ERR_HOOK, // Invalid hook type: uc_hook_add() + UC_ERR_INSN_INVALID, // Quit emulation due to invalid instruction: uc_emu_start() + UC_ERR_MAP, // Invalid memory mapping: uc_mem_map() + UC_ERR_WRITE_PROT, // Quit emulation due to UC_MEM_WRITE_PROT violation: uc_emu_start() + UC_ERR_READ_PROT, // Quit emulation due to UC_MEM_READ_PROT violation: uc_emu_start() + UC_ERR_FETCH_PROT, // Quit emulation due to UC_MEM_FETCH_PROT violation: uc_emu_start() + UC_ERR_ARG, // Inavalid argument provided to uc_xxx function (See specific function API) + UC_ERR_READ_UNALIGNED, // Unaligned read + UC_ERR_WRITE_UNALIGNED, // Unaligned write + UC_ERR_FETCH_UNALIGNED, // Unaligned fetch + UC_ERR_HOOK_EXIST, // hook for this event already existed + UC_ERR_RESOURCE, // Insufficient resource: uc_emu_start() + UC_ERR_EXCEPTION, // Unhandled CPU exception +} uc_err; + +extern const char *uc_strerror(uc_err code); + +// All type of memory accesses for UC_HOOK_MEM_* +typedef enum uc_mem_type { + UC_MEM_READ = 16, // Memory is read from + UC_MEM_WRITE, // Memory is written to + UC_MEM_FETCH, // Memory is fetched + UC_MEM_READ_UNMAPPED, // Unmapped memory is read from + UC_MEM_WRITE_UNMAPPED, // Unmapped memory is written to + UC_MEM_FETCH_UNMAPPED, // Unmapped memory is fetched + UC_MEM_WRITE_PROT, // Write to write protected, but mapped, memory + UC_MEM_READ_PROT, // Read from read protected, but mapped, memory + UC_MEM_FETCH_PROT, // Fetch from non-executable, but mapped, memory + UC_MEM_READ_AFTER, // Memory is read from (successful access) +} uc_mem_type; + +typedef enum uc_prot { + UC_PROT_NONE = 0, + UC_PROT_READ = 1, + UC_PROT_WRITE = 2, + UC_PROT_EXEC = 4, + UC_PROT_ALL = 7, +} uc_prot; + +// All type of hooks for uc_hook_add() API. +typedef enum uc_hook_type { + // Hook all interrupt/syscall events + UC_HOOK_INTR = 1 << 0, + // Hook a particular instruction - only a very small subset of instructions supported here + UC_HOOK_INSN = 1 << 1, + // Hook a range of code + UC_HOOK_CODE = 1 << 2, + // Hook basic blocks + UC_HOOK_BLOCK = 1 << 3, + // Hook for memory read on unmapped memory + UC_HOOK_MEM_READ_UNMAPPED = 1 << 4, + // Hook for invalid memory write events + UC_HOOK_MEM_WRITE_UNMAPPED = 1 << 5, + // Hook for invalid memory fetch for execution events + UC_HOOK_MEM_FETCH_UNMAPPED = 1 << 6, + // Hook for memory read on read-protected memory + UC_HOOK_MEM_READ_PROT = 1 << 7, + // Hook for memory write on write-protected memory + UC_HOOK_MEM_WRITE_PROT = 1 << 8, + // Hook for memory fetch on non-executable memory + UC_HOOK_MEM_FETCH_PROT = 1 << 9, + // Hook memory read events. + UC_HOOK_MEM_READ = 1 << 10, + // Hook memory write events. + UC_HOOK_MEM_WRITE = 1 << 11, + // Hook memory fetch for execution events + UC_HOOK_MEM_FETCH = 1 << 12, + // Hook memory read events, but only successful access. + // The callback will be triggered after successful read. + UC_HOOK_MEM_READ_AFTER = 1 << 13, + // Hook invalid instructions exceptions. + UC_HOOK_INSN_INVALID = 1 << 14, + // Hook blocks unconditionally, ignoring the range modifier. Used to optimize hook invocation + UC_HOOK_BLOCK_UNCONDITIONAL = 1 << 15, +} uc_hook_type; + +// Hook type for all events of unmapped memory access +#define UC_HOOK_MEM_UNMAPPED (UC_HOOK_MEM_READ_UNMAPPED + UC_HOOK_MEM_WRITE_UNMAPPED + UC_HOOK_MEM_FETCH_UNMAPPED) +// Hook type for all events of illegal protected memory access +#define UC_HOOK_MEM_PROT (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_FETCH_PROT) +// Hook type for all events of illegal read memory access +#define UC_HOOK_MEM_READ_INVALID (UC_HOOK_MEM_READ_PROT + UC_HOOK_MEM_READ_UNMAPPED) +// Hook type for all events of illegal write memory access +#define UC_HOOK_MEM_WRITE_INVALID (UC_HOOK_MEM_WRITE_PROT + UC_HOOK_MEM_WRITE_UNMAPPED) +// Hook type for all events of illegal fetch memory access +#define UC_HOOK_MEM_FETCH_INVALID (UC_HOOK_MEM_FETCH_PROT + UC_HOOK_MEM_FETCH_UNMAPPED) +// Hook type for all events of illegal memory access +#define UC_HOOK_MEM_INVALID (UC_HOOK_MEM_UNMAPPED + UC_HOOK_MEM_PROT) +// Hook type for all events of valid memory access +// NOTE: UC_HOOK_MEM_READ is triggered before UC_HOOK_MEM_READ_PROT and UC_HOOK_MEM_READ_UNMAPPED, so +// this hook may technically trigger on some invalid reads. +#define UC_HOOK_MEM_VALID (UC_HOOK_MEM_READ + UC_HOOK_MEM_WRITE + UC_HOOK_MEM_FETCH) + +typedef struct uc_mem_region { + uint64_t begin; // begin address of the region (inclusive) + uint64_t end; // end address of the region (inclusive) + uint32_t perms; // memory permissions of the region +} uc_mem_region; + + +struct uc_context; +typedef struct uc_context uc_context; + +typedef struct { + void* ctx; + uc_err (*emu_start)(void*, uint64_t, uint64_t, uint64_t, uint64_t); + uc_err (*emu_stop)(void*); + uc_err (*reg_read)(void*, int, void*); + uc_err (*reg_read_batch)(void*, int*, void**, int); + uc_err (*reg_write)(void*, int, const void*); + uc_err (*reg_write_batch)(void*, int*, void* const*, int); + uc_err (*reg_ptr)(void*, int, void**); + + uc_err (*mem_read)(void*, uint64_t, void*, size_t); + uc_err (*mem_write)(void*, uint64_t, const void*, size_t); + uc_err (*mem_set)(void*, uint64_t, uint8_t, size_t); + uc_err (*mem_protect)(void*, uint64_t, size_t, uint32_t); + uc_err (*mem_regions)(void*, uc_mem_region**, uint32_t*); + + uc_err (*block_hook_add)(void*, uc_hook*, int, void*, void*, uint64_t, uint64_t); + uc_err (*mem_hook_add)(void*, uc_hook*, int, void*, void*, uint64_t, uint64_t); + uc_err (*int_hook_add)(void*, uc_hook*, int, void*, void*, uint64_t, uint64_t); + uc_err (*hook_del)(void*, uc_hook); + + uc_err (*context_alloc)(void*, uc_context**); + uc_err (*context_save)(void*, uc_context*); + uc_err (*context_restore)(void*, uc_context*); + uc_err (*free)(void*); + uc_err (*fuzzer_init_cov)(void*, void*, uint32_t); + uc_err (*fuzzer_reset_cov)(void*, int); + + uc_err (*save_info)(void*); + +} uc_engine; + + +typedef void (*uc_cb_hookcode_t)(uc_engine *uc, uint64_t address, uint32_t size, void *user_data); +typedef void (*uc_cb_hookintr_t)(uc_engine *uc, uint32_t intno, void *user_data); +typedef bool (*uc_cb_hookinsn_invalid_t)(uc_engine *uc, void *user_data); +typedef uint32_t (*uc_cb_insn_in_t)(uc_engine *uc, uint32_t port, int size, void *user_data); + + +typedef void (*uc_cb_hookmem_t)(uc_engine *uc, uc_mem_type type, + uint64_t address, int size, int64_t value, void *user_data); + + + +#endif \ No newline at end of file diff --git a/harness/fuzzware_harness/native/util.c b/harness/fuzzware_harness/native/util.c index a72baf5..a2c2b6b 100644 --- a/harness/fuzzware_harness/native/util.c +++ b/harness/fuzzware_harness/native/util.c @@ -1,7 +1,9 @@ #include #include +#include +#include #include -#include +#include "unicorn.h" int get_instruction_size(uint64_t insn, bool is_thumb) { if(is_thumb) { @@ -34,16 +36,16 @@ void print_state(uc_engine *uc) { puts("\n==== UC Reg state ===="); for (int i = 0; i < NUM_DUMPED_REGS; ++i) { - uc_reg_read(uc, reg_ids[i], ®); + uc->reg_read(uc->ctx, reg_ids[i], ®); printf("%s: 0x%08x\n", reg_names[i], reg); } puts("\n==== UC Stack state ===="); uint32_t sp; - uc_reg_read(uc, UC_ARM_REG_SP, &sp); + uc->reg_read(uc->ctx, UC_ARM_REG_SP, &sp); for (int i = -4; i < 16; ++i) { uint32_t val; - if(uc_mem_read(uc, sp+4*i, &val, 4)) { + if(uc->mem_read(uc->ctx, sp+4*i, &val, 4)) { continue; } printf("0x%08x: %08x", sp+4*i, val); @@ -56,11 +58,11 @@ void print_state(uc_engine *uc) { puts("======================\n"); puts("\n==== UC Other Stack state ===="); - uc_reg_read(uc, UC_ARM_REG_OTHER_SP, &sp); + uc->reg_read(uc->ctx, UC_ARM_REG_OTHER_SP, &sp); for (int i = -4; i < 16; ++i) { uint32_t val; - if(uc_mem_read(uc, sp+4*i, &val, 4)) { + if(uc->mem_read(uc->ctx, sp+4*i, &val, 4)) { continue; } printf("0x%08x: %08x", sp+4*i, val); @@ -73,3 +75,8 @@ void print_state(uc_engine *uc) { puts("======================\n"); fflush(stdout); } + + +const char *uc_strerror(uc_err code) { + return "unknown"; +} diff --git a/harness/fuzzware_harness/native/util.h b/harness/fuzzware_harness/native/util.h index fb32fcc..2032b9b 100644 --- a/harness/fuzzware_harness/native/util.h +++ b/harness/fuzzware_harness/native/util.h @@ -2,7 +2,7 @@ #define NATIVE_UTIL_H #include -#include +#include "unicorn.h" #define min(a, b) (a < b ? a : b) diff --git a/harness/fuzzware_harness/native/wrapper.h b/harness/fuzzware_harness/native/wrapper.h new file mode 100644 index 0000000..3e050e7 --- /dev/null +++ b/harness/fuzzware_harness/native/wrapper.h @@ -0,0 +1,7 @@ +#include "unicorn.h" +#include "native_hooks.h" +#include "native_tracing.h" +#include "timer.h" +#include "interrupt_triggers.h" +#include "core_peripherals/cortexm_nvic.h" +#include "core_peripherals/cortexm_systick.h" diff --git a/harness/fuzzware_harness/util.py b/harness/fuzzware_harness/util.py index dfaefbb..3b59651 100644 --- a/harness/fuzzware_harness/util.py +++ b/harness/fuzzware_harness/util.py @@ -172,12 +172,26 @@ def resolve_region_file_paths(config_file_path, config): and relative paths. """ + debug_info = config.get('debug_info') + for _, region in config['memory_map'].items(): path = region.get('file') if path: region['file'] = resolve_config_file_pattern(os.path.dirname(config_file_path), path) logger.info("Found path '{}' for pattern '{}'".format(region['file'], path)) + # Check if there is file with the same name with the .elf file extension to use for + # debug info + name, ext = os.path.splitext(region['file']) + elf_path = name + ".elf" + if debug_info is None and os.path.exists(elf_path): + config['debug_info'] = elf_path + logger.info(f"Found path '{elf_path}' for debug info") + + if debug_info: + config['debug_info'] = resolve_config_file_pattern(os.path.dirname(config_file_path), debug_info) + logger.info("Found path '{}' for debug info '{}'".format(config['debug_info'], debug_info)) + def load_config_deep(path): if not os.path.isfile(path): return {} diff --git a/harness/harness.py b/harness/harness.py new file mode 100644 index 0000000..d39c843 --- /dev/null +++ b/harness/harness.py @@ -0,0 +1,5 @@ +import fuzzware_harness + +if __name__ == '__main__': + fuzzware_harness.main() + diff --git a/icicle/.gitignore b/icicle/.gitignore new file mode 100644 index 0000000..af3ca5e --- /dev/null +++ b/icicle/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version \ No newline at end of file diff --git a/icicle/Cargo.lock b/icicle/Cargo.lock new file mode 100644 index 0000000..d2fae0c --- /dev/null +++ b/icicle/Cargo.lock @@ -0,0 +1,1207 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca9b76e919fd83ccfb509f51b28c333c0e03f2221616e347a129215cec4e4a9" +dependencies = [ + "cpp_demangle", + "fallible-iterator", + "gimli", + "object", + "rustc-demangle", + "smallvec", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bindgen" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "3.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cranelift" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd96bba738454eb373087df6d6891b18009361123fef90930def4978e3837448" +dependencies = [ + "cranelift-codegen", + "cranelift-frontend", +] + +[[package]] +name = "cranelift-bforest" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529ffacce2249ac60edba2941672dfedf3d96558b415d0d8083cd007456e0f55" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427d105f617efc8cb55f8d036a7fded2e227892d8780b4985e5551f8d27c4a92" +dependencies = [ + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551674bed85b838d45358e3eab4f0ffaa6790c70dc08184204b9a54b41cdb7d1" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b3a63ae57498c3eb495360944a33571754241e15e47e3bcae6082f40fec5866" + +[[package]] +name = "cranelift-entity" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11aa8aa624c72cc1c94ea3d0739fa61248260b5b14d3646f51593a88d67f3e6e" + +[[package]] +name = "cranelift-frontend" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "544ee8f4d1c9559c9aa6d46e7aaeac4a13856d620561094f35527356c7d21bd0" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed16b14363d929b8c37e3c557d0a7396791b383ecc302141643c054343170aad" + +[[package]] +name = "cranelift-jit" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0308e7418208639fb96c1a3dc04955fa41c4bc92dfce9106635185f71d5caf46" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-module", + "cranelift-native", + "libc", + "log", + "region", + "target-lexicon", + "windows-sys", +] + +[[package]] +name = "cranelift-module" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76979aac10dbcf0c222cd5902565bc93597ac30bbe9d879a2aa5f2402d1561f2" +dependencies = [ + "anyhow", + "cranelift-codegen", +] + +[[package]] +name = "cranelift-native" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51617cf8744634f2ed3c989c3c40cd6444f63377c6d994adab0d85807f3eb682" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crepe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0c81f0055a7c877a9a69ec9d667a0b14c2b38394c712f54b9a400d035f49a9" +dependencies = [ + "petgraph", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "icicle" +version = "0.1.0" +dependencies = [ + "bindgen", + "bytemuck", + "cc", + "icicle-fuzzing", + "icicle-vm", + "once_cell", + "pcode", + "pyo3", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "icicle-cpu" +version = "0.1.0" +dependencies = [ + "addr2line", + "bitflags", + "bytemuck", + "icicle-mem", + "object", + "pcode", + "sleigh-runtime", + "target-lexicon", + "tracing", +] + +[[package]] +name = "icicle-fuzzing" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitflags", + "bytemuck", + "crepe", + "icicle-vm", + "ihex", + "pcode", + "ron", + "serde", + "sleigh-runtime", + "target-lexicon", + "tracing", +] + +[[package]] +name = "icicle-jit" +version = "0.2.0" +dependencies = [ + "cranelift", + "cranelift-codegen", + "cranelift-jit", + "cranelift-module", + "cranelift-native", + "icicle-cpu", + "memoffset", + "pcode", + "target-lexicon", + "tracing", +] + +[[package]] +name = "icicle-linux" +version = "0.1.0" +dependencies = [ + "bitflags", + "bstr", + "bytemuck", + "icicle-cpu", + "object", + "pcode", + "sleigh-runtime", + "target-lexicon", + "tracing", +] + +[[package]] +name = "icicle-mem" +version = "0.3.0" +dependencies = [ + "tracing", +] + +[[package]] +name = "icicle-vm" +version = "0.2.0" +dependencies = [ + "anyhow", + "icicle-cpu", + "icicle-jit", + "icicle-linux", + "object", + "pcode", + "sleigh-compile", + "sleigh-runtime", + "target-lexicon", + "tracing", +] + +[[package]] +name = "ihex" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "crc32fast", + "flate2", + "hashbrown", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "os_str_bytes" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pcode" +version = "0.2.0" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regalloc2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43a209257d978ef079f3d446331d0f1794f5e0fc19b306a199983857833a779" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "sleigh-compile" +version = "0.3.0" +dependencies = [ + "pcode", + "sleigh-parse", + "sleigh-runtime", +] + +[[package]] +name = "sleigh-parse" +version = "0.3.0" + +[[package]] +name = "sleigh-runtime" +version = "0.1.0" +dependencies = [ + "pcode", + "sleigh-parse", +] + +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unindent" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/icicle/Cargo.toml b/icicle/Cargo.toml new file mode 100644 index 0000000..1571e06 --- /dev/null +++ b/icicle/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "icicle" +version = "0.1.0" +edition = "2021" + +[lib] +name = "icicle" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.16.5", features = ["extension-module"] } +icicle-vm = { path = "../../../icicle-emu/icicle-vm" } +icicle-fuzzing = { path = "../../../icicle-emu/icicle-fuzzing" } +pcode = { path = "../../../icicle-emu/sleigh/pcode" } +tracing = { version = "0.1.36", default-features = false, features = ["release_max_level_info"] } +tracing-subscriber = { version = "0.3.15", default-features = false, features = ["fmt", "env-filter", "ansi"] } +once_cell = "1.13.0" +bytemuck = "1.11.0" + +[build-dependencies] +cc = "1.0.73" +bindgen = "0.60.1" + +[profile.release] +debug = 1 diff --git a/icicle/build.rs b/icicle/build.rs new file mode 100644 index 0000000..5a1b369 --- /dev/null +++ b/icicle/build.rs @@ -0,0 +1,49 @@ +fn main() { + let files = [ + "native_hooks.c", + "timer.c", + "interrupt_triggers.c", + "util.c", + "state_snapshotting.c", + "native_tracing.c", + "uc_snapshot.c", + "core_peripherals/cortexm_nvic.c", + "core_peripherals/cortexm_systick.c", + ]; + let base_path = std::path::Path::new("../harness/fuzzware_harness/native/"); + + cc::Build::new() + .include(&base_path) + .files(files.iter().map(|file| base_path.join(file))) + .debug(true) + .warnings(false) // Fuzzware generates a lot of `unused-parameter` warning + .compiler("clang") + // .define("DEBUG_NVIC", "1") + // .define("DEBUG_STATE_RESTORE", "1") + // .define("DEBUG", "1") + // .define("DEBUG_TIMER", "1") + // .define("DEBUG_TIMER_TICK", "1") + .compile("fuzzware-native"); + + for file in files { + println!("cargo:rerun-if-changed=./harness/fuzzware_harness/native/{file}"); + } + + let wrapper_header = "../harness/fuzzware_harness/native/wrapper.h"; + println!("cargo:rerun-if-changed={wrapper_header}"); + + let bindings = bindgen::builder() + .header(wrapper_header) + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .size_t_is_usize(true) + .layout_tests(false) + .default_enum_style(bindgen::EnumVariation::ModuleConsts) + .translate_enum_integer_types(true) + .generate() + .expect("failed to generated bindings"); + + let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("fuzzware_bindings.rs")) + .expect("failed to write bindings"); +} diff --git a/icicle/pyproject.toml b/icicle/pyproject.toml new file mode 100644 index 0000000..07a948d --- /dev/null +++ b/icicle/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["maturin>=0.12,<0.13"] +build-backend = "maturin" + +[project] +name = "icicle" +requires-python = ">=3.6" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + diff --git a/icicle/rustfmt.toml b/icicle/rustfmt.toml new file mode 100644 index 0000000..de107dd --- /dev/null +++ b/icicle/rustfmt.toml @@ -0,0 +1,9 @@ +edition = "2018" +version = "Two" +comment_width = 100 +wrap_comments = true +use_field_init_shorthand = true +overflow_delimited_expr = true +control_brace_style = "ClosingNextLine" +max_width = 100 +use_small_heuristics = "Max" diff --git a/icicle/src/arm.rs b/icicle/src/arm.rs new file mode 100644 index 0000000..e95a679 --- /dev/null +++ b/icicle/src/arm.rs @@ -0,0 +1,415 @@ +use icicle_vm::{ + cpu::lifter::{BlockState, InstructionSource, PcodeOpInjector}, + Vm, +}; +use pcode::Op; + +pub(crate) const EXCP_SWI: u32 = 2; + +use crate::{fuzzware::uc_arm_reg::*, Context}; + +pub(crate) struct SpecialRegs { + pub ng: pcode::VarNode, + pub zr: pcode::VarNode, + pub cy: pcode::VarNode, + pub ov: pcode::VarNode, + pub xpsr: pcode::VarNode, +} + +impl SpecialRegs { + pub fn get(vm: &mut Vm) -> Self { + Self { + ng: vm.cpu.arch.sleigh.get_reg("NG").unwrap().var, + zr: vm.cpu.arch.sleigh.get_reg("ZR").unwrap().var, + cy: vm.cpu.arch.sleigh.get_reg("CY").unwrap().var, + ov: vm.cpu.arch.sleigh.get_reg("OV").unwrap().var, + xpsr: vm.cpu.arch.sleigh.get_reg("xpsr").unwrap().var, + } + } +} + +const XPSR_NZCV_MASK: u32 = 0xf000_0000; + +pub(crate) fn read_xpsr(ctx: &mut Context) -> u32 { + let mut xpsr = ctx.vm.cpu.read_reg::(ctx.regs.xpsr) & !crate::arm::XPSR_NZCV_MASK; + xpsr |= (ctx.vm.cpu.read_reg::(ctx.regs.ng) as u32) << 31; + xpsr |= (ctx.vm.cpu.read_reg::(ctx.regs.zr) as u32) << 30; + xpsr |= (ctx.vm.cpu.read_reg::(ctx.regs.cy) as u32) << 29; + xpsr |= (ctx.vm.cpu.read_reg::(ctx.regs.ov) as u32) << 28; + xpsr +} + +pub(crate) fn write_xpsr(ctx: &mut Context, xpsr: u32) { + ctx.vm.cpu.write_reg::(ctx.regs.ng, ((xpsr >> 31) & 0b1) as u8); + ctx.vm.cpu.write_reg::(ctx.regs.zr, ((xpsr >> 30) & 0b1) as u8); + ctx.vm.cpu.write_reg::(ctx.regs.cy, ((xpsr >> 29) & 0b1) as u8); + ctx.vm.cpu.write_reg::(ctx.regs.ov, ((xpsr >> 28) & 0b1) as u8); + ctx.vm.cpu.write_reg::(ctx.regs.xpsr, xpsr); +} + +/// Most of this should probably be implemented as part of the SLEIGH specification instead. +pub(crate) fn add_arm_extras(vm: &mut Vm) { + const CPSR_IRQ_MASK_BIT: u32 = 1 << 7; + const CPSR_FIQ_MASK_BIT: u32 = 1 << 6; + + // Add missing registers. + let xpsr_reg = vm.cpu.arch.sleigh.add_custom_reg("xpsr", 4).unwrap(); + let other_sp_reg = vm.cpu.arch.sleigh.add_custom_reg("other_sp", 4).unwrap(); + let basepri_reg = vm.cpu.arch.sleigh.add_custom_reg("basepri", 4).unwrap(); + let primask_reg = vm.cpu.arch.sleigh.add_custom_reg("primask", 4).unwrap(); + + override_op( + vm, + "getCurrentExceptionNumber", + move |_: &dyn InstructionSource, _, _, output: pcode::VarNode, b: &mut BlockState| { + b.pcode.push((output, Op::IntAnd, (xpsr_reg, 0x1ff_u32))); + false + }, + ); + + override_op( + vm, + "enableIRQinterrupts", + move |_: &dyn InstructionSource, _, _, _, b: &mut BlockState| { + b.pcode.push((primask_reg, Op::IntAnd, (primask_reg, !CPSR_IRQ_MASK_BIT))); + false + }, + ); + + override_op( + vm, + "disableIRQinterrupts", + move |_: &dyn InstructionSource, _, _, _, b: &mut BlockState| { + b.pcode.push((primask_reg, Op::IntOr, (primask_reg, CPSR_IRQ_MASK_BIT))); + false + }, + ); + + override_op( + vm, + "enableFIQinterrupts", + move |_: &dyn InstructionSource, _, _, _, b: &mut BlockState| { + b.pcode.push((primask_reg, Op::IntAnd, (primask_reg, !CPSR_FIQ_MASK_BIT))); + false + }, + ); + + override_op( + vm, + "disableFIQinterrupts", + move |_: &dyn InstructionSource, _, _, _, b: &mut BlockState| { + b.pcode.push((primask_reg, Op::IntOr, (primask_reg, CPSR_FIQ_MASK_BIT))); + false + }, + ); + + override_op( + vm, + "isIRQinterruptsEnabled", + move |_: &dyn InstructionSource, _, _, output, b: &mut BlockState| { + let shift = 31 - CPSR_IRQ_MASK_BIT.leading_zeros(); + b.pcode.push((output, Op::IntRight, (primask_reg, shift))); + // Even though the p-code operation is called "isIRQinterruptsEnabled" it is intended to + // returns the value of `PRIMASK` directly (which is 0 when interrupts are enabled, and + // 1 when they are enabled). + // b.pcode.push((output, Op::IntNot, output)); + b.pcode.push((output, Op::IntAnd, (output, 1))); + false + }, + ); + + override_op( + vm, + "getBasePriority", + move |_: &dyn InstructionSource, _, _, output: pcode::VarNode, b: &mut BlockState| { + b.pcode.push((output, Op::IntAnd, (basepri_reg, 0xff_u32))); + false + }, + ); + + override_op( + vm, + "setBasePriority", + move |_: &dyn InstructionSource, _, inputs: pcode::Inputs, _, b: &mut BlockState| { + let val = inputs.first(); + b.pcode.push((basepri_reg, Op::IntAnd, (val, 0xff_u32))); + false + }, + ); + + // Control registers. controls current mode of the stack and whether thread mode should be + // privileged. + // + // Bit 0: Execution privilege in thread mode: + // - 0: thread mode is unprivilege + // - 1: thread mode is privilege + // + // Bit 1: + // - 0: thread mode + // - 1: main-stack mode + let control = vm.cpu.arch.sleigh.add_custom_reg("spsel", 4).unwrap(); + // Cached copy of spsel bit 1 + let current_sp_mode_is_psp = + vm.cpu.arch.sleigh.add_custom_reg("curr_sp_mode_is_psp", 4).unwrap(); + + override_op( + vm, + "isCurrentModePrivileged", + move |_: &dyn InstructionSource, _, _, output: pcode::VarNode, b: &mut BlockState| { + // Assume always true to match unicorn behavior. + // Note: should be equal to `control & 1` in thread mode and `1` in main stack mode. + b.pcode.push((output, Op::Copy, pcode::Value::Const(1, output.size))); + false + }, + ); + + override_op( + vm, + "isThreadModePrivileged", + move |_: &dyn InstructionSource, _, _, output: pcode::VarNode, b: &mut BlockState| { + b.pcode.push((output, Op::IntAnd, (control.truncate(1), 0b1_u8))); + false + }, + ); + + override_op( + vm, + "setThreadModePrivileged", + move |_: &dyn InstructionSource, _, inputs: pcode::Inputs, _, b: &mut BlockState| { + let is_privileged = inputs.first(); + b.pcode.push((control, Op::IntAnd, (control, !0b1_u32))); + b.pcode.push((control.truncate(1), Op::IntOr, (control.truncate(1), is_privileged))); + false + }, + ); + + override_op( + vm, + "isThreadMode", + move |_: &dyn InstructionSource, _, _, output: pcode::VarNode, b: &mut BlockState| { + b.pcode.push((output, Op::IntEqual, (current_sp_mode_is_psp, 1_u32))); + false + }, + ); + + let sp_reg = vm.cpu.arch.reg_sp; + override_op( + vm, + "getMainStackPointer", + move |_: &dyn InstructionSource, _, _, output, b: &mut BlockState| { + b.pcode.select(output, current_sp_mode_is_psp, other_sp_reg, sp_reg); + false + }, + ); + override_op( + vm, + "setMainStackPointer", + move |_: &dyn InstructionSource, _, inputs: pcode::Inputs, _, b: &mut BlockState| { + let val = inputs.first(); + b.pcode.select(sp_reg, current_sp_mode_is_psp, sp_reg, val); + b.pcode.select(other_sp_reg, current_sp_mode_is_psp, val, other_sp_reg); + false + }, + ); + override_op( + vm, + "getProcessStackPointer", + move |_: &dyn InstructionSource, _, _, output, b: &mut BlockState| { + b.pcode.select(output, current_sp_mode_is_psp, sp_reg, other_sp_reg); + false + }, + ); + override_op( + vm, + "setProcessStackPointer", + move |_: &dyn InstructionSource, _, inputs: pcode::Inputs, _, b: &mut BlockState| { + let val = inputs.first(); + b.pcode.select(sp_reg, current_sp_mode_is_psp, val, sp_reg); + b.pcode.select(other_sp_reg, current_sp_mode_is_psp, other_sp_reg, val); + false + }, + ); + override_op( + vm, + "setStackMode", + move |_: &dyn InstructionSource, _, inputs: pcode::Inputs, _, b: &mut BlockState| { + let mode = b.pcode.alloc_tmp(4); + b.pcode.push((mode, Op::ZeroExtend, inputs.first())); + + // Switch stacks if required. + let mode_changed = b.pcode.alloc_tmp(1); + b.pcode.push((mode_changed, Op::IntEqual, (current_sp_mode_is_psp, mode))); + let new_sp = b.pcode.alloc_tmp(4); + b.pcode.select(new_sp, mode_changed, other_sp_reg, sp_reg); + b.pcode.select(other_sp_reg, mode_changed, sp_reg, other_sp_reg); + b.pcode.push((sp_reg, Op::Copy, new_sp)); + + // Update registers that keep track of the current stack mode. + b.pcode.push((current_sp_mode_is_psp, Op::Copy, mode)); + b.pcode.push((mode, Op::IntLeft, (mode, 1_u32))); + b.pcode.push((control, Op::IntAnd, (control, !0b10_u32))); + b.pcode.push((control, Op::IntOr, (control, mode))); + false + }, + ); + override_op( + vm, + "isUsingMainStack", + move |_: &dyn InstructionSource, _, _, output: pcode::VarNode, b: &mut BlockState| { + b.pcode.push((output, Op::IntEqual, (current_sp_mode_is_psp, 0_u32))); + false + }, + ); +} + +fn override_op(vm: &mut Vm, name: &str, injector: impl PcodeOpInjector + Sized + 'static) { + let idx = vm.cpu.arch.sleigh.get_userop(name).unwrap(); + vm.lifter.op_injectors.insert(idx, Box::new(injector)); +} + +/// Returns a mapping from Unicorn Register IDs to Icicle varnodes. +pub(crate) fn map_uc_to_varnodes(vm: &Vm) -> Vec { + let mut vars = vec![pcode::VarNode::NONE; UC_ARM_REG_ENDING as usize]; + + let mut map = |id: u32, name: &str| match vm.cpu.arch.sleigh.get_reg(name) { + Some(reg) => vars[id as usize] = reg.var, + None => eprintln!("{name} not found in Sleigh spec"), + }; + + // map(UC_ARM_REG_APSR, "apsr"); + // map(UC_ARM_REG_APSR_NZCV, "apsr_nzcv"); + map(UC_ARM_REG_CPSR, "cpsr"); + map(UC_ARM_REG_FPEXC, "fpexc"); + // map(UC_ARM_REG_FPINST, "fpinst"); + map(UC_ARM_REG_FPSCR, "fpscr"); + // map(UC_ARM_REG_FPSCR_NZCV, "fpscr_nzcv"); + map(UC_ARM_REG_FPSID, "fpsid"); + // map(UC_ARM_REG_ITSTATE, "itstate"); + map(UC_ARM_REG_LR, "lr"); + map(UC_ARM_REG_PC, "pc"); + map(UC_ARM_REG_SP, "sp"); + map(UC_ARM_REG_SPSR, "spsr"); + map(UC_ARM_REG_D0, "d0"); + map(UC_ARM_REG_D1, "d1"); + map(UC_ARM_REG_D2, "d2"); + map(UC_ARM_REG_D3, "d3"); + map(UC_ARM_REG_D4, "d4"); + map(UC_ARM_REG_D5, "d5"); + map(UC_ARM_REG_D6, "d6"); + map(UC_ARM_REG_D7, "d7"); + map(UC_ARM_REG_D8, "d8"); + map(UC_ARM_REG_D9, "d9"); + map(UC_ARM_REG_D10, "d10"); + map(UC_ARM_REG_D11, "d11"); + map(UC_ARM_REG_D12, "d12"); + map(UC_ARM_REG_D13, "d13"); + map(UC_ARM_REG_D14, "d14"); + map(UC_ARM_REG_D15, "d15"); + map(UC_ARM_REG_D16, "d16"); + map(UC_ARM_REG_D17, "d17"); + map(UC_ARM_REG_D18, "d18"); + map(UC_ARM_REG_D19, "d19"); + map(UC_ARM_REG_D20, "d20"); + map(UC_ARM_REG_D21, "d21"); + map(UC_ARM_REG_D22, "d22"); + map(UC_ARM_REG_D23, "d23"); + map(UC_ARM_REG_D24, "d24"); + map(UC_ARM_REG_D25, "d25"); + map(UC_ARM_REG_D26, "d26"); + map(UC_ARM_REG_D27, "d27"); + map(UC_ARM_REG_D28, "d28"); + map(UC_ARM_REG_D29, "d29"); + map(UC_ARM_REG_D30, "d30"); + map(UC_ARM_REG_D31, "d31"); + // map(UC_ARM_REG_FPINST2, "fpinst2"); + map(UC_ARM_REG_MVFR0, "mvfr0"); + map(UC_ARM_REG_MVFR1, "mvfr1"); + // map(UC_ARM_REG_MVFR2, "mvfr2"); + map(UC_ARM_REG_Q0, "q0"); + map(UC_ARM_REG_Q1, "q1"); + map(UC_ARM_REG_Q2, "q2"); + map(UC_ARM_REG_Q3, "q3"); + map(UC_ARM_REG_Q4, "q4"); + map(UC_ARM_REG_Q5, "q5"); + map(UC_ARM_REG_Q6, "q6"); + map(UC_ARM_REG_Q7, "q7"); + map(UC_ARM_REG_Q8, "q8"); + map(UC_ARM_REG_Q9, "q9"); + map(UC_ARM_REG_Q10, "q10"); + map(UC_ARM_REG_Q11, "q11"); + map(UC_ARM_REG_Q12, "q12"); + map(UC_ARM_REG_Q13, "q13"); + map(UC_ARM_REG_Q14, "q14"); + map(UC_ARM_REG_Q15, "q15"); + map(UC_ARM_REG_R0, "r0"); + map(UC_ARM_REG_R1, "r1"); + map(UC_ARM_REG_R2, "r2"); + map(UC_ARM_REG_R3, "r3"); + map(UC_ARM_REG_R4, "r4"); + map(UC_ARM_REG_R5, "r5"); + map(UC_ARM_REG_R6, "r6"); + map(UC_ARM_REG_R7, "r7"); + map(UC_ARM_REG_R8, "r8"); + map(UC_ARM_REG_R9, "r9"); + map(UC_ARM_REG_R10, "r10"); + map(UC_ARM_REG_R11, "r11"); + map(UC_ARM_REG_R12, "r12"); + map(UC_ARM_REG_S0, "s0"); + map(UC_ARM_REG_S1, "s1"); + map(UC_ARM_REG_S2, "s2"); + map(UC_ARM_REG_S3, "s3"); + map(UC_ARM_REG_S4, "s4"); + map(UC_ARM_REG_S5, "s5"); + map(UC_ARM_REG_S6, "s6"); + map(UC_ARM_REG_S7, "s7"); + map(UC_ARM_REG_S8, "s8"); + map(UC_ARM_REG_S9, "s9"); + map(UC_ARM_REG_S10, "s10"); + map(UC_ARM_REG_S11, "s11"); + map(UC_ARM_REG_S12, "s12"); + map(UC_ARM_REG_S13, "s13"); + map(UC_ARM_REG_S14, "s14"); + map(UC_ARM_REG_S15, "s15"); + map(UC_ARM_REG_S16, "s16"); + map(UC_ARM_REG_S17, "s17"); + map(UC_ARM_REG_S18, "s18"); + map(UC_ARM_REG_S19, "s19"); + map(UC_ARM_REG_S20, "s20"); + map(UC_ARM_REG_S21, "s21"); + map(UC_ARM_REG_S22, "s22"); + map(UC_ARM_REG_S23, "s23"); + map(UC_ARM_REG_S24, "s24"); + map(UC_ARM_REG_S25, "s25"); + map(UC_ARM_REG_S26, "s26"); + map(UC_ARM_REG_S27, "s27"); + map(UC_ARM_REG_S28, "s28"); + map(UC_ARM_REG_S29, "s29"); + map(UC_ARM_REG_S30, "s30"); + map(UC_ARM_REG_S31, "s31"); + // map(UC_ARM_REG_C1_C0_2, "c1_c0_2"); + // map(UC_ARM_REG_C13_C0_2, "c13_c0_2"); + // map(UC_ARM_REG_C13_C0_3, "c13_c0_3"); + // map(UC_ARM_REG_IPSR, "ipsr"); + // map(UC_ARM_REG_MSP, "msp"); + // map(UC_ARM_REG_PSP, "psp"); + // map(UC_ARM_REG_CONTROL, "control"); + + // Registers missing from SLEIGH spec + map(UC_ARM_REG_XPSR, "xpsr"); + map(UC_ARM_REG_OTHER_SP, "other_sp"); + map(UC_ARM_REG_CURR_SP_MODE_IS_PSP, "curr_sp_mode_is_psp"); + map(UC_ARM_REG_SPSEL, "spsel"); + map(UC_ARM_REG_BASEPRI, "basepri"); + map(UC_ARM_REG_PRIMASK, "primask"); + + // These are aliases to other registers. + // map(UC_ARM_REG_R13, "sp"); + // map(UC_ARM_REG_R14, "lr"); + // map(UC_ARM_REG_R15, "pc"); + // map(UC_ARM_REG_SB, "r9"); + // map(UC_ARM_REG_SL, "r10"); + // map(UC_ARM_REG_FP, "r11"); + // map(UC_ARM_REG_IP, "r12"); + + vars +} diff --git a/icicle/src/fuzzware.rs b/icicle/src/fuzzware.rs new file mode 100644 index 0000000..9ceb8fd --- /dev/null +++ b/icicle/src/fuzzware.rs @@ -0,0 +1,14 @@ +#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/fuzzware_bindings.rs")); + +pub const NVIC_EXCEPT_MAGIC_RET_MASK: u64 = 0xfffffff0; + +pub const ANY_MEM_HOOK: uc_hook_type::Type = uc_hook_type::UC_HOOK_MEM_WRITE + | uc_hook_type::UC_HOOK_MEM_READ + | uc_hook_type::UC_HOOK_MEM_READ_AFTER + | uc_hook_type::UC_HOOK_MEM_WRITE_PROT + | uc_hook_type::UC_HOOK_MEM_READ_PROT; + +pub const ANY_BLOCK_HOOK: uc_hook_type::Type = + uc_hook_type::UC_HOOK_BLOCK | uc_hook_type::UC_HOOK_BLOCK_UNCONDITIONAL; diff --git a/icicle/src/lib.rs b/icicle/src/lib.rs new file mode 100644 index 0000000..1bcd1d7 --- /dev/null +++ b/icicle/src/lib.rs @@ -0,0 +1,820 @@ +#![feature(backtrace)] + +pub mod fuzzware; + +mod arm; +mod unicorn_api; + +use std::{ + cell::UnsafeCell, + collections::{HashMap, HashSet}, +}; + +use icicle_vm::{ + cpu::{ + mem::{perm, Mapping, ReadAfterHook, ReadHook, WriteHook}, + Cpu, + }, + Vm, +}; + +use once_cell::sync::Lazy; +use pyo3::{exceptions::PyValueError, prelude::*, types::PyByteArray, AsPyPointer}; + +use crate::{ + fuzzware::{uc_hook, uc_hook_type}, + unicorn_api::{get_u64, uc_perms_to_icicle_perms, FuzzwareEnvironment}, +}; + +#[allow(non_camel_case_types)] +type uc_err = fuzzware::uc_err::Type; + +fn into_py_err(err: impl std::fmt::Display) -> PyErr { + PyValueError::new_err(err.to_string()) +} + +#[allow(unused)] +pub(crate) enum HookKind { + Mem { read: Option, read_after: Option, write: Option, fault: Option }, + Block { injector_id: usize }, + InterruptRetHook, + SyscallHook, +} + +pub(crate) struct Context { + pub vm: Vm, + pub uc_vars: Vec, + pub regs: arm::SpecialRegs, + py_callbacks: Vec<(PyObject, Option)>, + py_default_mmio_user_data: Option, + vtable: Option>, + + path_tracer_hook: pcode::HookId, + print_exit_info: bool, + kill_on_next_run: bool, + stop_requested: bool, + exec_count: usize, + + next_hook_id: usize, + hooks: HashMap, + coverage_bitmap: Option<(*mut u8, u32)>, + prev_pc_var: Option, + context_var: Option, + crashing_addresses: HashSet, + + reads: Vec<(u64, u64, u32)>, + last_block_count: usize, +} + +impl Context { + pub fn new(disable_shadow_stack: bool) -> PyResult { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_env("ICICLE_LOG")) + .without_time() + .init(); + + std::env::set_var("ICICLE_ENABLE_JIT_VERIFIER", "false"); + let mut config = icicle_vm::cpu::Config::from_target_triple("arm-none"); + + let disable_shadow_stack = std::env::var("ICICLE_DISABLE_SHADOW_STACK") + .ok() + .map_or(disable_shadow_stack, |x| x == "1"); + config.enable_shadow_stack = !disable_shadow_stack; + let mut vm = icicle_vm::build(&config).map_err(into_py_err)?; + + arm::add_arm_extras(&mut vm); + let vars = arm::map_uc_to_varnodes(&vm); + let regs = arm::SpecialRegs::get(&mut vm); + + vm.env = Box::new(unicorn_api::FuzzwareEnvironment::new()); + + let path_tracer_hook = vm.cpu.add_hook(Box::new(unicorn_api::PathTracer::new())); + + Ok(Self { + vm, + regs, + py_callbacks: vec![], + py_default_mmio_user_data: None, + uc_vars: vars, + vtable: None, + path_tracer_hook, + print_exit_info: false, + kill_on_next_run: false, + stop_requested: false, + next_hook_id: 0, + exec_count: 0, + hooks: HashMap::new(), + coverage_bitmap: None, + prev_pc_var: None, + context_var: None, + crashing_addresses: HashSet::new(), + reads: vec![], + last_block_count: 0, + }) + } + + pub fn register_hook(&mut self, kind: HookKind) -> usize { + self.hooks.insert(self.next_hook_id, kind); + self.next_hook_id += 1; + self.next_hook_id - 1 + } + + pub fn add_mem_hook( + &mut self, + kind: uc_hook_type::Type, + begin: u64, + end: u64, + hook: H, + ) -> Option + where + H: Clone + WriteHook + ReadHook + ReadAfterHook + unicorn_api::ExceptionHook + 'static, + { + // @fixme: clean up partially created hooks on failure. + + let mut write = None; + if kind & uc_hook_type::UC_HOOK_MEM_WRITE != 0 { + write = Some(self.vm.cpu.mem.add_write_hook(begin, end, Box::new(hook.clone()))?); + } + + let mut read = None; + if kind & uc_hook_type::UC_HOOK_MEM_READ != 0 { + read = Some(self.vm.cpu.mem.add_read_hook(begin, end, Box::new(hook.clone()))?); + } + + let mut read_after = None; + if kind & uc_hook_type::UC_HOOK_MEM_READ_AFTER != 0 { + read_after = + Some(self.vm.cpu.mem.add_read_after_hook(begin, end, Box::new(hook.clone()))?); + } + + let mut fault = None; + if kind & uc_hook_type::UC_HOOK_MEM_WRITE_PROT != 0 + || kind & uc_hook_type::UC_HOOK_MEM_READ_PROT != 0 + { + let env = self.vm.env.as_any().downcast_mut::().unwrap(); + fault = Some(env.add_mem_fault_hook(begin, end, kind, Box::new(hook))?) + } + + Some(self.register_hook(HookKind::Mem { read, read_after, write, fault })) + } + + pub fn add_block_hook(&mut self, begin: u64, end: u64, hook: H) -> Option + where + H: icicle_vm::cpu::Hook + 'static, + { + let hook_id = self.vm.cpu.add_hook(Box::new(hook)); + let injector_id = + icicle_vm::injector::register_block_hook_injector(&mut self.vm, begin, end, hook_id); + Some(self.register_hook(HookKind::Block { injector_id })) + } + + pub fn get_reg_slice(&mut self, regid: i32) -> Option<&mut [u8]> { + let var = *self.uc_vars.get(regid as usize)?; + if var == pcode::VarNode::NONE { + // Print backtrace so we know where in fuzzware + eprintln!( + "No Icicle variable found for regid={regid}\n{}", + std::backtrace::Backtrace::force_capture(), + ); + } + self.vm.cpu.regs.get_mut(var) + } + + // Super unsafe: creates a self referencing struct. + unsafe fn build_vtable(&mut self) { + use unicorn_api::*; + let vtable = Box::new(fuzzware::uc_engine { + ctx: (self as *mut Context).cast(), + emu_start: Some(emu_start), + emu_stop: Some(emu_stop), + reg_read: Some(reg_read), + reg_read_batch: Some(reg_read_batch), + reg_write: Some(reg_write), + reg_write_batch: Some(reg_write_batch), + reg_ptr: Some(reg_ptr), + mem_read: Some(mem_read), + mem_write: Some(mem_write), + mem_set: Some(mem_set), + mem_protect: Some(mem_protect), + mem_regions: Some(mem_regions), + block_hook_add: Some(block_hook_add), + mem_hook_add: Some(mem_hook_add), + int_hook_add: Some(int_hook_add), + hook_del: Some(hook_del), + context_alloc: Some(context_alloc), + context_save: Some(context_save), + context_restore: Some(context_restore), + free: Some(free), + fuzzer_init_cov: Some(fuzzer_init_cov), + fuzzer_reset_cov: Some(fuzzer_reset_cov), + save_info: Some(save_info), + }); + self.vtable = Some(vtable); + } +} + +#[pyclass(dict, unsendable)] +pub struct Uc { + ctx: UnsafeCell>, +} + +impl Uc { + unsafe fn uc_ptr(&self) -> *mut fuzzware::uc_engine { + (*self.ctx.get()).vtable.as_mut().unwrap().as_mut() + } +} + +#[pymethods] +impl Uc { + #[new] + fn new(arch: i32, mode: u32, disable_shadow_stack: bool) -> PyResult { + eprintln!("Uc.__init__({arch}, {mode}, {disable_shadow_stack})"); + let mut ctx = Box::new(Context::new(disable_shadow_stack)?); + unsafe { ctx.build_vtable() }; + + let vtable = ctx.vtable.as_mut().unwrap().as_mut() as *mut fuzzware::uc_engine; + eprintln!("ctx: {ctx:p}, vtable: {vtable:p}"); + + Ok(Self { ctx: UnsafeCell::new(ctx) }) + } + + #[args(timeout = "0", count = "0")] + fn emu_start(&mut self, _begin: u64, _until: u64, _timeout: u64, _count: u64) -> PyResult<()> { + Err(PyValueError::new_err("unimplemented: emu_start")) + } + + fn emu_stop(&mut self) -> PyResult<()> { + Err(PyValueError::new_err("unimplemented: emu_stop")) + } + + #[args(opt = "None")] + fn reg_read(&self, regid: i32, _opt: Option<&str>) -> PyResult { + let value = unsafe { &mut *self.ctx.get() } + .get_reg_slice(regid) + .ok_or_else(|| PyValueError::new_err(format!("Unknown register: {regid}")))?; + Ok(get_u64(value)) + } + + fn reg_write(&self, regid: i32, value: u64) -> PyResult<()> { + eprintln!("Uc.reg_write({regid}, {value:#x})"); + let dst = unsafe { &mut *self.ctx.get() } + .get_reg_slice(regid) + .ok_or_else(|| PyValueError::new_err(format!("Unknown register: {regid}")))?; + dst.copy_from_slice(&value.to_le_bytes()[..dst.len()]); + Ok(()) + } + + fn mem_read<'py>( + &self, + py: Python<'py>, + address: u64, + size: u64, + ) -> PyResult<&'py PyByteArray> { + eprintln!("Uc.mem_read({address:#x}, {size:#x})"); + let mut buf = vec![0; size as usize]; + unsafe { &mut *self.ctx.get() } + .vm + .cpu + .mem + .read_bytes(address, &mut buf, perm::NONE) + .map_err(into_py_err)?; + Ok(PyByteArray::new(py, &buf)) + } + + fn mem_write(&mut self, address: u64, data: &[u8]) -> PyResult<()> { + eprintln!("Uc.mem_write({address:#x}, {:#x})", data.len()); + self.ctx.get_mut().vm.cpu.mem.write_bytes(address, data, perm::NONE).map_err(into_py_err) + } + + #[args(perms = "fuzzware::uc_prot::UC_PROT_ALL")] + fn mem_map(&mut self, address: u64, size: u64, perms: u32) -> PyResult<()> { + let perms = uc_perms_to_icicle_perms(perms); + eprintln!("Uc.mem_map({address:#x}, {size:#x}, {})", perm::display(perms)); + if !self + .ctx + .get_mut() + .vm + .cpu + .mem + .map_memory(address, address + size, Mapping { perm: perms | perm::INIT, value: 0x0 }) + { + return Err(PyValueError::new_err("map_memory failed")); + } + Ok(()) + } + + fn mem_map_ptr(&mut self, _address: u64, _size: u64, _perms: u64, _ptr: u64) -> PyResult<()> { + Err(PyValueError::new_err("unimplemented: mem_map_ptr")) + } + + fn mem_unmap(&mut self, _address: u64, _size: u64) -> PyResult<()> { + Err(PyValueError::new_err("unimplemented: mem_unmap")) + } + + #[args(perms = "fuzzware::uc_prot::UC_PROT_ALL")] + fn mem_protect(&mut self, address: u64, size: u64, perms: u32) -> PyResult<()> { + let perms = uc_perms_to_icicle_perms(perms); + eprintln!("Uc.mem_protect({address:#x}, {size:#x}, {})", perm::display(perms)); + self.ctx.get_mut().vm.cpu.mem.update_perm(address, size, perms).map_err(into_py_err) + } + + fn query(&mut self, _query_mode: u64) -> PyResult<()> { + Err(PyValueError::new_err("unimplemented: query")) + } + + #[args(begin = "1", end = "0", _arg1 = "0")] + fn hook_add<'py>( + mut self_: PyRefMut<'py, Self>, + htype: uc_hook_type::Type, + callback: PyObject, + user_data: Option, + begin: u64, + end: u64, + _arg1: u64, + ) -> PyResult { + eprintln!( + "Uc.hook_add(htype={htype}, callback={callback:?}, \ + userdata={user_data:?}, begin={begin:#x}, end={end:#x})" + ); + + let id = self_.ctx.get_mut().py_callbacks.len(); + self_.ctx.get_mut().py_callbacks.push((callback, user_data)); + + let hook = PythonHook { uc: self_.as_ptr(), id }; + if htype & fuzzware::ANY_MEM_HOOK != 0 { + return self_ + .ctx + .get_mut() + .add_mem_hook(htype, begin, end, hook) + .ok_or_else(|| PyValueError::new_err("Failed to add mem hook")); + } + + if htype & fuzzware::ANY_BLOCK_HOOK != 0 { + return self_ + .ctx + .get_mut() + .add_block_hook(begin, end, hook) + .ok_or_else(|| PyValueError::new_err("Failed to add block hook")); + } + + Err(PyValueError::new_err(format!("unimplemented: hook_add: {htype}"))) + } + + fn set_debug_file(&mut self, path: String) -> PyResult<()> { + let env = self.ctx.get_mut().vm.env.as_any().downcast_mut::().unwrap(); + env.set_debug_info(path.as_ref()).map_err(|e| PyValueError::new_err(e)) + } + + fn native_init<'py>( + &mut self, + py: Python<'py>, + py_exit_hook: Option, + mmio_regions: Vec<(u64, u64)>, + py_default_mmio_user_data: PyObject, + mut exit_at_bbls: Vec, + exit_at_hit_num: u32, + p_do_print_exit_info: i32, + fuzz_consumption_timeout: u64, + p_instr_limit: u64, + ) -> PyResult { + let print_exit_info = p_do_print_exit_info != 0; + self.ctx.get_mut().print_exit_info = print_exit_info; + + if print_exit_info || unicorn_api::TRACE_PATHS { + let ctx = self.ctx.get_mut(); + icicle_vm::injector::register_block_hook_injector( + &mut ctx.vm, + 0, + u64::MAX, + ctx.path_tracer_hook, + ); + } + + eprintln!( + "Uc.native_init({py_exit_hook:0x?}, {mmio_regions:x?}, {py_default_mmio_user_data:?}, {exit_at_bbls:0x?}, {print_exit_info}, {fuzz_consumption_timeout}, {p_instr_limit})", + ); + eprintln!("emulator pid = {}", std::process::id()); + let mut starts: Vec = mmio_regions.iter().map(|(s, _)| *s).collect(); + let mut ends: Vec = mmio_regions.iter().map(|(_, e)| *e).collect(); + + let exit_hook = py_exit_hook.map(|hook| { + *PY_EXIT_HOOK.lock().unwrap() = Some(hook); + call_python_exit_hook as ExitHookNonNull + }); + + let result = unsafe { + fuzzware::init( + self.uc_ptr(), + exit_hook, + mmio_regions.len() as i32, + starts.as_mut_ptr(), + ends.as_mut_ptr(), + py_default_mmio_user_data.as_ref(py).as_ptr() as *mut _, + exit_at_bbls.len() as u32, + exit_at_bbls.as_mut_ptr(), + exit_at_hit_num, + p_do_print_exit_info, + fuzz_consumption_timeout, + p_instr_limit, + ) + }; + + // Keep around user_data to prevent it from being GCed by the python runtime. + self.ctx.get_mut().py_default_mmio_user_data = Some(py_default_mmio_user_data); + + Ok(result) + } + + fn native_init_timer_hook(&mut self, global_timer_scale: u32) -> PyResult { + eprintln!("Uc.init_timer_hook({global_timer_scale})"); + let result = unsafe { fuzzware::init_timer_hook(self.uc_ptr(), global_timer_scale) }; + Ok(result) + } + + fn native_init_systick(&mut self, reload_val: u32) -> PyResult { + eprintln!("Uc.native_init_systick({reload_val})"); + let result = unsafe { fuzzware::init_systick(self.uc_ptr(), reload_val) }; + Ok(result) + } + + fn native_add_interrupt_trigger( + &mut self, + addr: u64, + irq: u32, + num_skips: u32, + num_pends: u32, + fuzz_mode: u32, + trigger_mode: u32, + every_nth_tick: u64, + ) -> PyResult { + eprintln!( + "Uc.add_interrupt_trigger({addr:#x}, {irq}, {num_skips}, {num_pends}, {fuzz_mode}, {trigger_mode}, {every_nth_tick})" + ); + let result = unsafe { + fuzzware::add_interrupt_trigger( + self.uc_ptr(), + addr, + irq, + num_skips, + num_pends, + fuzz_mode, + trigger_mode, + every_nth_tick, + ) + }; + Ok(result) + } + + fn native_register_py_handled_mmio_ranges(&mut self) -> PyResult<()> { + todo!() + } + + fn native_register_linear_mmio_models( + &mut self, + mut starts: Vec, + mut ends: Vec, + mut pcs: Vec, + mut init_vals: Vec, + mut steps: Vec, + ) -> PyResult { + let count = starts.len(); + assert!( + ends.len() == count + && pcs.len() == count + && init_vals.len() == count + && steps.len() == count + ); + unsafe { + Ok(fuzzware::register_linear_mmio_models( + self.uc_ptr(), + starts.as_mut_ptr(), + ends.as_mut_ptr(), + pcs.as_mut_ptr(), + init_vals.as_mut_ptr(), + steps.as_mut_ptr(), + count as i32, + )) + } + } + + fn native_register_constant_mmio_models( + &mut self, + mut starts: Vec, + mut ends: Vec, + mut pcs: Vec, + mut vals: Vec, + ) -> PyResult { + let count = starts.len(); + assert!(ends.len() == count && pcs.len() == count && vals.len() == count); + unsafe { + Ok(fuzzware::register_constant_mmio_models( + self.uc_ptr(), + starts.as_mut_ptr(), + ends.as_mut_ptr(), + pcs.as_mut_ptr(), + vals.as_mut_ptr(), + count as i32, + )) + } + } + + fn native_register_bitextract_mmio_models( + &mut self, + mut starts: Vec, + mut ends: Vec, + mut pcs: Vec, + mut byte_sizes: Vec, + mut left_shifts: Vec, + mut masks: Vec, + ) -> PyResult { + let count = starts.len(); + assert!( + ends.len() == count + && pcs.len() == count + && byte_sizes.len() == count + && left_shifts.len() == count + && masks.len() == count + ); + unsafe { + Ok(fuzzware::register_bitextract_mmio_models( + self.uc_ptr(), + starts.as_mut_ptr(), + ends.as_mut_ptr(), + pcs.as_mut_ptr(), + byte_sizes.as_mut_ptr(), + left_shifts.as_mut_ptr(), + masks.as_mut_ptr(), + count as i32, + )) + } + } + + fn native_register_value_set_mmio_models( + &mut self, + mut starts: Vec, + mut ends: Vec, + mut pcs: Vec, + mut value_sets: Vec>, + ) -> PyResult { + let count = starts.len(); + assert!(ends.len() == count && pcs.len() == count && value_sets.len() == count); + + let mut value_nums: Vec<_> = value_sets.iter().map(|x| x.len() as u32).collect(); + let mut value_lists: Vec<_> = value_sets.iter_mut().map(|x| x.as_mut_ptr()).collect(); + + unsafe { + Ok(fuzzware::register_value_set_mmio_models( + self.uc_ptr(), + starts.as_mut_ptr(), + ends.as_mut_ptr(), + pcs.as_mut_ptr(), + value_nums.as_mut_ptr(), + value_lists.as_mut_ptr(), + count as i32, + )) + } + } + + fn native_set_ignored_mmio_addresses( + &mut self, + mut addrs: Vec, + mut pcs: Vec, + ) -> PyResult { + let count = addrs.len(); + assert!(addrs.len() == pcs.len()); + unsafe { + Ok(fuzzware::set_ignored_mmio_addresses( + addrs.as_mut_ptr(), + pcs.as_mut_ptr(), + count as i32, + )) + } + } + + fn native_init_nvic( + &mut self, + vtor: u32, + num_irq: u32, + interrupt_limit: u32, + mut disabled_interrupts: Vec, + ) -> PyResult { + eprintln!( + "Uc.native_init_nvic({vtor:#x},{num_irq},{interrupt_limit},{disabled_interrupts:x?})" + ); + let result = unsafe { + fuzzware::init_nvic( + self.uc_ptr(), + vtor, + num_irq, + interrupt_limit, + disabled_interrupts.len() as u32, + disabled_interrupts.as_mut_ptr(), + ) + }; + Ok(result) + } + + fn native_init_tracing( + &mut self, + mut bbl_set_trace_path: Option>, + mut bbl_hash_path: Option>, + mut mmio_set_trace_path: Option>, + mmio_ranges: Vec<(u64, u64)>, + ) -> PyResult { + eprintln!( + r#"Uc.native_init_tracing("{}", "{}", "{}", {mmio_ranges:0x?})"#, + display_path(&bbl_set_trace_path), + display_path(&bbl_hash_path), + display_path(&mmio_set_trace_path), + ); + + let mut starts: Vec = mmio_ranges.iter().map(|(s, _)| *s).collect(); + let mut ends: Vec = mmio_ranges.iter().map(|(_, e)| *e).collect(); + + let result = unsafe { + fuzzware::init_tracing( + self.uc_ptr(), + get_path_ptr(&mut bbl_set_trace_path), + get_path_ptr(&mut bbl_hash_path), + get_path_ptr(&mut mmio_set_trace_path), + mmio_ranges.len(), + starts.as_mut_ptr(), + ends.as_mut_ptr(), + ) + }; + Ok(result) + } + + fn native_emulate( + &self, + fuzz_file_path: Vec, + mut prefix_input_file_path: Option>, + ) -> PyResult { + eprintln!( + "Uc.native_emulate(\"{}\", \"{}\")", + fuzz_file_path.escape_ascii(), + display_path(&prefix_input_file_path) + ); + let mut fuzz_file_path = Some(fuzz_file_path); + let result = unsafe { + fuzzware::emulate( + self.uc_ptr(), + get_path_ptr(&mut fuzz_file_path), + get_path_ptr(&mut prefix_input_file_path), + ) + }; + Ok(result) + } + + fn add_timer<'py>( + &mut self, + py: Python<'py>, + reload_value: u64, + callback: Option, + isr_number: u64, + ) -> PyResult { + eprintln!( + "Uc.add_timer({reload_value}, {:p}, {isr_number})", + callback.map_or(std::ptr::null(), |x| x.as_ref(py).as_ptr()) + ); + Ok(0) + } +} + +fn get_path_ptr(path: &mut Option>) -> *mut i8 { + match path.as_mut() { + Some(path) => { + path.push(0); + path.as_mut_ptr().cast() + } + None => std::ptr::null_mut(), + } +} + +fn display_path<'a>(path: &'a Option>) -> impl std::fmt::Display + 'a { + static EMPTY: Vec = Vec::new(); + path.as_ref().unwrap_or(&EMPTY).escape_ascii() +} + +#[derive(Clone)] +pub struct PythonHook { + uc: *mut pyo3::ffi::PyObject, + id: usize, +} + +impl PythonHook { + fn do_call(&self, handle_call: F) + where + for<'a> F: FnOnce(&'a PyAny, Py, Option<&PyAny>) -> PyResult<&'a PyAny>, + { + use pyo3::conversion::FromPyPointer; + + Python::with_gil(|py| { + let uc: Py = + unsafe { PyAny::from_borrowed_ptr_or_panic(py, self.uc) }.extract().unwrap(); + let uc_ref = uc.clone(); + + let (callback, user_data) = &unsafe { &*uc.borrow(py).ctx.get() }.py_callbacks[self.id]; + + let result = (handle_call)( + callback.as_ref(py), + uc_ref, + user_data.as_ref().map(|x| x.as_ref(py)), + ); + if let Err(e) = result { + e.print(py); + panic!("Python Error"); + } + }); + } +} + +impl icicle_vm::cpu::Hook for PythonHook { + fn call(&mut self, _cpu: &mut Cpu, pc: u64) { + self.do_call(|callback, uc, user_data| callback.call1((uc, pc, 0, user_data))) + } + + fn as_any(&mut self) -> &mut dyn std::any::Any { + self + } +} + +impl WriteHook for PythonHook { + fn write(&mut self, _mem: &mut icicle_vm::cpu::Mmu, addr: u64, value: &[u8]) { + self.do_call(|callback, uc, user_data| { + let mtype = fuzzware::uc_mem_type::UC_MEM_WRITE; + callback.call1((uc, mtype, addr, value.len(), get_u64(value), user_data)) + }) + } +} + +impl ReadHook for PythonHook { + fn read(&mut self, _mem: &mut icicle_vm::cpu::Mmu, addr: u64, size: u8) { + self.do_call(|callback, uc, user_data| { + let mtype = fuzzware::uc_mem_type::UC_MEM_READ; + callback.call1((uc, mtype, addr, size as usize, 0, user_data)) + }) + } +} + +impl ReadAfterHook for PythonHook { + fn read(&mut self, _mem: &mut icicle_vm::cpu::Mmu, addr: u64, value: &[u8]) { + self.do_call(|callback, uc, user_data| { + let mtype = fuzzware::uc_mem_type::UC_MEM_READ_AFTER; + callback.call1((uc, mtype, addr, value.len(), get_u64(value), user_data)) + }) + } +} + +impl unicorn_api::ExceptionHook for PythonHook { + fn handle_exception(&mut self, _addr: u64, _kind: fuzzware::uc_mem_type::Type) -> bool { + todo!() + } +} + +static PY_EXIT_HOOK: Lazy>> = + Lazy::new(|| std::sync::Mutex::default()); + +type ExitHookNonNull = unsafe extern "C" fn(i32, i32); + +extern "C" fn call_python_exit_hook(status: i32, kill_signal: i32) { + Python::with_gil(|py| { + let guard = PY_EXIT_HOOK.lock().unwrap(); + if let Some(hook) = guard.as_ref() { + if let Err(e) = hook.call1(py, (status, kill_signal)) { + e.print(py); + panic!("Python Error"); + } + } + }) +} + +#[pyfunction] +fn get_latest_mmio_fuzz_access_index() -> PyResult { + Ok(unsafe { fuzzware::get_latest_mmio_fuzz_access_index() }) +} + +#[pyfunction] +fn get_latest_mmio_fuzz_access_size() -> PyResult { + Ok(unsafe { fuzzware::get_latest_mmio_fuzz_access_size() }) +} + +#[pyfunction] +fn fuzz_remaining() -> PyResult { + Ok(unsafe { fuzzware::fuzz_remaining() }) +} + +#[pyfunction] +fn fuzz_consumed() -> PyResult { + Ok(unsafe { fuzzware::fuzz_consumed() }) +} + +#[pymodule] +fn icicle(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + m.add_function(wrap_pyfunction!(get_latest_mmio_fuzz_access_index, m)?)?; + m.add_function(wrap_pyfunction!(get_latest_mmio_fuzz_access_size, m)?)?; + m.add_function(wrap_pyfunction!(fuzz_remaining, m)?)?; + m.add_function(wrap_pyfunction!(fuzz_consumed, m)?)?; + Ok(()) +} diff --git a/icicle/src/unicorn_api.rs b/icicle/src/unicorn_api.rs new file mode 100644 index 0000000..073b1a6 --- /dev/null +++ b/icicle/src/unicorn_api.rs @@ -0,0 +1,926 @@ +use std::{ + io::Write, + os::raw::{c_int, c_void}, +}; + +use icicle_vm::{ + cpu::{ + debug_info::{DebugInfo, SourceLocation}, + mem::perm, + CpuSnapshot, ExceptionCode, Hook, + }, + VmExit, +}; + +use crate::{ + fuzzware::{uc_err::*, *}, + uc_err, Context, HookKind, +}; + +pub(crate) const DEBUG: bool = false; +pub(crate) const TRACE_MMIO_READS: bool = false; +pub(crate) const TRACE_PATHS: bool = false; +pub(crate) const SAVE_DISASM: bool = false; + +macro_rules! debug { + ($($arg:tt)*) => {{ + if DEBUG { + eprintln!($($arg)*) + } + }} +} + +pub fn uc_perms_to_icicle_perms(uc_perms: u32) -> u8 { + let mut perm = perm::MAP; + if uc_perms & uc_prot::UC_PROT_EXEC != 0 { + perm |= perm::EXEC; + } + if uc_perms & uc_prot::UC_PROT_READ != 0 { + perm |= perm::READ; + } + if uc_perms & uc_prot::UC_PROT_WRITE != 0 { + perm |= perm::WRITE; + } + perm +} + +pub fn icicle_perms_to_uc_perms(perm: u8) -> u32 { + let mut uc_perm = 0; + if perm & perm::EXEC != 0 { + uc_perm |= uc_prot::UC_PROT_EXEC; + } + if perm & perm::READ != 0 { + uc_perm |= uc_prot::UC_PROT_READ; + } + if perm & perm::WRITE != 0 { + uc_perm |= uc_prot::UC_PROT_WRITE; + } + uc_perm +} + +fn read_err_to_uc_err(err: icicle_vm::cpu::mem::MemError) -> uc_err { + match err { + icicle_vm::cpu::mem::MemError::Unmapped => UC_ERR_READ_UNMAPPED, + icicle_vm::cpu::mem::MemError::ReadViolation => UC_ERR_READ_PROT, + icicle_vm::cpu::mem::MemError::Unaligned => UC_ERR_READ_UNALIGNED, + icicle_vm::cpu::mem::MemError::OutOfMemory => UC_ERR_NOMEM, + _ => UC_ERR_EXCEPTION, + } +} + +#[allow(unused)] +fn write_err_to_uc_err(err: icicle_vm::cpu::mem::MemError) -> uc_err { + match err { + icicle_vm::cpu::mem::MemError::Unmapped => UC_ERR_WRITE_UNMAPPED, + icicle_vm::cpu::mem::MemError::WriteViolation => UC_ERR_WRITE_PROT, + icicle_vm::cpu::mem::MemError::Unaligned => UC_ERR_WRITE_UNALIGNED, + icicle_vm::cpu::mem::MemError::OutOfMemory => UC_ERR_NOMEM, + _ => UC_ERR_EXCEPTION, + } +} + +pub struct PathTracer { + /// A list of (block address, icount, fuzz_offset) pairs tracking all blocks hit by the + /// emulator. + pub blocks: Vec<(u64, u64, u64)>, +} + +impl PathTracer { + pub fn new() -> Self { + Self { blocks: vec![] } + } +} + +impl icicle_vm::cpu::Hook for PathTracer { + fn call(&mut self, cpu: &mut icicle_vm::cpu::Cpu, pc: u64) { + let fuzz_offset = unsafe { crate::fuzzware::fuzz_consumed() }; + self.blocks.push((pc, cpu.icount(), fuzz_offset as u64)); + } + + fn as_any(&mut self) -> &mut dyn std::any::Any { + self + } +} + +pub trait ExceptionHook { + fn handle_exception(&mut self, addr: u64, kind: uc_mem_type::Type) -> bool; +} + +// uc_engine *uc, uint32_t intno, void *user_data +type SyscallHook = unsafe extern "C" fn(*mut uc_engine, u32, *mut c_void); + +// uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *user_data +type MemCallback = + unsafe extern "C" fn(*mut uc_engine, uc_mem_type::Type, u64, u32, u64, *mut c_void) -> u8; + +#[derive(Clone)] +struct UnicornHook { + vtable: *mut uc_engine, /* Super unsafe: we end up with multiple mutable references, however + * this saves a lot of code rewriting. */ + callback: *const c_void, + userdata: *mut c_void, +} + +impl ExceptionHook for UnicornHook { + fn handle_exception(&mut self, addr: u64, kind: uc_mem_type::Type) -> bool { + unsafe { + let func_ptr: MemCallback = std::mem::transmute(self.callback); + func_ptr(self.vtable, kind, addr, 1, 0, self.userdata) != 0 + } + } +} + +pub fn get_u64(value: &[u8]) -> u64 { + match *value { + [x0] => u8::from_le_bytes([x0]) as u64, + [x0, x1] => u16::from_le_bytes([x0, x1]) as u64, + [x0, x1, x2, x3] => u32::from_le_bytes([x0, x1, x2, x3]) as u64, + [x0, x1, x2, x3, x4, x5, x6, x7] => u64::from_le_bytes([x0, x1, x2, x3, x4, x5, x6, x7]), + _ => 0, + } +} + +impl icicle_vm::cpu::mem::WriteHook for UnicornHook { + fn write(&mut self, _: &mut icicle_vm::cpu::Mmu, addr: u64, value: &[u8]) { + let ty = uc_mem_type::UC_MEM_WRITE; + unsafe { + let func_ptr: MemCallback = std::mem::transmute(self.callback); + func_ptr(self.vtable, ty, addr, value.len() as u32, get_u64(value), self.userdata); + } + } +} + +impl icicle_vm::cpu::mem::ReadHook for UnicornHook { + fn read(&mut self, _: &mut icicle_vm::cpu::Mmu, addr: u64, size: u8) { + let ty = uc_mem_type::UC_MEM_READ; + unsafe { + let func_ptr: MemCallback = std::mem::transmute(self.callback); + func_ptr(self.vtable, ty, addr, size as u32, 0, self.userdata); + + if TRACE_MMIO_READS && size == 4 { + let ctx = &mut *{ &mut *self.vtable.as_mut().unwrap() }.ctx.cast::(); + let value = ctx.vm.cpu.mem.read_u32(addr, perm::NONE).unwrap(); + let pc = ctx.vm.cpu.read_pc(); + ctx.reads.push((addr, pc, value)); + } + } + } +} + +impl icicle_vm::cpu::mem::ReadAfterHook for UnicornHook { + fn read(&mut self, _: &mut icicle_vm::cpu::Mmu, addr: u64, value: &[u8]) { + let ty = uc_mem_type::UC_MEM_READ_AFTER; + unsafe { + let func_ptr: MemCallback = std::mem::transmute(self.callback); + func_ptr(self.vtable, ty, addr, value.len() as u32, get_u64(value), self.userdata); + } + } +} + +// uc_engine *uc, uint64_t addr, int size, void *user_data +type BlockCallback = unsafe extern "C" fn(*mut uc_engine, u64, u32, *mut c_void) -> u8; + +extern "sysv64" fn uc_call_translator(_: *mut icicle_vm::cpu::Cpu, addr: u64, userdata: *mut ()) { + unsafe { + let uc_hook = &mut *userdata.cast::(); + let func_ptr: BlockCallback = std::mem::transmute(uc_hook.callback); + func_ptr(uc_hook.vtable, addr, 0, uc_hook.userdata); + } +} + +impl icicle_vm::cpu::Hook for UnicornHook { + fn call(&mut self, _cpu: &mut icicle_vm::cpu::Cpu, addr: u64) { + unsafe { + let func_ptr: BlockCallback = std::mem::transmute(self.callback); + func_ptr(self.vtable, addr, 0, self.userdata); + } + } + + fn as_ptr( + &self, + ) -> Option<(extern "sysv64" fn(*mut icicle_vm::cpu::Cpu, u64, *mut ()), *mut ())> { + Some((uc_call_translator, (self as *const UnicornHook as *mut UnicornHook).cast())) + } + + fn as_any(&mut self) -> &mut dyn std::any::Any { + self + } +} + +pub(crate) struct FuzzwareEnvironment { + hooks: Vec<(u64, u64, uc_hook_type::Type, Box)>, + last_fault_addr: u64, + interrupt_ret_hook: Option, + syscall_hook: Option, + debug_info: DebugInfo, +} + +impl FuzzwareEnvironment { + pub fn new() -> Self { + Self { + hooks: vec![], + last_fault_addr: 0, + interrupt_ret_hook: None, + syscall_hook: None, + debug_info: DebugInfo::default(), + } + } + + pub fn set_debug_info(&mut self, path: &std::path::Path) -> Result<(), String> { + tracing::info!("Using debug info from: {}", path.display()); + let file = + std::fs::read(path).map_err(|e| format!("failed to read `{}`: {e}", path.display()))?; + self.debug_info.add_file(&file, 0)?; + Ok(()) + } + + pub fn add_mem_fault_hook( + &mut self, + start: u64, + end: u64, + kind: uc_hook_type::Type, + hook: Box, + ) -> Option { + let next_id: u32 = self.hooks.len().try_into().unwrap(); + self.hooks.push((start, end, kind, hook)); + Some(next_id) + } + + fn check_mem_fault_hooks( + &mut self, + cpu: &mut icicle_vm::cpu::Cpu, + addr: u64, + hook_kind: uc_hook_type::Type, + mem_kind: uc_mem_type::Type, + ) { + if addr == self.last_fault_addr { + // Double fault, prevent infinite loops. + return; + } + self.last_fault_addr = addr; + + for (start, end, kind, hook) in &mut self.hooks { + if start > end || (*start <= addr && addr < *end) { + if *kind & hook_kind == hook_kind { + if hook.handle_exception(addr, mem_kind) { + cpu.exception.clear(); + } + } + } + } + } + + fn handle_nvic_exception(&mut self, cpu: &mut icicle_vm::cpu::Cpu) { + if let Some(hook) = self.interrupt_ret_hook.as_mut() { + // Prevent the CPU from resuming the faulting instruction. + cpu.exception.clear(); + cpu.block_id = u64::MAX; + cpu.block_offset = 0; + + // Trigger the hook. + hook.call(cpu, cpu.read_pc()); + } + } +} + +impl icicle_vm::cpu::Environment for FuzzwareEnvironment { + fn load(&mut self, _: &mut icicle_vm::cpu::Cpu, _: &[u8]) -> Result<(), String> { + Err("Fuzzware environment should not directly load the binary".into()) + } + + fn handle_exception(&mut self, cpu: &mut icicle_vm::cpu::Cpu) -> Option { + match ExceptionCode::from_u32(cpu.exception.code) { + ExceptionCode::ReadPerm | ExceptionCode::ReadUninitialized => { + self.check_mem_fault_hooks( + cpu, + cpu.exception.value, + uc_hook_type::UC_HOOK_MEM_READ_PROT, + uc_mem_type::UC_MEM_READ_PROT, + ); + } + ExceptionCode::WritePerm => { + self.check_mem_fault_hooks( + cpu, + cpu.exception.value, + uc_hook_type::UC_HOOK_MEM_WRITE_PROT, + uc_mem_type::UC_MEM_WRITE_PROT, + ); + } + ExceptionCode::Syscall => { + if let Some(hook) = self.syscall_hook.as_mut() { + unsafe { + let func_ptr: SyscallHook = std::mem::transmute(hook.callback); + func_ptr(hook.vtable, crate::arm::EXCP_SWI, hook.userdata); + } + } + } + ExceptionCode::ShadowStackInvalid => { + if cpu.exception.value & NVIC_EXCEPT_MAGIC_RET_MASK == NVIC_EXCEPT_MAGIC_RET_MASK { + self.handle_nvic_exception(cpu); + } + } + ExceptionCode::InvalidInstruction => { + let pc = cpu.read_pc(); + if pc & NVIC_EXCEPT_MAGIC_RET_MASK == NVIC_EXCEPT_MAGIC_RET_MASK { + cpu.pop_shadow_stack(pc); + self.handle_nvic_exception(cpu); + } + } + _ => {} + } + + None + } + + fn next_timer(&self) -> u64 { + u64::MAX + } + + fn snapshot(&mut self) -> Box { + Box::new(()) + } + + fn restore(&mut self, _snapshot: &Box) {} + + fn as_any(&mut self) -> &mut dyn std::any::Any { + self + } + + fn symbolize_addr( + &mut self, + _cpu: &mut icicle_vm::cpu::Cpu, + addr: u64, + ) -> Option { + self.debug_info.symbolize_addr(addr) + } + + fn lookup_symbol(&mut self, symbol: &str) -> Option { + // Note: we clear the thumb bit because the SLEIGH spec normalizes jumps to thumb addresses. + self.debug_info.symbols.resolve_sym(symbol).map(|x| x & !0b1) + } +} + +pub unsafe extern "C" fn emu_start( + ctx: *mut c_void, + begin: u64, + until: u64, + timeout: u64, + count: u64, +) -> uc_err { + debug!("icicle_unicorn_api::emu_start({begin:#x}, {until:#x}, {timeout:#x}, {count:#x})"); + let ctx = &mut *ctx.cast::(); + ctx.vm.cpu.mem.tlb.clear(); + + if TRACE_MMIO_READS { + ctx.reads.clear(); + } + + if ctx.print_exit_info || TRACE_PATHS { + let path_tracer = ctx.vm.cpu.get_hook_mut(ctx.path_tracer_hook); + path_tracer.as_any().downcast_mut::().unwrap().blocks.clear(); + } + + if ctx.kill_on_next_run { + panic!("prior crash detected"); + } + ctx.exec_count += 1; + + ctx.vm.cpu.write_pc(begin & !0b1); + ctx.vm.cpu.set_isa_mode(1); + ctx.vm.cpu.exception.clear(); + + if count != 0 { + ctx.vm.icount_limit = count; + } + if let Some(limit) = std::env::var("ICOUNT_LIMIT").ok() { + ctx.vm.icount_limit = limit.parse().unwrap(); + } + + let exit = ctx.vm.run(); + maybe_save_disasm(ctx); + if std::mem::take(&mut ctx.stop_requested) { + if ctx.print_exit_info { + save_exit_info(ctx, exit); + } + // Exit was requested by the fuzzer, so ignore the exit reason generated by the emulator. + return UC_ERR_OK; + } + + save_exit_info(ctx, exit); + + // ctx.kill_on_next_run = true; + UC_ERR_EXCEPTION +} + +fn maybe_save_disasm(ctx: &mut Context) { + if ctx.last_block_count >= ctx.vm.code.blocks.len() || !SAVE_DISASM { + return; + } + + if ctx.last_block_count == 0 { + let _ = std::fs::remove_dir_all("./disasm"); + let _ = std::fs::create_dir("./disasm"); + } + + ctx.last_block_count = ctx.vm.code.blocks.len(); + std::fs::write( + format!("disasm/{:05}.asm", ctx.last_block_count), + icicle_vm::debug::dump_disasm(&ctx.vm).unwrap().as_bytes(), + ) + .unwrap(); + save_trace(ctx, format!("disasm/{:05}.trace.txt", ctx.last_block_count).as_ref(), false); +} + +pub unsafe extern "C" fn save_info(ctx: *mut c_void) -> uc_err { + let ctx = &mut *ctx.cast::(); + save_exit_info(ctx, VmExit::Running); + UC_ERR_OK +} + +fn save_exit_info(ctx: &mut Context, exit: VmExit) { + let pc = ctx.vm.cpu.read_pc(); + if !ctx.crashing_addresses.insert(pc) && !ctx.print_exit_info { + return; + } + + eprintln!( + "[{pc:#x}] VM exit: {exit:?}\n\ncallstack:\n{}\nregisters:\n{}\n", + icicle_vm::debug::backtrace(&mut ctx.vm), + icicle_vm::debug::print_regs(&ctx.vm, &icicle_vm::debug::get_debug_regs(&ctx.vm.cpu)), + ); + + if ctx.print_exit_info || TRACE_PATHS { + save_trace(ctx, "full_trace.txt".as_ref(), true); + } + std::fs::write("crash_disasm.asm", icicle_vm::debug::dump_disasm(&ctx.vm).unwrap().as_bytes()) + .unwrap(); + + if TRACE_MMIO_READS { + let mut output = std::io::BufWriter::new(std::fs::File::create("mmio_reads.txt").unwrap()); + for (addr, pc, value) in &ctx.reads { + writeln!(output, "{addr:#x},{pc:#0x},{value:#x}").unwrap(); + } + } + + std::fs::write("tlb.txt", format!("{:#x?}", ctx.vm.cpu.mem.tlb)).unwrap(); +} + +fn save_trace(ctx: &mut Context, path: &std::path::Path, debug: bool) { + let mut output = std::io::BufWriter::new(std::fs::File::create(path).unwrap()); + let path_tracer = ctx.vm.cpu.get_hook_mut(ctx.path_tracer_hook); + let blocks = path_tracer.as_any().downcast_ref::().unwrap().blocks.clone(); + for (addr, icount, fuzz_offset) in &blocks { + writeln!(output, "{addr:#x},{icount},{fuzz_offset}").unwrap(); + } + + if debug { + // Also print the final blocks to stderr + let max_blocks_to_print = + std::env::var("PRINT_FINAL_BLOCKS").ok().and_then(|x| x.parse().ok()).unwrap_or(10); + eprintln!("Final blocks:"); + for (addr, icount, fuzz_offset) in blocks.iter().rev().take(max_blocks_to_print) { + let location = ctx + .vm + .env + .symbolize_addr(&mut ctx.vm.cpu, *addr) + .unwrap_or(SourceLocation::default()); + eprintln!("{addr:#x}: {location} (icount = {icount}, input_offset = {fuzz_offset})"); + } + eprintln!(""); + } +} + +pub unsafe extern "C" fn emu_stop(ctx: *mut c_void) -> uc_err { + debug!("icicle_unicorn_api::emu_stop"); + let ctx = &mut *ctx.cast::(); + ctx.vm.cpu.exception.code = ExceptionCode::Environment as u32; + ctx.vm.cpu.exception.value = 1; + + // We might end up with an "uninitalized read" if this stop was requested from a `read_hook`, so + // we set a flag to ensure we can check for this case when the emulator is stopping. + ctx.stop_requested = true; + UC_ERR_OK +} + +pub unsafe extern "C" fn reg_read(ctx: *mut c_void, regid: c_int, value: *mut c_void) -> uc_err { + // debug!("icicle_unicorn_api::reg_read({regid})"); + let ctx = &mut *ctx.cast::(); + match regid as u32 { + uc_arm_reg::UC_ARM_REG_XPSR => *value.cast::() = crate::arm::read_xpsr(ctx), + _ => { + let data = match ctx.get_reg_slice(regid) { + Some(slice) => slice, + None => panic!("read to invalid register: {regid}"), + }; + std::ptr::copy_nonoverlapping(data.as_ptr(), value.cast(), data.len()); + } + }; + UC_ERR_OK +} + +pub unsafe extern "C" fn reg_read_batch( + ctx: *mut c_void, + regs: *mut c_int, + vals: *mut *mut c_void, + count: c_int, +) -> uc_err { + let regs = std::slice::from_raw_parts(regs, count as usize); + let vals = std::slice::from_raw_parts_mut(vals, count as usize); + + debug!("icicle_unicorn_api::reg_read_batch({regs:?})"); + + for (regid, val) in regs.iter().zip(vals) { + let result = reg_read(ctx, *regid, *val); + if result != UC_ERR_OK { + return result; + } + } + + UC_ERR_OK +} + +pub unsafe extern "C" fn reg_write(ctx: *mut c_void, regid: c_int, value: *const c_void) -> uc_err { + debug!("icicle_unicorn_api::reg_write({regid})"); + let ctx = &mut *ctx.cast::(); + + match regid as u32 { + uc_arm_reg::UC_ARM_REG_PC => { + let new_pc = (*value.cast::() & !0b1) as u64; + ctx.vm.cpu.set_isa_mode(1); + ctx.vm.cpu.exception.code = ExceptionCode::ExternalAddr as u32; + ctx.vm.cpu.exception.value = new_pc; + ctx.vm.cpu.write_pc(new_pc); + } + uc_arm_reg::UC_ARM_REG_XPSR => crate::arm::write_xpsr(ctx, *value.cast::()), + _ => { + let data = match ctx.get_reg_slice(regid) { + Some(slice) => slice, + None => panic!("read to invalid register: {regid}"), + }; + std::ptr::copy_nonoverlapping(value.cast(), data.as_mut_ptr(), data.len()); + } + } + + UC_ERR_OK +} + +pub unsafe extern "C" fn reg_write_batch( + ctx: *mut c_void, + regs: *mut c_int, + vals: *const *mut c_void, + count: c_int, +) -> uc_err { + let regs = std::slice::from_raw_parts(regs, count as usize); + let vals = std::slice::from_raw_parts(vals, count as usize); + debug!("icicle_unicorn_api::reg_write_batch({regs:?})"); + + for (regid, val) in regs.iter().zip(vals) { + let result = reg_write(ctx, *regid, *val); + if result != UC_ERR_OK { + return result; + } + } + + UC_ERR_OK +} + +pub unsafe extern "C" fn reg_ptr( + ctx: *mut c_void, + regid: c_int, + value: *mut *mut c_void, +) -> uc_err { + debug!("icicle_unicorn_api::reg_ptr({regid})"); + let ctx = &mut *ctx.cast::(); + let data = match ctx.get_reg_slice(regid) { + Some(slice) => slice, + None => return UC_ERR_ARG, + }; + *value = data.as_mut_ptr().cast(); + UC_ERR_OK +} + +pub unsafe extern "C" fn mem_read( + ctx: *mut c_void, + address: u64, + buf: *mut c_void, + count: usize, +) -> uc_err { + // debug!("icicle_unicorn_api::mem_read"); + let ctx = &mut *ctx.cast::(); + match count { + 1 => match ctx.vm.cpu.mem.read_u8(address, perm::NONE) { + Ok(x) => *buf.cast() = x, + Err(e) => return read_err_to_uc_err(e), + }, + 2 => match ctx.vm.cpu.mem.read_u16(address, perm::NONE) { + Ok(x) => *buf.cast() = x, + Err(e) => return read_err_to_uc_err(e), + }, + 4 => match ctx.vm.cpu.mem.read_u32(address, perm::NONE) { + Ok(x) => *buf.cast() = x, + Err(e) => return read_err_to_uc_err(e), + }, + 8 => match ctx.vm.cpu.mem.read_u64(address, perm::NONE) { + Ok(x) => *buf.cast() = x, + Err(e) => return read_err_to_uc_err(e), + }, + _ => { + let buf = std::slice::from_raw_parts_mut(buf.cast::(), count); + if let Err(e) = ctx.vm.cpu.mem.read_bytes(address, buf, perm::NONE) { + return read_err_to_uc_err(e); + } + } + } + UC_ERR_OK +} + +pub unsafe extern "C" fn mem_write( + ctx: *mut c_void, + address: u64, + buf: *const c_void, + count: usize, +) -> uc_err { + debug!("icicle_unicorn_api::mem_write({address:#0x})"); + let ctx = &mut *ctx.cast::(); + let result = match count { + 1 => ctx.vm.cpu.mem.write_u8(address, *buf.cast(), perm::NONE), + 2 => ctx.vm.cpu.mem.write_u16(address, *buf.cast(), perm::NONE), + 4 => ctx.vm.cpu.mem.write_u32(address, *buf.cast(), perm::NONE), + 8 => ctx.vm.cpu.mem.write_u64(address, *buf.cast(), perm::NONE), + _ => { + let buf = std::slice::from_raw_parts(buf.cast::(), count); + ctx.vm.cpu.mem.write_bytes(address, buf, perm::NONE) + } + }; + // Fuzzware seems to not check errors, so just panic here instead. + result.expect("mem_write failed"); + UC_ERR_OK +} + +pub unsafe extern "C" fn mem_set(ctx: *mut c_void, address: u64, value: u8, size: usize) -> uc_err { + debug!("icicle_unicorn_api::mem_set({address:#0x}, {value:#0x}, {size:#0x}"); + let ctx = &mut *ctx.cast::(); + + // Fuzzware seems to not check errors, so just panic here instead. + ctx.vm.cpu.mem.fill_mem(address, size as u64, value).expect("mem_set failed"); + UC_ERR_OK +} + +pub unsafe extern "C" fn mem_protect( + ctx: *mut c_void, + address: u64, + size: usize, + perms: u32, +) -> uc_err { + let ctx = &mut *ctx.cast::(); + let perms = uc_perms_to_icicle_perms(perms); + debug!("icicle_unicorn_api::mem_protect({address:#x}, {size:#x}, {})", perm::display(perms)); + if let Err(e) = ctx.vm.cpu.mem.update_perm(address, size as u64, perms) { + return read_err_to_uc_err(e); + } + UC_ERR_OK +} + +pub unsafe extern "C" fn mem_regions( + ctx: *mut c_void, + regions: *mut *mut uc_mem_region, + count: *mut u32, +) -> uc_err { + debug!("icicle_unicorn_api::mem_regions"); + let ctx = &mut *ctx.cast::(); + + let mut out = vec![]; + for (start, end, kind) in ctx.vm.cpu.mem.mapping.iter() { + debug!("start={start:#0x}, end={end:#x}, kind={kind:?}"); + match kind { + icicle_vm::cpu::mem::MemoryMapping::Physical(page) => { + let icicle_perm = ctx.vm.cpu.mem.get_physical(page.index).data().perm + [(start - page.addr) as usize]; + out.push(uc_mem_region { + begin: start, + end: end - 1, // Unicorn API expects inclusive ranges + perms: icicle_perms_to_uc_perms(icicle_perm), + }); + } + icicle_vm::cpu::mem::MemoryMapping::Unallocated(region) => { + out.push(uc_mem_region { + begin: start, + end: end - 1, + perms: icicle_perms_to_uc_perms(region.perm), + }); + } + _ => {} + } + } + + *count = out.len() as u32; + *regions = Vec::leak(out).as_mut_ptr(); + + UC_ERR_OK +} + +pub unsafe extern "C" fn block_hook_add( + ctx: *mut c_void, + hh: *mut uc_hook, + kind: c_int, + callback: *mut c_void, + userdata: *mut c_void, + begin: u64, + end: u64, +) -> uc_err { + debug!( + "icicle_unicorn_api::block_hook_add(kind={kind}, callback={callback:p}, \ + userdata={userdata:p}, begin={begin:#x}, end={end:#x})" + ); + let ctx = &mut *ctx.cast::(); + + let (begin, end) = if begin > end { (0, u64::MAX) } else { (begin, end) }; + let vtable = (*ctx).vtable.as_mut().unwrap().as_mut() as *mut uc_engine; + let hook = UnicornHook { vtable, callback, userdata }; + + if begin == NVIC_EXCEPT_MAGIC_RET_MASK { + let env = ctx.vm.env.as_any().downcast_mut::().unwrap(); + env.interrupt_ret_hook = Some(hook); + *hh = ctx.register_hook(HookKind::InterruptRetHook); + } + else { + let hook_id = ctx.vm.cpu.add_hook(Box::new(hook)); + let injector_id = + icicle_vm::injector::register_block_hook_injector(&mut ctx.vm, begin, end, hook_id); + *hh = ctx.register_hook(HookKind::Block { injector_id }); + } + + UC_ERR_OK +} + +pub unsafe extern "C" fn mem_hook_add( + ctx: *mut c_void, + hh: *mut uc_hook, + kind: c_int, + callback: *mut c_void, + userdata: *mut c_void, + begin: u64, + end: u64, +) -> uc_err { + let kind = kind as uc_hook_type::Type; + debug!( + "icicle_unicorn_api::mem_hook_add(kind={kind}, callback={callback:p}, \ + userdata={userdata:p}, begin={begin:#x}, end={end:#x})" + ); + let ctx = ctx.cast::(); + + let vtable = (*ctx).vtable.as_mut().unwrap().as_mut() as *mut uc_engine; + let hook = UnicornHook { vtable, callback, userdata }; + + *hh = match (*ctx).add_mem_hook(kind, begin, end, hook) { + Some(id) => id, + None => return UC_ERR_HOOK, + }; + UC_ERR_OK +} + +pub unsafe extern "C" fn int_hook_add( + ctx: *mut c_void, + hh: *mut uc_hook, + _kind: c_int, + callback: *mut c_void, + userdata: *mut c_void, + _begin: u64, + _end: u64, +) -> uc_err { + debug!("icicle_unicorn_api::int_hook_add"); + + let ctx = &mut *ctx.cast::(); + let vtable = (*ctx).vtable.as_mut().unwrap().as_mut() as *mut uc_engine; + let hook = UnicornHook { vtable, callback, userdata }; + + let env = ctx.vm.env.as_any().downcast_mut::().unwrap(); + env.syscall_hook = Some(hook); + *hh = ctx.register_hook(HookKind::SyscallHook); + + UC_ERR_OK +} + +pub unsafe extern "C" fn hook_del(ctx: *mut c_void, hh: uc_hook) -> uc_err { + debug!("icicle_unicorn_api::hook_del({hh})"); + let ctx = &mut *ctx.cast::(); + let hook = match ctx.hooks.remove(&hh) { + Some(hook) => hook, + None => return UC_ERR_HANDLE, + }; + + match hook { + HookKind::Mem { read, read_after, write, fault } => { + if let Some(_fault) = fault { + let _env = ctx.vm.env.as_any().downcast_mut::().unwrap(); + // @todo: remove hook. + } + if let Some(_hook) = read { + // @todo! + } + if let Some(_hook) = read_after { + // @todo! + } + if let Some(_hook) = write { + // @todo! + } + } + HookKind::Block { injector_id: _ } => { + // @todo + } + HookKind::InterruptRetHook => { + let env = ctx.vm.env.as_any().downcast_mut::().unwrap(); + env.interrupt_ret_hook = None; + } + HookKind::SyscallHook => { + let env = ctx.vm.env.as_any().downcast_mut::().unwrap(); + env.syscall_hook = None; + } + } + UC_ERR_OK +} + +pub unsafe extern "C" fn context_alloc(ctx: *mut c_void, context: *mut *mut uc_context) -> uc_err { + debug!("icicle_unicorn_api::context_alloc"); + let ctx = &mut *ctx.cast::(); + let ptr = Box::leak(Box::new(ctx.vm.cpu.snapshot())); + *context = (ptr as *mut CpuSnapshot).cast::(); + UC_ERR_OK +} + +pub unsafe extern "C" fn context_save(ctx: *mut c_void, context: *mut uc_context) -> uc_err { + debug!("icicle_unicorn_api::context_save"); + let ctx = &mut *ctx.cast::(); + *context.cast::() = ctx.vm.cpu.snapshot(); + UC_ERR_OK +} + +pub unsafe extern "C" fn context_restore(ctx: *mut c_void, context: *mut uc_context) -> uc_err { + debug!("icicle_unicorn_api::context_restore"); + let ctx = &mut *ctx.cast::(); + ctx.vm.cpu.mem.tlb.clear(); + ctx.vm.cpu.restore(&*context.cast::()); + UC_ERR_OK +} + +pub unsafe extern "C" fn free(ctx: *mut c_void) -> uc_err { + debug!("icicle_unicorn_api::free"); + let _ctx = &mut *ctx.cast::(); + // @fixme we never free memory + UC_ERR_OK +} + +pub unsafe extern "C" fn fuzzer_init_cov( + ctx: *mut c_void, + bitmap: *mut c_void, + map_size: u32, +) -> uc_err { + debug!("icicle_unicorn_api::fuzzer_init_cov({bitmap:p},{map_size:#x})"); + let ctx = &mut *ctx.cast::(); + + let context_bits = match std::env::var("ICICLE_CONTEXT_BITS") { + Ok(bits) => bits.parse().unwrap(), + Err(_) => 0, + }; + + let store_ref = match std::env::var("ICICLE_BLOCK_COVERAGE_ONLY").ok() { + Some(_) => icicle_fuzzing::coverage::BlockCoverageBuilder::new() + .enable_context(context_bits != 0) + .finish(&mut ctx.vm, bitmap.cast(), map_size), + None => icicle_fuzzing::coverage::AFLHitCountsBuilder::new() + .with_context(context_bits) + .finish(&mut ctx.vm, bitmap.cast(), map_size), + }; + + if let Some(level) = std::env::var("AFL_COMPCOV_LEVEL").ok() { + let level = level.parse::().expect("Invalid value for AFL_COMPCOV_LEVEL"); + tracing::info!("AFL_COMPCOV_LEVEL = {level}"); + icicle_fuzzing::compcov::CompCovBuilder::new().level(level).finish(&mut ctx.vm, store_ref); + } + + ctx.coverage_bitmap = Some((bitmap.cast(), map_size)); + ctx.prev_pc_var = ctx.vm.cpu.arch.sleigh.get_reg("afl.prev_pc").map(|x| x.var); + ctx.context_var = ctx.vm.cpu.arch.sleigh.get_reg("afl.context").map(|x| x.var); + + UC_ERR_OK +} + +pub unsafe extern "C" fn fuzzer_reset_cov(ctx: *mut c_void, do_clear: c_int) -> uc_err { + debug!("icicle_unicorn_api::fuzzer_reset_cov({do_clear})"); + let ctx = &mut *ctx.cast::(); + let (ptr, size) = match ctx.coverage_bitmap { + Some(value) => value, + None => return UC_ERR_ARG, + }; + if do_clear != 0 { + std::slice::from_raw_parts_mut(ptr, size as usize).fill(0); + } + if let Some(prev_pc) = ctx.prev_pc_var { + ctx.vm.cpu.write_reg(prev_pc, 0_u16); + } + if let Some(context) = ctx.context_var { + ctx.vm.cpu.write_reg(context, 0_u16); + } + + UC_ERR_OK +} diff --git a/setup.sh b/setup.sh index 2318ff4..dcd35c1 100755 --- a/setup.sh +++ b/setup.sh @@ -12,6 +12,14 @@ echo "[*] Building afl and Unicorn" UNICORN_QEMU_FLAGS="--python=/usr/bin/python3" make -C afl clean all || exit 1 pushd unicorn; USERNAME=`whoami` ./build_unicorn.sh || { popd; exit 1; }; popd +echo "[*] Building afl" +make -C afl clean all || exit 1 + +echo "[*] Building Icicle" +pip3 install -U maturin +pushd icicle; maturin build --release || { popd; exit 1; }; popd +pip3 install --force-reinstall icicle/target/wheels/icicle-0.1.0*.whl + echo "[*] Building native harness module" make -C harness/fuzzware_harness/native clean all || exit 1 diff --git a/unicorn/build_unicorn.sh b/unicorn/build_unicorn.sh index cbcac3b..ad94d98 100755 --- a/unicorn/build_unicorn.sh +++ b/unicorn/build_unicorn.sh @@ -2,7 +2,7 @@ UC_DIR=fuzzware-unicorn echo "[*] Cleaning Unicorn / QEMU..." -pushd "$UC_DIR" && rm -f qemu/config-host.mak; make -C qemu distclean clean; make clean; popd +# pushd "$UC_DIR" && rm -f qemu/config-host.mak; make -C qemu distclean clean; make clean; popd echo "[*] Building Unicorn..."