class AflRunner(object): """AFL fuzzer.""" __slots__ = [ "loop", "clock", "machine", "debug", "machine_config_patches", "machine_config_defaults", "switch_list", "use_virtual", "_invalid_input", "_exception" ] def __init__(self, use_virtual, debug): """Initialize fuzzer.""" self.loop = None self.clock = None self.machine = None # type: TestMachineController self.debug = debug self.machine_config_patches = dict() self.machine_config_patches['mpf'] = dict() self.machine_config_patches['mpf']['default_platform_hz'] = 1 self.machine_config_patches['bcp'] = [] self.machine_config_defaults = {} self.switch_list = [] self.use_virtual = use_virtual self._invalid_input = False self._exception = None def _exception_handler(self, loop, context): try: loop.stop() except RuntimeError: pass self._exception = context def get_platform(self): """Return platform.""" if self.use_virtual: return "virtual" return "smart_virtual" @staticmethod def get_config_file(): """Return config file.""" return "config.yaml" def get_options(self): """Return option arrays.""" mpfconfig = os.path.abspath( os.path.join(mpf.core.__path__[0], os.pardir, 'mpfconfig.yaml')) return { 'force_platform': self.get_platform(), 'mpfconfigfile': mpfconfig, 'configfile': Util.string_to_event_list(self.get_config_file()), 'debug': False, 'bcp': False, 'no_load_cache': False, 'create_config_cache': True, 'text_ui': False, 'production': not self.debug, } def advance_time_and_run(self, delta=1.0): """Advance time and run.""" try: self.loop.run_until_complete( asyncio.sleep(delay=delta, loop=self.loop)) return except RuntimeError as e: if self._exception and "exception" in self._exception: raise self._exception['exception'] elif self._exception: raise Exception(self._exception, e) raise e def setup(self, machine_path): """Set up fuzzer.""" self.loop = TimeTravelLoop() self.loop.set_exception_handler(self._exception_handler) self.clock = TestClock(self.loop) config_loader = UnitTestConfigLoader(machine_path, ["config.yaml"], self.machine_config_defaults, self.machine_config_patches, {}) config = config_loader.load_mpf_config() try: # remove virtual_platform_start_active_switches as it messes with out switch logic config.get_machine_config().pop( "virtual_platform_start_active_switches") except KeyError: pass self.machine = TestMachineController(self.get_options(), config, self.machine_config_patches, self.machine_config_defaults, self.clock, dict(), True) try: self.loop.run_until_complete(self.machine.initialise()) except RuntimeError as e: try: self.machine.stop() # pylint: disable-msg=broad-except except Exception: pass if self._exception and "exception" in self._exception: raise self._exception['exception'] elif self._exception: raise Exception(self._exception, e) raise e self.machine.events.process_event_queue() self.advance_time_and_run(1) self.switch_list = sorted(self.machine.switches.keys()) for switch in self.machine.switches: self.machine.switch_controller.process_switch_obj(switch, 0, True) def add_balls(self): """Add balls.""" balls_to_add = 3 for device in sorted(self.machine.ball_devices.values()): if "trough" in device.tags: for switch in device.ball_count_handler.counter.config.get( 'ball_switches', []): self.machine.switch_controller.process_switch_obj( switch, 1, True) balls_to_add -= 1 if balls_to_add == 0: break if balls_to_add == 0: break if device.ball_count_handler.counter.config.get( 'entrance_switch'): self.machine.switch_controller.process_switch_obj( device.ball_count_handler.counter. config['entrance_switch'], 1, True) balls_to_add -= 1 if balls_to_add == 0: break # let balls settle self.advance_time_and_run(10) def start_game(self): """Start game.""" found_start_switch = False for switch in self.machine.switches: if "start" in switch.tags: found_start_switch = True self.machine.switch_controller.process_switch_obj( switch, 1, True) self.machine.switch_controller.process_switch_obj( switch, 0, True) if not found_start_switch: raise AssertionError( "Did not find any switch tagged with tag \"start\".") def _abort(self, **kwargs): """Abort fuzzer run.""" del kwargs self._invalid_input = True if not self.debug: os._exit(0) # NOQA def run(self, actions, find_logic_bugs): """Run fuzzer.""" if find_logic_bugs: self.machine.events.add_handler("balldevice_ball_missing", self._abort) self.machine.events.add_handler("found_new_ball", self._abort) self.machine.events.add_handler("mode_game_stopped", self._abort) for action in actions: if self._invalid_input: # bail out if we hit an invalid input. afl will notice this return if action & 0b10000000: if action == 0b11111111: if self.machine.game and \ self.machine.game.balls_in_play < self.machine.ball_controller.num_balls_known: self.machine.playfield.add_ball() self.machine.game.balls_in_play += 1 else: self._abort() else: ms = int(action & 0b01111111) ms *= ms self.advance_time_and_run(ms / 1000.0) else: switch = int(action & 0b01111111) if switch >= len(self.switch_list): self._abort() return switch_obj = self.machine.switches[self.switch_list[switch]] state = switch_obj.hw_state ^ 1 # print(switch_list[switch], state, switch_obj.hw_state) self.machine.switch_controller.process_switch_by_num( switch_obj.hw_switch.number, state, self.machine.default_platform) if find_logic_bugs: self.advance_time_and_run(60) if self._invalid_input: # might happen late return balls = 0 for playfield in self.machine.playfields: balls += playfield.balls if balls != self.machine.game.balls_in_play: print("Balls in play:", self.machine.game.balls_in_play) print("Playfields:") for playfield in self.machine.playfields: print(playfield.name, playfield.balls) print("Devices:") for device in self.machine.ball_devices: print(device.name, device.balls, device.available_balls) raise AssertionError( "Balls in play do not match balls on playfields.") def dump(self, wait, add_balls, start_game, actions): """Dump fuzzer input.""" if add_balls: balls_to_add = 3 for device in sorted(self.machine.ball_devices.values()): if "trough" in device.tags: for switch in device.ball_count_handler.counter.config.get( 'ball_switches', []): print("Enable switch {}".format(switch.name)) switch.hw_state = 1 balls_to_add -= 1 if balls_to_add == 0: break if balls_to_add == 0: break if device.ball_count_handler.counter.config.get( 'entrance_switch'): print("Enable switch {}".format( device.config['entrance_switch'].name)) device.config['entrance_switch'].switch.hw_state = 1 balls_to_add -= 1 if balls_to_add == 0: break print("Advance time 10s") if start_game: for switch in self.machine.switches: if "start" in switch.tags: print("Enable/disable switch {}".format(switch.name)) if wait > 0: print("Advance time {}s".format(wait)) for action in actions: if action & 0b10000000: if action == 0b11111111: print("Add ball into play") else: ms = int(action & 0b01111111) ms *= ms print("Advance time by {} ms".format(ms)) else: switch = int(action & 0b01111111) if switch >= len(self.switch_list): print("Invalid switch. Abort!") return switch_obj = self.machine.switches[self.switch_list[switch]] switch_obj.hw_state = state = switch_obj.hw_state ^ 1 # print(switch_list[switch], state, switch_obj.hw_state) print("Toggle switch {}. New state: {}".format( switch_obj.name, state))
class AflRunner(object): """AFL fuzzer.""" def __init__(self, use_virtual): """Initialize fuzzer.""" self.loop = None self.clock = None self.machine = None # type: TestMachineController self.machine_config_patches = dict() self.machine_config_patches['mpf'] = dict() self.machine_config_patches['mpf']['default_platform_hz'] = 1 self.machine_config_patches['bcp'] = [] self.machine_config_defaults = {} self.switch_list = [] self.use_virtual = use_virtual self._invalid_input = False def _exception_handler(self, loop, context): try: loop.stop() except RuntimeError: pass self._exception = context def get_platform(self): """Return platform.""" if self.use_virtual: return "virtual" else: return "smart_virtual" def getConfigFile(self): # noqa """Return config file.""" return "config.yaml" def getAbsoluteMachinePath(self): # noqa """Return an absolute path based on machine_path.""" return "/home/kantert/cloud/flipper/src/good_vs_evil" def getOptions(self): # noqa """Return option arrays.""" mpfconfig = os.path.abspath( os.path.join(mpf.core.__path__[0], os.pardir, 'mpfconfig.yaml')) return { 'force_platform': self.get_platform(), 'mpfconfigfile': mpfconfig, 'configfile': Util.string_to_list(self.getConfigFile()), 'debug': True, 'bcp': False, 'no_load_cache': False, 'create_config_cache': True, 'text_ui': False, } def advance_time_and_run(self, delta=1.0): """Advance time and run.""" try: self.loop.run_until_complete( asyncio.sleep(delay=delta, loop=self.loop)) return except RuntimeError as e: if self._exception and "exception" in self._exception: raise self._exception['exception'] elif self._exception: raise Exception(self._exception, e) raise e def setUp(self, machine_path): """Set up fuzzer.""" self.loop = TimeTravelLoop() self.loop.set_exception_handler(self._exception_handler) self.clock = TestClock(self.loop) self.machine = TestMachineController( os.path.abspath(os.path.join(mpf.core.__path__[0], os.pardir)), machine_path, self.getOptions(), self.machine_config_patches, self.machine_config_defaults, self.clock, dict(), True) self.loop.run_until_complete(self.machine.initialise()) self.machine.events.process_event_queue() self.advance_time_and_run(1) self.switch_list = sorted(self.machine.switches.keys()) for switch in self.machine.switches: self.machine.switch_controller.process_switch_obj(switch, 0, True) def add_balls(self): """Add balls.""" for device in self.machine.ball_devices: if "trough" in device.tags: for switch in device.config['ball_switches']: self.machine.switch_controller.process_switch_obj( switch, 1, True) if device.config['entrance_switch']: self.machine.switch_controller.process_switch_obj( device.config['entrance_switch'], 1, True) # let balls settle self.advance_time_and_run(10) def start_game(self): """Start game.""" for switch in self.machine.switches: if "start" in switch.tags: self.machine.switch_controller.process_switch_obj( switch, 1, True) self.machine.switch_controller.process_switch_obj( switch, 0, True) def _abort(self, **kwargs): """Abort fuzzer run.""" del kwargs self._invalid_input = True def run(self, actions, find_logic_bugs): """Run fuzzer.""" if find_logic_bugs: self.machine.events.add_handler("balldevice_ball_missing", self._abort) self.machine.events.add_handler("found_new_ball", self._abort) self.machine.events.add_handler("mode_game_stopped", self._abort) for action in actions: if self._invalid_input: # bail out if we hit an invalid input. afl will notice this return if action & 0b10000000: ms = int(action & 0b01111111) ms *= ms self.advance_time_and_run(ms / 1000.0) else: switch = int(action & 0b01111111) if switch >= len(self.switch_list): continue switch_obj = self.machine.switches[self.switch_list[switch]] state = switch_obj.hw_state ^ 1 # print(switch_list[switch], state, switch_obj.hw_state) self.machine.switch_controller.process_switch_by_num( switch_obj.hw_switch.number, state, self.machine.default_platform) if find_logic_bugs: self.advance_time_and_run(60) if self._invalid_input: # might happen late return balls = 0 for playfield in self.machine.playfields: balls += playfield.balls if balls != self.machine.game.balls_in_play: print("Balls in play:", self.machine.game.balls_in_play) print("Playfields:") for playfield in self.machine.playfields: print(playfield.name, playfield.balls) print("Devices:") for device in self.machine.ball_devices: print(device.name, device.balls, device.available_balls) raise AssertionError( "Balls in play do not match balls on playfields.") def dump(self, wait, add_balls, start_game, actions): """Dump fuzzer input.""" if add_balls: for device in self.machine.ball_devices: if "trough" in device.tags: for switch in device.config['ball_switches']: print("Enable switch {}".format(switch.name)) if device.config['entrance_switch']: print("Enable switch {}".format(switch.name)) print("Advance time 10s") if start_game: for switch in self.machine.switches: if "start" in switch.tags: print("Enable/disable switch {}".format(switch.name)) if wait > 0: print("Advance time {}s".format(wait)) for action in actions: if action & 0b10000000: ms = int(action & 0b01111111) ms *= ms print("Advance time by {} ms".format(ms)) else: switch = int(action & 0b01111111) if switch >= len(self.switch_list): continue switch_obj = self.machine.switches[self.switch_list[switch]] state = switch_obj.hw_state ^ 1 # print(switch_list[switch], state, switch_obj.hw_state) print("Toggle switch {}. New state: {}".format( switch_obj.name, state)) self.machine.switch_controller.process_switch_by_num( switch_obj.hw_switch.number, state, self.machine.default_platform)