Esempio n. 1
0
            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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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()
Esempio n. 5
0
    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'],
        }
Esempio n. 6
0
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)
Esempio n. 7
0
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
Esempio n. 8
0
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()
Esempio n. 9
0
 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
Esempio n. 10
0
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)
Esempio n. 11
0
 def __exit__(self, *exc):
     if self.proc and self.proc.poll() is None:
         Controller.kill_process_tree(self.proc.pid)
     return None
Esempio n. 12
0
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