예제 #1
0
    def setUp(self):
        self.tmpdir = tempfile.mkdtemp(prefix='test_zzuflog_')
        (fd1, f1) = tempfile.mkstemp(text=True, dir=self.tmpdir)
        os.close(fd1)
        self.infile = f1

        self.log = ZzufLog(self.infile)
예제 #2
0
    def setUp(self):
        (fd1, f1) = tempfile.mkstemp(text=True)
        os.close(fd1)
        self.infile = f1

        (fd2, f2) = tempfile.mkstemp(text=True)
        os.close(fd2)
        self.outfile = f2

        self.log = ZzufLog(self.infile, self.outfile)
    def setUp(self):
        self.tmpdir = tempfile.mkdtemp(prefix='test_zzuflog_')
        (fd1, f1) = tempfile.mkstemp(text=True, dir=self.tmpdir)
        os.close(fd1)
        self.infile = f1

        self.log = ZzufLog(self.infile)
예제 #4
0
파일: zzufrun.py 프로젝트: CERTCC/certfuzz
    def _postrun(self):
        if not self.saw_crash:
            logger.debug('No crash seen')
            return

        # we must have seen a crash
        # get the results
        zzuf_log = ZzufLog(self.zzuf_log_path)

        # dump zzuflog into our log
        logger.debug("ZzufLog:")
        from pprint import pformat
        for line in pformat(zzuf_log.__dict__).splitlines():
            logger.debug(line)

        # Don't generate cases for killed process or out-of-memory
        # In the default mode, zzuf will report a signal. In copy (and exit code) mode, zzuf will
        # report the exit code in its output log.  The exit code is 128 + the signal number.
        self.saw_crash = zzuf_log.crash_logged()
    def _postrun(self):
        if not self.saw_crash:
            logger.debug('No crash seen')
            return

        # we must have seen a crash
        # get the results
        zzuf_log = ZzufLog(self.zzuf_log_path)

        # dump zzuflog into our log
        logger.debug("ZzufLog:")
        from pprint import pformat
        for line in pformat(zzuf_log.__dict__).splitlines():
            logger.debug(line)

        # Don't generate cases for killed process or out-of-memory
        # In the default mode, zzuf will report a signal. In copy (and exit code) mode, zzuf will
        # report the exit code in its output log.  The exit code is 128 + the signal number.
        self.saw_crash = zzuf_log.crash_logged()
예제 #6
0
    def test_get_last_line(self):
        open(self.infile, 'w')
        self.assertEqual(self.log._get_last_line(), '')

        (fd, f) = tempfile.mkstemp(text=True)
        os.write(fd, "firstline\n")
        os.write(fd, "secondline\n")
        os.write(fd, "thirdline\n")
        os.close(fd)

        log = ZzufLog(f, self.outfile)
        # log.line gets the result of _get_last_line before the infile is wiped out
        self.assertEqual(log.line, 'thirdline')
        self.delete_file(f)
예제 #7
0
    def test_read_zzuf_log(self):
        (fd, f) = tempfile.mkstemp(text=True)
        line = "zzuf[s=%d,r=%s]: %s\n"
        os.write(fd, line % (10, "0.1-0.2", "foo"))
        os.write(fd, line % (85, "0.01-0.02", "bar"))
        os.close(fd)

        log = ZzufLog(f, self.outfile)

        self.assertEqual(log.seed, 85)
        self.assertEqual(log.range, "0.01-0.02")
        self.assertEqual(log.result, "bar")
        self.assertEqual(log.line, (line % (85, "0.01-0.02", "bar")).strip())

        # cleanup
        self.delete_file(f)
class Test(unittest.TestCase):
    def delete_file(self, f):
        if os.path.exists(f):
            os.remove(f)
        self.assertFalse(os.path.exists(f))

    def tearDown(self):
        shutil.rmtree(self.tmpdir)

    def setUp(self):
        self.tmpdir = tempfile.mkdtemp(prefix='test_zzuflog_')
        (fd1, f1) = tempfile.mkstemp(text=True, dir=self.tmpdir)
        os.close(fd1)
        self.infile = f1

        self.log = ZzufLog(self.infile)

    def test_get_last_line(self):
        open(self.infile, 'w')
        self.assertEqual(self.log._get_last_line(), '')

        (fd, f) = tempfile.mkstemp(text=True)
        os.write(fd, "firstline\n")
        os.write(fd, "secondline\n")
        os.write(fd, "thirdline\n")
        os.close(fd)

        log = ZzufLog(f)
        # log.line gets the result of _get_last_line before the infile is wiped out
        self.assertEqual(log.line, 'thirdline')
        self.delete_file(f)

    def test_set_exitcode(self):
        self.log.result = "blah"
        self.log._set_exitcode()
        self.assertEqual(self.log.exitcode, '')

        self.log.result = "exit 1701"
        self.log._set_exitcode()
        self.assertEqual(self.log.exitcode, 1701)

    def test_set_signal(self):
        self.log.result = "blah"
        self.log._set_signal()
        self.assertEqual(self.log.signal, '')

        self.log.result = "signal 17938"
        self.log._set_signal()
        self.assertEqual(self.log.signal, '17938')

    def test_parse_line(self):
        self.log.line = "blah"
        self.assertEqual(self.log._parse_line(), (False, False, ''))
        self.log.line = "zzuf[s=99,r=foo]: Welcome to Jurassic Park"
        self.assertEqual(self.log._parse_line(), (99, 'foo', 'Welcome to Jurassic Park'))

    def test_was_out_of_memory(self):
        # should be true
        self.log.result = "signal 15"
        self.assertTrue(self.log.was_out_of_memory)
        self.log.result = "exit 143"
        self.assertTrue(self.log.was_out_of_memory)

        # should be false
        self.log.result = "signal 8"
        self.assertFalse(self.log.was_out_of_memory)
        self.log.result = "exit 18"
        self.assertFalse(self.log.was_out_of_memory)

    def test_was_killed(self):
        # should be true
        self.log.result = "signal 9"
        self.assertTrue(self.log.was_killed)
        self.log.result = "exit 137"
        self.assertTrue(self.log.was_killed)

        # should be false
        self.log.result = "signal 8"
        self.assertFalse(self.log.was_killed)
        self.log.result = "exit 18"
        self.assertFalse(self.log.was_killed)

    def test_read_zzuf_log(self):
        (fd, f) = tempfile.mkstemp(text=True)
        line = "zzuf[s=%d,r=%s]: %s\n"
        os.write(fd, line % (10, "0.1-0.2", "foo"))
        os.write(fd, line % (85, "0.01-0.02", "bar"))
        os.close(fd)

        log = ZzufLog(f)

        self.assertEqual(log.seed, 85)
        self.assertEqual(log.range, "0.01-0.02")
        self.assertEqual(log.result, "bar")
        self.assertEqual(log.line, (line % (85, "0.01-0.02", "bar")).strip())

        # cleanup
        self.delete_file(f)

    def test_crash_logged(self):
        self.log.result = "a"
        self.log._set_exitcode()
        self.assertFalse(self.log.crash_logged())

        # _was_killed => true
        # should be false
        self.log.result = "signal 9"
        self.log._set_exitcode()
        self.assertFalse(self.log.crash_logged())

        # _was_out_of_memory => true
        # should be false
        self.log.result = "signal 15"
        self.log._set_exitcode()
        self.assertFalse(self.log.crash_logged())

        # should be false since infile is empty
        self.log.result = "a"
        self.log._set_exitcode()
        self.assertFalse(self.log.parsed)
        self.assertFalse(self.log.crash_logged())

        # should be true
        self.log.result = "a"
        self.log._set_exitcode()
        self.log.parsed = True  # have to fake it since infile is empty
        self.assertTrue(self.log.crash_logged())
예제 #9
0
class Test(unittest.TestCase):
    def delete_file(self, f):
        if os.path.exists(f):
            os.remove(f)
        self.assertFalse(os.path.exists(f))

    def tearDown(self):
        self.delete_file(self.infile)
        self.delete_file(self.outfile)

    def setUp(self):
        (fd1, f1) = tempfile.mkstemp(text=True)
        os.close(fd1)
        self.infile = f1

        (fd2, f2) = tempfile.mkstemp(text=True)
        os.close(fd2)
        self.outfile = f2

        self.log = ZzufLog(self.infile, self.outfile)

    def test_get_last_line(self):
        open(self.infile, 'w')
        self.assertEqual(self.log._get_last_line(), '')

        (fd, f) = tempfile.mkstemp(text=True)
        os.write(fd, "firstline\n")
        os.write(fd, "secondline\n")
        os.write(fd, "thirdline\n")
        os.close(fd)

        log = ZzufLog(f, self.outfile)
        # log.line gets the result of _get_last_line before the infile is wiped out
        self.assertEqual(log.line, 'thirdline')
        self.delete_file(f)

    def test_set_exitcode(self):
        self.log.result = "blah"
        self.log._set_exitcode()
        self.assertEqual(self.log.exitcode, '')

        self.log.result = "exit 1701"
        self.log._set_exitcode()
        self.assertEqual(self.log.exitcode, 1701)

    def test_set_signal(self):
        self.log.result = "blah"
        self.log._set_signal()
        self.assertEqual(self.log.signal, '')

        self.log.result = "signal 17938"
        self.log._set_signal()
        self.assertEqual(self.log.signal, '17938')

    def test_parse_line(self):
        self.log.line = "blah"
        self.assertEqual(self.log._parse_line(), (False, False, ''))
        self.log.line = "zzuf[s=99,r=foo]: Welcome to Jurassic Park"
        self.assertEqual(self.log._parse_line(), (99, 'foo', 'Welcome to Jurassic Park'))

    def test_was_out_of_memory(self):
        # should be true
        self.log.result = "signal 15"
        self.assertTrue(self.log._was_out_of_memory())
        self.log.result = "exit 143"
        self.assertTrue(self.log._was_out_of_memory())

        # should be false
        self.log.result = "signal 8"
        self.assertFalse(self.log._was_out_of_memory())
        self.log.result = "exit 18"
        self.assertFalse(self.log._was_out_of_memory())

    def test_was_killed(self):
        # should be true
        self.log.result = "signal 9"
        self.assertTrue(self.log._was_killed())
        self.log.result = "exit 137"
        self.assertTrue(self.log._was_killed())

        # should be false
        self.log.result = "signal 8"
        self.assertFalse(self.log._was_killed())
        self.log.result = "exit 18"
        self.assertFalse(self.log._was_killed())

    def test_read_zzuf_log(self):
        (fd, f) = tempfile.mkstemp(text=True)
        line = "zzuf[s=%d,r=%s]: %s\n"
        os.write(fd, line % (10, "0.1-0.2", "foo"))
        os.write(fd, line % (85, "0.01-0.02", "bar"))
        os.close(fd)

        log = ZzufLog(f, self.outfile)

        self.assertEqual(log.seed, 85)
        self.assertEqual(log.range, "0.01-0.02")
        self.assertEqual(log.result, "bar")
        self.assertEqual(log.line, (line % (85, "0.01-0.02", "bar")).strip())

        # cleanup
        self.delete_file(f)

    def test_crash_logged(self):
        self.log.result = "a"
        self.log._set_exitcode()
        self.assertFalse(self.log.crash_logged(False))

        # _was_killed => true
        # should be false
        self.log.result = "signal 9"
        self.log._set_exitcode()
        self.assertFalse(self.log.crash_logged(False))

        # _was_out_of_memory => true
        # should be false
        self.log.result = "signal 15"
        self.log._set_exitcode()
        self.assertFalse(self.log.crash_logged(False))

        # should be false since infile is empty
        self.log.result = "a"
        self.log._set_exitcode()
        self.assertFalse(self.log.parsed)
        self.assertFalse(self.log.crash_logged(False))

        # should be true
        self.log.result = "a"
        self.log._set_exitcode()
        self.log.parsed = True # have to fake it since infile is empty
        self.assertTrue(self.log.crash_logged(False))
예제 #10
0
파일: bff.py 프로젝트: wflk/certfuzz
def main():
    global START_SEED
    hashes = []

    # give up if we don't have a debugger
    debuggers.verify_supported_platform()

    setup_logging_to_console(logger, logging.INFO)
    logger.info("Welcome to BFF!")

    scriptpath = os.path.dirname(sys.argv[0])
    logger.info('Scriptpath is %s', scriptpath)

    # parse command line options
    logger.info('Parsing command line options')
    parser = OptionParser()
    parser.add_option('',
                      '--debug',
                      dest='debug',
                      help='Turn on debugging output',
                      action='store_true')
    parser.add_option('-c',
                      '--config',
                      dest='cfg',
                      help='Config file location')
    (options, args) = parser.parse_args()  #@UnusedVariable

    # Get the cfg file name
    if options.cfg:
        remote_cfg_file = options.cfg
    else:
        remote_cfg_file = get_config_file(scriptpath)

    # die unless the remote config is present
    assert os.path.exists(
        remote_cfg_file
    ), 'Cannot find remote config file: %s, Please create it or use --config option to specify a different location.' % remote_cfg_file

    # copy remote config to local:
    local_cfg_file = os.path.expanduser('~/bff.cfg')
    filetools.copy_file(remote_cfg_file, local_cfg_file)

    # Read the cfg file
    logger.info('Reading config from %s', local_cfg_file)
    cfg = cfg_helper.read_config_options(local_cfg_file)

    # set up local logging
    setup_logfile(cfg.local_dir,
                  log_basename='bff.log',
                  level=logging.DEBUG,
                  max_bytes=1e8,
                  backup_count=3)

    # set up remote logging
    setup_logfile(cfg.output_dir,
                  log_basename='bff.log',
                  level=logging.INFO,
                  max_bytes=1e7,
                  backup_count=5)

    try:
        check_for_script(cfg)
    except:
        logger.warning("Please configure BFF to fuzz a binary.  Exiting...")
        sys.exit()

    z.setup_dirs_and_files(local_cfg_file, cfg)

    # make sure we cache it for the next run
    #    cache_state(cfg.campaign_id, 'cfg', cfg, cfg.cached_config_file)

    sr = get_cached_state('seedrange', cfg.campaign_id,
                          cfg.cached_seedrange_file)
    if not sr:
        sr = SeedRange(cfg.start_seed, cfg.seed_interval, cfg.max_seed)

    # set START_SEED global for timestamping
    START_SEED = sr.s1

    start_process_killer(scriptpath, cfg)

    z.set_unbuffered_stdout()

    # set up the seedfile set so we can pick seedfiles for everything else...
    seedfile_set = get_cached_state('seedfile_set', cfg.campaign_id,
                                    cfg.cached_seedfile_set)
    if not seedfile_set:
        logger.info('Building seedfile set')
        sfs_logfile = os.path.join(cfg.seedfile_output_dir, 'seedfile_set.log')
        with SeedfileSet(
                campaign_id=cfg.campaign_id,
                originpath=cfg.seedfile_origin_dir,
                localpath=cfg.seedfile_local_dir,
                outputpath=cfg.seedfile_output_dir,
                logfile=sfs_logfile,
        ) as sfset:
            seedfile_set = sfset

    # set up the watchdog timeout within the VM and restart the daemon
    if cfg.watchdogtimeout:
        watchdog = WatchDog(cfg.watchdogfile, cfg.watchdogtimeout)
        touch_watchdog_file(cfg)
        watchdog.go()

    cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set,
                cfg.cached_seedfile_set)

    sf = seedfile_set.next_item()

    # Run the program once to cache it into memory
    z.cache_program_once(cfg, sf.path)

    # Give target time to die
    time.sleep(1)

    # flag to indicate whether this is a fresh script start up or not
    first_chunk = True

    # remember our parent process id so we can tell if it changes later
    _last_ppid = os.getppid()

    # campaign.go
    while sr.in_max_range():

        # wipe the tmp dir clean to try to avoid filling the VM disk
        TmpReaper().clean_tmp()

        sf = seedfile_set.next_item()

        r = sf.rangefinder.next_item()
        sr.set_s2()

        while sr.in_range():
            # interval.go
            logger.debug('Starting interval %d-%d', sr.s1, sr.s2)

            # Prevent watchdog from rebooting VM.  If /tmp/fuzzing exists and is stale, the machine will reboot
            touch_watchdog_file(cfg)

            # check parent process id
            _ppid_now = os.getppid()
            if not _ppid_now == _last_ppid:
                logger.warning('Parent process ID changed from %d to %d',
                               _last_ppid, _ppid_now)
                _last_ppid = _ppid_now

            # do the fuzz
            cmdline = cfg.get_command(sf.path)

            if first_chunk:
                # disable the --quiet option in zzuf
                # on the first chunk only
                quiet_flag = False
                first_chunk = False
            else:
                quiet_flag = True

            zzuf = Zzuf(
                cfg.local_dir,
                sr.s1,
                sr.s2,
                cmdline,
                sf.path,
                cfg.zzuf_log_file,
                cfg.copymode,
                r.min,
                r.max,
                cfg.progtimeout,
                quiet_flag,
            )
            saw_crash = zzuf.go()

            if not saw_crash:
                # we must have made it through this chunk without a crash
                # so go to next chunk
                try_count = sr.s1_s2_delta()
                sf.record_tries(tries=try_count)
                r.record_tries(tries=try_count)

                # emit a log entry
                crashcount = z.get_crashcount(cfg.crashers_dir)
                rate = get_rate(sr.s1)
                seed_str = "seeds=%d-%d" % (sr.s1, sr.s2)
                range_str = "range=%.6f-%.6f" % (r.min, r.max)
                rate_str = "Rate=(%.2f/s %.1f/m %.0f/h %.0f/d)" % (
                    rate, rate * 60, rate * 3600, rate * 86400)
                expected_density = seedfile_set.expected_crash_density
                xd_str = "expected=%.9f" % expected_density
                xr_str = 'expected_rate=%.6f uniq/day' % (expected_density *
                                                          rate * 86400)
                logger.info('Fuzzing %s %s %s %s %s %s crash_count=%d',
                            sf.path, seed_str, range_str, rate_str, xd_str,
                            xr_str, crashcount)

                # set s1 to s2 so that as soon as we continue we'll break out of the sr.in_range() loop
                sr.set_s1_to_s2()
                continue

            # we must have seen a crash

            # get the results
            zzuf_log = ZzufLog(cfg.zzuf_log_file,
                               cfg.zzuf_log_out(sf.output_dir))

            # Don't generate cases for killed process or out-of-memory
            # In the default mode, zzuf will report a signal. In copy (and exit code) mode, zzuf will
            # report the exit code in its output log.  The exit code is 128 + the signal number.
            crash_status = zzuf_log.crash_logged(cfg.copymode)
            sr.bookmark_s1()
            sr.s1 = zzuf_log.seed

            # record the fact that we've made it this far
            try_count = sr.s1_delta()
            sf.record_tries(tries=try_count)
            r.record_tries(tries=try_count)

            new_uniq_crash = False
            if crash_status:
                logger.info('Generating testcase for %s', zzuf_log.line)
                # a true crash
                zzuf_range = zzuf_log.range
                # create the temp dir for the results
                cfg.create_tmpdir()
                outfile = cfg.get_testcase_outfile(sf.path, sr.s1)
                logger.debug('Output file is %s', outfile)
                testcase = zzuf.generate_test_case(sf.path, sr.s1, zzuf_range,
                                                   outfile)

                # Do internal verification using GDB / Valgrind / Stderr
                fuzzedfile = file_handlers.basicfile.BasicFile(outfile)

                with BffCrash(cfg, sf, fuzzedfile, cfg.program,
                              cfg.debugger_timeout, cfg.killprocname,
                              cfg.backtracelevels, cfg.crashers_dir, sr.s1,
                              r) as c:
                    if c.is_crash:
                        new_uniq_crash = verify_crasher(
                            c, hashes, cfg, seedfile_set)

                    # record the zzuf log line for this crash
                    if not c.logger:
                        c.get_logger()
                    c.logger.debug("zzuflog: %s", zzuf_log.line)
                    c.logger.info('Command: %s', testcase.cmdline)

                cfg.clean_tmpdir()

            sr.increment_seed()

            # cache objects in case of reboot
            cache_state(cfg.campaign_id, 'seedrange', sr,
                        cfg.cached_seedrange_file)
            pickled_seedfile_file = os.path.join(cfg.cached_objects_dir,
                                                 sf.pkl_file())
            cache_state(cfg.campaign_id, sf.cache_key(), sf,
                        pickled_seedfile_file)
            cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set,
                        cfg.cached_seedfile_set)

            if new_uniq_crash:
                # we had a hit, so break the inner while() loop
                # so we can pick a new range. This is to avoid
                # having a crash-rich range run away with the
                # probability before other ranges have been tried
                break
예제 #11
0
def main():
    global START_SEED
    hashes = []

    # give up if we don't have a debugger
    debuggers.verify_supported_platform()

    setup_logging_to_console(logger, logging.INFO)
    logger.info("Welcome to BFF!")

    scriptpath = os.path.dirname(sys.argv[0])
    logger.info('Scriptpath is %s', scriptpath)

    # parse command line options
    logger.info('Parsing command line options')
    parser = OptionParser()
    parser.add_option('', '--debug', dest='debug', help='Turn on debugging output', action='store_true')
    parser.add_option('-c', '--config', dest='cfg', help='Config file location')
    (options, args) = parser.parse_args()  #@UnusedVariable

    # Get the cfg file name
    if options.cfg:
        remote_cfg_file = options.cfg
    else:
        remote_cfg_file = get_config_file(scriptpath)

    # die unless the remote config is present
    assert os.path.exists(remote_cfg_file), 'Cannot find remote config file: %s, Please create it or use --config option to specify a different location.' % remote_cfg_file

    # copy remote config to local:
    local_cfg_file = os.path.expanduser('~/bff.cfg')
    filetools.copy_file(remote_cfg_file, local_cfg_file)

    # Read the cfg file
    logger.info('Reading config from %s', local_cfg_file)
    cfg = cfg_helper.read_config_options(local_cfg_file)

    # set up local logging
    setup_logfile(cfg.local_dir, log_basename='bff.log', level=logging.DEBUG,
                  max_bytes=1e8, backup_count=3)

    # set up remote logging
    setup_logfile(cfg.output_dir, log_basename='bff.log', level=logging.INFO,
                  max_bytes=1e7, backup_count=5)

    try:
        check_for_script(cfg)
    except:
        logger.warning("Please configure BFF to fuzz a binary.  Exiting...")
        sys.exit()

    z.setup_dirs_and_files(local_cfg_file, cfg)

    # make sure we cache it for the next run
#    cache_state(cfg.campaign_id, 'cfg', cfg, cfg.cached_config_file)

    sr = get_cached_state('seedrange', cfg.campaign_id, cfg.cached_seedrange_file)
    if not sr:
        sr = SeedRange(cfg.start_seed, cfg.seed_interval, cfg.max_seed)

    # set START_SEED global for timestamping
    START_SEED = sr.s1

    start_process_killer(scriptpath, cfg)

    z.set_unbuffered_stdout()

    # set up the seedfile set so we can pick seedfiles for everything else...
    seedfile_set = get_cached_state('seedfile_set', cfg.campaign_id, cfg.cached_seedfile_set)
    if not seedfile_set:
        logger.info('Building seedfile set')
        sfs_logfile = os.path.join(cfg.seedfile_output_dir, 'seedfile_set.log')
        with SeedfileSet(campaign_id=cfg.campaign_id,
                         originpath=cfg.seedfile_origin_dir,
                         localpath=cfg.seedfile_local_dir,
                         outputpath=cfg.seedfile_output_dir,
                         logfile=sfs_logfile,
                         ) as sfset:
            seedfile_set = sfset

    # set up the watchdog timeout within the VM and restart the daemon
    if cfg.watchdogtimeout:
        watchdog = WatchDog(cfg.watchdogfile, cfg.watchdogtimeout)
        touch_watchdog_file(cfg)
        watchdog.go()

    cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set, cfg.cached_seedfile_set)

    sf = seedfile_set.next_item()

    # Run the program once to cache it into memory
    z.cache_program_once(cfg, sf.path)

    # Give target time to die
    time.sleep(1)

    # flag to indicate whether this is a fresh script start up or not
    first_chunk = True

    # remember our parent process id so we can tell if it changes later
    _last_ppid = os.getppid()

    # campaign.go
    while sr.in_max_range():

        # wipe the tmp dir clean to try to avoid filling the VM disk
        TmpReaper().clean_tmp()

        sf = seedfile_set.next_item()

        r = sf.rangefinder.next_item()
        sr.set_s2()

        while sr.in_range():
            # interval.go
            logger.debug('Starting interval %d-%d', sr.s1, sr.s2)

            # Prevent watchdog from rebooting VM.  If /tmp/fuzzing exists and is stale, the machine will reboot
            touch_watchdog_file(cfg)

            # check parent process id
            _ppid_now = os.getppid()
            if not _ppid_now == _last_ppid:
                logger.warning('Parent process ID changed from %d to %d', _last_ppid, _ppid_now)
                _last_ppid = _ppid_now

            # do the fuzz
            cmdline = cfg.get_command(sf.path)

            if first_chunk:
                # disable the --quiet option in zzuf
                # on the first chunk only
                quiet_flag = False
                first_chunk = False
            else:
                quiet_flag = True

            zzuf = Zzuf(cfg.local_dir,
                        sr.s1,
                        sr.s2,
                        cmdline,
                        sf.path,
                        cfg.zzuf_log_file,
                        cfg.copymode,
                        r.min,
                        r.max,
                        cfg.progtimeout,
                        quiet_flag,
                        )
            saw_crash = zzuf.go()

            if not saw_crash:
                # we must have made it through this chunk without a crash
                # so go to next chunk
                try_count = sr.s1_s2_delta()
                sf.record_tries(tries=try_count)
                r.record_tries(tries=try_count)

                # emit a log entry
                crashcount = z.get_crashcount(cfg.crashers_dir)
                rate = get_rate(sr.s1)
                seed_str = "seeds=%d-%d" % (sr.s1, sr.s2)
                range_str = "range=%.6f-%.6f" % (r.min, r.max)
                rate_str = "Rate=(%.2f/s %.1f/m %.0f/h %.0f/d)" % (rate, rate * 60, rate * 3600, rate * 86400)
                expected_density = seedfile_set.expected_crash_density
                xd_str = "expected=%.9f" % expected_density
                xr_str = 'expected_rate=%.6f uniq/day' % (expected_density * rate * 86400)
                logger.info('Fuzzing %s %s %s %s %s %s crash_count=%d',
                    sf.path, seed_str, range_str, rate_str, xd_str, xr_str, crashcount)

                # set s1 to s2 so that as soon as we continue we'll break out of the sr.in_range() loop
                sr.set_s1_to_s2()
                continue

            # we must have seen a crash

            # get the results
            zzuf_log = ZzufLog(cfg.zzuf_log_file, cfg.zzuf_log_out(sf.output_dir))

            # Don't generate cases for killed process or out-of-memory
            # In the default mode, zzuf will report a signal. In copy (and exit code) mode, zzuf will
            # report the exit code in its output log.  The exit code is 128 + the signal number.
            crash_status = zzuf_log.crash_logged(cfg.copymode)
            sr.bookmark_s1()
            sr.s1 = zzuf_log.seed

            # record the fact that we've made it this far
            try_count = sr.s1_delta()
            sf.record_tries(tries=try_count)
            r.record_tries(tries=try_count)

            new_uniq_crash = False
            if crash_status:
                logger.info('Generating testcase for %s', zzuf_log.line)
                # a true crash
                zzuf_range = zzuf_log.range
                # create the temp dir for the results
                cfg.create_tmpdir()
                outfile = cfg.get_testcase_outfile(sf.path, sr.s1)
                logger.debug('Output file is %s', outfile)
                testcase = zzuf.generate_test_case(sf.path, sr.s1, zzuf_range, outfile)

                # Do internal verification using GDB / Valgrind / Stderr
                fuzzedfile = file_handlers.basicfile.BasicFile(outfile)

                with BffCrash(cfg, sf, fuzzedfile, cfg.program, cfg.debugger_timeout,
                              cfg.killprocname, cfg.backtracelevels,
                              cfg.crashers_dir, sr.s1, r) as c:
                    if c.is_crash:
                        new_uniq_crash = verify_crasher(c, hashes, cfg, seedfile_set)

                    # record the zzuf log line for this crash
                    if not c.logger:
                        c.get_logger()
                    c.logger.debug("zzuflog: %s", zzuf_log.line)
                    c.logger.info('Command: %s', testcase.cmdline)

                cfg.clean_tmpdir()

            sr.increment_seed()

            # cache objects in case of reboot
            cache_state(cfg.campaign_id, 'seedrange', sr, cfg.cached_seedrange_file)
            pickled_seedfile_file = os.path.join(cfg.cached_objects_dir, sf.pkl_file())
            cache_state(cfg.campaign_id, sf.cache_key(), sf, pickled_seedfile_file)
            cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set, cfg.cached_seedfile_set)

            if new_uniq_crash:
                # we had a hit, so break the inner while() loop
                # so we can pick a new range. This is to avoid
                # having a crash-rich range run away with the
                # probability before other ranges have been tried
                break