def filter(*args, **kwargs): issue = fn(*args, **kwargs) if not issue: return None try: proc = subprocess.Popen( shlex.split(command, posix=sys.platform != 'win32'), cwd=cwd or os.getcwd(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=dict(os.environ, **json.loads(env or '{}'))) stdout, stderr = proc.communicate(timeout=timeout) if proc.returncode == 0: issue[property] = stdout else: logger.debug( 'SubprocessPropertyDecorator exited with non-zero exit code while setting the {property} property.\n' '{stdout}\n{stderr}'.format( property=property, stdout=stdout.decode('utf-8', errors='ignore'), stderr=stderr.decode('utf-8', errors='ignore'))) except subprocess.TimeoutExpired: logger.debug( 'Timeout expired in the SubprocessPropertyDecorator while setting the {property} property.' .format(property=property)) Controller.kill_process_tree(proc.pid) return issue
def execute(args=None, parser=None): parser = build_parser(parent=parser) parser.add_argument( '--max-cycles', metavar='N', default=None, type=int, help= 'limit number of fuzz job cycles to %(metavar)s (default: no limit)') arguments = parser.parse_args(args) logger = logging.getLogger('fuzzinator') logger.addHandler(RainbowLoggingHandler(sys.stdout)) logger.setLevel(arguments.log_level) config = configparser.ConfigParser( interpolation=configparser.ExtendedInterpolation()) config.read(arguments.config) controller = Controller(config=config) controller.listener = CliListener() try: controller.run(max_cycles=arguments.max_cycles) except KeyboardInterrupt: pass
def StdinSubprocessCall(command, cwd=None, env=None, test=None, timeout=None, **kwargs): """ Subprocess invocation-based call of a SUT that takes a test input on its stdin stream. **Mandatory parameter of the SUT call:** - ``command``: string to pass to the child shell as a command to run. **Optional parameters of the SUT call:** - ``cwd``: if not ``None``, change working directory before the command invocation. - ``env``: if not ``None``, a dictionary of variable names-values to update the environment with. - ``timeout``: run subprocess with timeout. **Result of the SUT call:** - If the child process exits with 0 exit code, no issue is returned. - Otherwise, an issue with ``'exit_code'``, ``'stdout'``, and ``'stderr'`` properties is returned. **Example configuration snippet:** .. code-block:: ini [sut.foo] call=fuzzinator.call.StdinSubprocessCall [sut.foo.call] command=./bin/foo - cwd=/home/alice/foo env={"BAR": "1"} """ env = dict(os.environ, **json.loads(env)) if env else None timeout = int(timeout) if timeout else None try: proc = subprocess.Popen(shlex.split(command, posix=sys.platform != 'win32'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd or os.getcwd(), env=env) stdout, stderr = proc.communicate(input=test, timeout=timeout) logger.debug('{stdout}\n{stderr}'.format(stdout=stdout.decode('utf-8', errors='ignore'), stderr=stderr.decode('utf-8', errors='ignore'))) if proc.returncode != 0: return { 'exit_code': proc.returncode, 'stdout': stdout, 'stderr': stderr, } except subprocess.TimeoutExpired: logger.debug('Timeout expired in the SUT\'s stdin subprocess runner.') Controller.kill_process_tree(proc.pid) return None
def execute(args=None, parser=None): parser = arg_parser.build_parser(parent=parser) parser.add_argument( '--force-encoding', default=None, choices=['utf-8', 'ascii'], help='force text encoding used for TUI widgets (instead of autodetect)' ) parser.add_argument( '--log-file', metavar='FILE', help='redirect stderr (instead of /dev/null; for debugging purposes)') parser.add_argument('-s', '--style', metavar='FILE', help='alternative style file for TUI') arguments = parser.parse_args(args) config = configparser.ConfigParser( interpolation=configparser.ExtendedInterpolation()) config.read(arguments.config) # Redirect or suppress errors to spare tui from superfluous messages. if arguments.log_file: sys.stdout = open(os.devnull, 'w') sys.stderr = open(arguments.log_file, 'w') else: sys.stdout = open(os.devnull, 'w') sys.stderr = open(os.devnull, 'w') if arguments.style: raw_style = json.load(arguments.style) else: raw_style = json.loads( pkgutil.get_data( __package__, os.path.join('resources', 'default_style.json')).decode(encoding='utf-8')) style = load_style(raw_style) if arguments.force_encoding: util.set_encoding(arguments.force_encoding) controller = Controller(config=config) tui = Tui(controller, style=style) controller.listener = TuiListener(tui.pipe, tui.events, tui.lock) fuzz_process = Process(target=controller.run, args=()) try: fuzz_process.start() tui.loop.run() finally: controller.kill_child_processes() fuzz_process.terminate() raise ExitMainLoop()
def wait_til_end(self): streams = {'stdout': b'', 'stderr': b''} select_fds = [stream.fileno() for stream in [self.proc.stderr, self.proc.stdout]] for fd in select_fds: fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) end_loop = False start_time = time.time() while not end_loop: try: time_left = self.timeout_per_test - (time.time() - start_time) if self.timeout_per_test else 0.5 if time_left <= 0: # Avoid waiting for the current test in the next iteration. Controller.kill_process_tree(self.proc.pid) break try: read_fds = select.select(select_fds, [], select_fds, time_left)[0] except select.error as e: if e.args[0] == errno.EINVAL: continue raise for stream, _ in streams.items(): if getattr(self.proc, stream).fileno() in read_fds: while True: stdout_chunk = getattr(self.proc, stream).read(512) if not stdout_chunk: break streams[stream] += stdout_chunk for end_pattern in self.end_texts: if end_pattern.encode('utf-8') in streams[stream]: end_loop = True break if self.proc.poll() is not None: break except IOError as e: logger.warning('[filter_streams] %s' % str(e)) logger.debug('{stdout}\n{stderr}'.format(stdout=streams['stdout'].decode('utf-8', errors='ignore'), stderr=streams['stderr'].decode('utf-8', errors='ignore'))) return { 'exit_code': self.proc.returncode, 'stderr': streams['stderr'], 'stdout': streams['stdout'], }
def SubprocessUpdate(command, cwd=None, env=None, timeout=None): """ Subprocess invocation-based SUT update. **Mandatory parameter of the SUT update:** - ``command``: string to pass to the child shell as a command to run. **Optional parameters of the SUT update:** - ``cwd``: if not ``None``, change working directory before the command invocation. - ``env``: if not ``None``, a dictionary of variable names-values to update the environment with. - ``timeout``: run subprocess with timeout. **Example configuration snippet:** .. code-block:: ini [sut.foo] update=fuzzinator.update.SubprocessUpdate #update_condition=... is needed to trigger the update [sut.foo.update] command=git pull && make cwd=/home/alice/foo env={"BAR": "1"} """ timeout = int(timeout) if timeout else None try: proc = subprocess.Popen(shlex.split(command, posix=sys.platform != 'win32'), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd or os.getcwd(), env=dict(os.environ, **json.loads(env or '{}'))) stdout, stderr = proc.communicate(timeout=timeout) if proc.returncode != 0: logger.warning(stderr) else: logger.info(stdout) except subprocess.TimeoutExpired: logger.debug('Timeout expired in the subprocess update.') Controller.kill_process_tree(proc.pid)
def execute(args=None, parser=None): parser = build_parser(parent=parser) arguments = parser.parse_args(args) logger = logging.getLogger('fuzzinator') logger.addHandler(RainbowLoggingHandler(sys.stdout)) logger.setLevel(arguments.log_level) config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation()) config.read(arguments.config) controller = Controller(config=config) controller.listener = CliListener() try: controller.run() except KeyboardInterrupt: pass
def execute(args=None, parser=None): parser = build_parser(parent=parser) parser.add_argument('--force-encoding', metavar='NAME', default=None, choices=['utf-8', 'ascii'], help='force text encoding used for TUI widgets (%(choices)s; default: autodetect)') parser.add_argument('-U', dest='force_encoding', action='store_const', const='utf-8', default=argparse.SUPPRESS, help='force UTF-8 encoding (alias for --force-encoding=%(const)s)') parser.add_argument('--log-file', metavar='FILE', help='redirect stderr (instead of /dev/null; for debugging purposes)') parser.add_argument('-s', '--style', metavar='FILE', help='alternative style file for TUI') arguments = parser.parse_args(args) error_msg = process_args(arguments) if error_msg: parser.error(error_msg) # Redirect or suppress errors to spare tui from superfluous messages. if arguments.log_file: sys.stdout = open(os.devnull, 'w') sys.stderr = open(arguments.log_file, 'w') else: sys.stdout = open(os.devnull, 'w') sys.stderr = open(os.devnull, 'w') if arguments.style: raw_style = json.load(arguments.style) else: raw_style = json.loads(pkgutil.get_data(__package__, os.path.join('resources', 'default_style.json')).decode(encoding='utf-8')) style = load_style(raw_style) if arguments.force_encoding: util.set_encoding(arguments.force_encoding) controller = Controller(config=arguments.config) tui = Tui(controller, style=style) controller.listener += TuiListener(tui.pipe, tui.events, tui.lock) fuzz_process = Process(target=controller.run, args=()) try: fuzz_process.start() tui.loop.run() except KeyboardInterrupt: # No need to handle CTRL+C as SIGINT is sent by the terminal to all # (sub)processes. pass except Exception: # Handle every kind of TUI exceptions except for KeyboardInterrupt. # SIGINT will trigger a KeyboardInterrupt exception in controller, # thus allowing it to perform proper cleanup. os.kill(fuzz_process.pid, signal.SIGINT) else: # Handle normal exit after 'Q' or F10. SIGINT will trigger a # KeyboardInterrupt exception in controller, thus allowing it to # perform proper cleanup. os.kill(fuzz_process.pid, signal.SIGINT) finally: raise ExitMainLoop()
def __enter__(self): os.makedirs(self.outdir, exist_ok=True) try: with open(os.devnull, 'w') as FNULL: proc = subprocess.Popen(shlex.split( self.command.format(uid=self.uid), posix=sys.platform != 'win32'), cwd=self.cwd, env=self.env, stdout=FNULL, stderr=FNULL) proc.communicate(timeout=self.timeout) except subprocess.TimeoutExpired: logger.debug('Timeout expired in the fuzzer\'s subprocess runner.') Controller.kill_process_tree(proc.pid) self.tests = [ os.path.join(self.outdir, test) for test in os.listdir(self.outdir) ] return self
def execute(args=None, parser=None): parser = build_parser(parent=parser) parser.add_argument( '--max-cycles', metavar='N', default=None, type=int, help= 'limit number of fuzz job cycles to %(metavar)s (default: no limit)') arguments = parser.parse_args(args) process_args(arguments) logger = logging.getLogger('fuzzinator') logger.addHandler(RainbowLoggingHandler(sys.stdout)) controller = Controller(config=arguments.config) controller.listener += CliListener() try: controller.run(max_cycles=arguments.max_cycles) except KeyboardInterrupt: Controller.kill_process_tree(os.getpid(), kill_root=False)
def __exit__(self, *exc): if self.proc and self.proc.poll() is None: Controller.kill_process_tree(self.proc.pid) return None
def SubprocessCall(command, cwd=None, env=None, no_exit_code=None, test=None, timeout=None, **kwargs): """ Subprocess invocation-based call of a SUT that takes test input on its command line. (See :class:`fuzzinator.call.FileWriterDecorator` for SUTs that take input from a file.) **Mandatory parameter of the SUT call:** - ``command``: string to pass to the child shell as a command to run (all occurrences of ``{test}`` in the string are replaced by the actual test input). **Optional parameters of the SUT call:** - ``cwd``: if not ``None``, change working directory before the command invocation. - ``env``: if not ``None``, a dictionary of variable names-values to update the environment with. - ``no_exit_code``: makes possible to force issue creation regardless of the exit code. - ``timeout``: run subprocess with timeout. **Result of the SUT call:** - If the child process exits with 0 exit code, no issue is returned. - Otherwise, an issue with ``'exit_code'``, ``'stdout'``, and ``'stderr'`` properties is returned. **Example configuration snippet:** .. code-block:: ini [sut.foo] call=fuzzinator.call.SubprocessCall [sut.foo.call] # assuming that {test} is something that can be interpreted by foo as # command line argument command=./bin/foo {test} cwd=/home/alice/foo env={"BAR": "1"} """ env = dict(os.environ, **json.loads(env)) if env else None no_exit_code = eval(no_exit_code) if no_exit_code else False timeout = int(timeout) if timeout else None try: proc = subprocess.Popen(shlex.split(command.format(test=test), posix=sys.platform != 'win32'), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd or os.getcwd(), env=env) stdout, stderr = proc.communicate(timeout=timeout) logger.debug('{stdout}\n{stderr}'.format( stdout=stdout.decode('utf-8', errors='ignore'), stderr=stderr.decode('utf-8', errors='ignore'))) if no_exit_code or proc.returncode != 0: return { 'exit_code': proc.returncode, 'stdout': stdout, 'stderr': stderr, } except subprocess.TimeoutExpired: logger.debug('Timeout expired in the SUT\'s subprocess runner.') Controller.kill_process_tree(proc.pid) return None
def __call__(self, test, **kwargs): if self.timeout: start_time = time.time() proc = subprocess.Popen(shlex.split(self.command.format(test=test), posix=sys.platform != 'win32'), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.cwd or os.getcwd(), env=self.env) streams = {'stdout': b'', 'stderr': b''} issue = None select_fds = [stream.fileno() for stream in [proc.stderr, proc.stdout]] for fd in select_fds: fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) end_loop = False while not end_loop: try: try: read_fds = select.select(select_fds, [], select_fds, 0.5)[0] except select.error as e: if e.args[0] == errno.EINVAL: continue raise for stream, _ in streams.items(): if getattr(proc, stream).fileno() in read_fds: while True: chunk = getattr(proc, stream).read(512) if not chunk: break streams[stream] += chunk for pattern in self.end_patterns: match = pattern.search(streams[stream]) if match is not None: end_loop = True issue = match.groupdict() if proc.poll() is not None or ( self.timeout and time.time() - start_time > self.timeout): break except IOError as e: logger.warning('[filter_streams] %s' % str(e)) Controller.kill_process_tree(proc.pid, sig=signal.SIGKILL) logger.debug('{stdout}\n{stderr}'.format( stdout=streams['stdout'].decode('utf-8', errors='ignore'), stderr=streams['stderr'].decode('utf-8', errors='ignore'))) if issue: issue.update( dict(exit_code=proc.returncode, stderr=streams['stderr'], stdout=streams['stdout'])) return issue