Beispiel #1
0
 def test_report_result(self):
     port = random.randint(8000, 9000)
     cl = csclient.Client(port=port,
                          scheme='http',
                          version_file=self._test_version_file)
     wu = csclient.WorkUnit()
     wu.plugin = Testlib.TEST_PLUGIN_NAME
     cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234'
     try:
         fd, wu.test_file = tempfile.mkstemp()
         os.close(fd)
         with open(wu.test_file, 'wb') as fp:
             fp.write(os.urandom(32) * 1024)
         with Testlib.TestServer(port) as srv:
             srv.set_mode('report')
             response = cl.report_result(count=1,
                                         defect='BADf00D',
                                         failure='DEADBEEF',
                                         file_name=wu.test_file,
                                         log='',
                                         plugin=wu.plugin,
                                         name='test')
         self.assertTrue(response)
     finally:
         if os.path.isfile(wu.test_file):
             os.remove(wu.test_file)
    def request(self, plugin_name=None):
        """
        Makes a request to the CrashStash server for a work unit.

        plugin_name is used to request work for a specific plugin. If None
        is used work for any active plugin can be assigned.

        Returns a WorkUnit or None if an update for the client or a plugin was
        received instead of a WorkUnit. None is also returned if there is a
        problem communicating with the server. None typically means try again.
        """
        c = csclient.Client(addr=self._ip_addr,
                            cert=self._cert,
                            port=self._port,
                            scheme=self._scheme,
                            debug=True)
        log.info('The current client version is: %s', c.get_client_version())
        log.info('Contacting server with work request...')
        if plugin_name is not None:
            log.info('Plugin manually requested: %s...', plugin_name)
        wu = c.request(plugin=plugin_name)
        sc = c.get_status_code()
        if sc is not None and sc != 200:
            log.warn('Status code: %d', sc)
        if c.required_client_update():
            # a client update is available, unpack it and return None
            # so another work request is made.
            log.info('Looks like the client needs to be updated.')
            new_version, file_name = c.required_client_update()
            self._fake_client_update(new_version, file_name)
            return None
        if c.required_plugin_update():
            # a plugin update is available, unpack it and return None
            # so another work request is made.
            pl_name, pl_version, pl_file = c.required_plugin_update()
            log.info('Looks like there is an update for %s.', pl_name)
            self._fake_plugin_update(pl_name, pl_version, pl_file)
            return None
        if not type(wu) == type(csclient.WorkUnit()):
            log.warn('Something went wrong expected a WorkUnit.')
            log.warn('Has this client been approved?')
            # wait to avoid hammering the server
            time.sleep(5)
            return None
        log.info('Received a work unit')
        log.info('plugin: %s', wu.plugin)
        log.info('for %d seconds or %d iterations (0 == no limit)',
                 wu.duration, wu.iterations)
        log.info('use this test case: %s', wu.test_file)
        log.info('it should have this sha1 hash: %s', wu.test_hash)
        if wu.allow_fuzzing:
            log.info('it is %sfuzzable', '' if wu.allow_fuzzing else 'NOT ')
        os.remove(wu.test_file)
        return wu
Beispiel #3
0
 def test_report_work_unit(self):
     port = random.randint(8000, 9000)
     cl = csclient.Client(port=port,
                          scheme='http',
                          version_file=self._test_version_file)
     wu = csclient.WorkUnit()
     wu.plugin = Testlib.TEST_PLUGIN_NAME
     wu.duration = 1200
     wu.iterations = 4567
     cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234'
     with Testlib.TestServer(port) as srv:
         srv.set_mode('report')
         response = cl.report_work(wu)
     self.assertTrue(response)
Beispiel #4
0
    def test_report_work_unit_fuzz(self):
        for _ in range(10):  # increase this to 1000 for a longer test
            port = random.randint(6000, 9000)
            cl = csclient.Client(port=port,
                                 scheme='http',
                                 timeout=random.choice([0.001, 0.01])
                                 if Testlib.Fuzz.dice(20) else None,
                                 version_file=self._test_version_file)
            wu = csclient.WorkUnit()
            if Testlib.Fuzz.dice():
                wu.allow_fuzzing = Testlib.Fuzz.string(None)
            elif not Testlib.Fuzz.dice():
                wu.allow_fuzzing = random.choice(['true', ''])

            if Testlib.Fuzz.dice():
                wu.duration = Testlib.Fuzz.int()
            elif not Testlib.Fuzz.dice():
                wu.duration = 1200

            if Testlib.Fuzz.dice():
                wu.iterations = Testlib.Fuzz.int()
            elif not Testlib.Fuzz.dice():
                wu.iterations = 1200

            if Testlib.Fuzz.dice():
                wu.plugin = Testlib.Fuzz.string(None)
            elif not Testlib.Fuzz.dice():
                wu.plugin = Testlib.TEST_PLUGIN_NAME
                cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234'

            if Testlib.Fuzz.dice():
                wu.test_name = Testlib.Fuzz.string(None)
            elif not Testlib.Fuzz.dice():
                wu.test_name = 'test_name'

            if Testlib.Fuzz.dice(20):
                wu = None
            with Testlib.TestServer(port) as srv:
                srv.set_mode('report')
                response = cl.report_work(wu)
    def run(self):
        """
        This is the main loop. Operations managed here include:
            - communication with the server
            - requesting work
            - reporting work and results
            - client and plugin updates
            - preparation to perform work
            - post work cleanup
        """
        log.info('Ctrl+C to quit')
        try:
            base_dir = os.getcwd()
            done = False

            while not done:
                log.info('Current time: %s',
                         time.strftime('%Y/%m/%d %H:%M:%S'))

                if self._scheme != 'https':
                    log.warning('Not using HTTPS')
                elif self._cert is None:
                    log.warning('Server certificate NOT provided')
                    log.warning('Server verification will NOT be performed')

                conn = csclient.Client(addr=self._ip_addr,
                                       cert=self._cert,
                                       port=self._port,
                                       scheme=self._scheme,
                                       version_file=self._version_file)

                log.info('Client version: %s', conn.get_client_version())
                log.info('Contacting %s with work request...', self._ip_addr)
                if self._requested_pl is not None:
                    log.info('Plugin manually requested: %s',
                             self._requested_pl)
                w_unit = conn.request(plugin=self._requested_pl)
                sc = conn.get_status_code()

                if sc is not None and sc != 200:
                    log.warning('Status code: %d', sc)
                    log.warning('Has this client been approved?')
                    if self._test_mode:
                        return False
                    log.warning('Waiting %d seconds', self._retry_delay)
                    time.sleep(self._retry_delay)
                    continue

                if conn.required_client_update():
                    cl_version, update_file = conn.required_client_update()
                    log.info('Client requires update to version: %s',
                             cl_version)
                    self._unpack_archive(update_file)
                    conn.update_client_version(cl_version)
                    return True

                if conn.required_plugin_update():
                    pl_name = conn.required_plugin_update()[0]
                    log.info('%s will now be updated', pl_name)
                    conn.update_plugin(dest_path='projects')
                    continue

                if not type(w_unit) == type(csclient.WorkUnit()):
                    log.warning('Server transaction failed')
                    log.warning('Waiting %d seconds', self._retry_delay)
                    time.sleep(self._retry_delay)
                    continue

                log.info('Got work for %s', w_unit.plugin)
                working_dir = '%s_%s' % (time.strftime('%Y%m%d-%H-%M-%S'),
                                         w_unit.plugin)
                os.mkdir(working_dir)

                try:
                    self._perform_work(w_unit, working_dir)
                except KeyboardInterrupt:
                    done = True
                    log.info('User interrupted work')
                    log.info('Will exit following work report')
                log.info('Reporting work')

                while not conn.report_work(self._report_unit):
                    log.warning('Failed to report work waiting %d seconds',
                                self._retry_delay)
                    time.sleep(self._retry_delay)

                log.info('%d result(s) to report', len(self._results))
                f_ids = list(self._results.keys())

                while f_ids:
                    if conn.report_result(**self._results[f_ids[0]]):
                        log.info('Result reported')
                        f_ids.pop(0)
                    else:
                        log.warning(
                            'Failed to report result waiting %d seconds',
                            self._retry_delay)
                        time.sleep(self._retry_delay)

                log.info('Work report complete')
                os.chdir(base_dir)
                self._report_unit = None
                self._results = None
                alf.delete(w_unit.test_file)
                alf.delete(os.path.abspath(working_dir))
                self._do_deletes()

        except KeyboardInterrupt:
            log.info('User interrupted')
            done = True

        finally:
            pass  #TODO: report errors

        return not done  # indicate relaunch should be performed
    def _perform_work(self, w_unit, working_dir, print_delay=60):
        """Handle running plugin and work from server using ALF"""
        log.info('Working directory: %s', working_dir)
        log.info('Duration: %d', w_unit.duration)
        log.info('Iterations: %d', w_unit.iterations)
        log.info('Allow fuzzing: %s', w_unit.allow_fuzzing)
        log.info('Test name: %s', w_unit.test_name)
        log.info('Test file: %s', w_unit.test_file)
        log.info('Test hash: %s', w_unit.test_hash)
        iterno = 0
        plugin = None
        self._results = dict()
        start_time = time.time()

        try:
            plugin_cls = self._load_plugin(w_unit.plugin)
            if plugin_cls is None:
                return

            plugin = plugin_cls(w_unit.test_file)
            tc_ext = os.path.splitext(w_unit.test_name)[1]
            os.chdir(working_dir)
            print_time = time.time() + 10
            end_time = w_unit.duration + start_time if w_unit.duration > 0 else 0
            log.info(
                'Working until: %s',
                time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(end_time)))
            log.info('%-15s %-15s %s', 'Iterations', 'Rate', 'Results')

            while (end_time and time.time() < end_time) or (
                    w_unit.iterations and iterno < w_unit.iterations):
                iterno += 1
                mutation_fn = 'mutation_%08X%s' % (iterno, tc_ext)
                result = plugin.do_iteration(mutation_fn,
                                             1 if w_unit.allow_fuzzing else 0)
                if result is not None:
                    if not isinstance(result, alf.FuzzResult):
                        raise TypeError('Expecting FuzzResult, not %s' %
                                        type(result))
                    if result.classification != alf.debug.NOT_AN_EXCEPTION:
                        if not os.path.isfile(mutation_fn):
                            raise Exception(
                                'result reported before mutation written to disk'
                            )
                        f_id = '-'.join(
                            [str(result.classification), result.minor])
                        if f_id not in self._results:
                            self._results[f_id] = {
                                'classification': str(result.classification),
                                'count': 0,
                                'defect': str(result.major),
                                'failure': str(result.minor),
                                'file_name': os.path.abspath(mutation_fn),
                                'log': result.text,
                                'name': os.path.basename(w_unit.test_name),
                                'plugin': str(w_unit.plugin)
                            }
                            # temporarily log result to file system
                            # used for viewing/debugging result before it is reported
                            # also saves results if unhandled exception occurs
                            with open('mutation_%08X.log.json' % iterno,
                                      'w') as fp:
                                json.dump(self._results[f_id],
                                          fp,
                                          ensure_ascii=False,
                                          indent=2)
                        else:
                            # duplicate don't save file
                            alf.delete(mutation_fn)
                        self._results[f_id]['count'] += 1
                if result is None or result.classification == alf.debug.NOT_AN_EXCEPTION:
                    alf.delete(mutation_fn)
                self._do_deletes()

                if time.time() >= print_time:  # print to console
                    print_time = time.time() + print_delay
                    elapsed_time = time.time() - start_time
                    rate = 0.0 if elapsed_time <= 0 else (1.0 * iterno /
                                                          elapsed_time)
                    log.info('%-15d %-15.2f %d', iterno, rate,
                             len(self._results))
        finally:
            elapsed_time = time.time() - start_time
            self._report_unit = csclient.WorkUnit()
            self._report_unit.duration = int(elapsed_time)
            self._report_unit.iterations = iterno
            self._report_unit.plugin = w_unit.plugin
            log.info('Ran %d iterations and found %d results in %.2fs', iterno,
                     len(self._results), elapsed_time)
            if plugin is not None:
                plugin.cleanup()
                plugin.on_exit()
            self._do_deletes()
Beispiel #7
0
    def test_report_result_fuzz(self):
        for _ in range(10):  # increase this to 1000 for a longer test
            port = random.randint(6000, 9000)
            cl = csclient.Client(port=port,
                                 scheme='http',
                                 timeout=random.choice([0.001, 0.01])
                                 if Testlib.Fuzz.dice(20) else None,
                                 version_file=self._test_version_file)
            wu = csclient.WorkUnit()
            args = {}
            if Testlib.Fuzz.dice():
                args['classification'] = Testlib.Fuzz.string(None)
            else:
                args['classification'] = 'UNKNOWN'

            if Testlib.Fuzz.dice():
                args['count'] = Testlib.Fuzz.int()
            else:
                args['count'] = 1

            if Testlib.Fuzz.dice():
                args['defect'] = Testlib.Fuzz.string(None)
            else:
                args['defect'] = 'BADf00D'

            if Testlib.Fuzz.dice():
                args['failure'] = Testlib.Fuzz.string(None)
            else:
                args['failure'] = 'DEADBEEF'

            if Testlib.Fuzz.dice():
                args['log'] = Testlib.Fuzz.string(None)
            else:
                args['log'] = 'test'

            if Testlib.Fuzz.dice():
                wu.plugin = Testlib.Fuzz.string(None)
            else:
                wu.plugin = Testlib.TEST_PLUGIN_NAME
                cl._versions['plugin_versions'][wu.plugin] = 'test_pl_v-1234'

            if Testlib.Fuzz.dice():
                wu.test_name = Testlib.Fuzz.string(None)
            else:
                wu.test_name = 'test_name'

            try:
                fd, wu.test_file = tempfile.mkstemp()
                os.close(fd)
                with open(wu.test_file, 'wb') as fp:
                    fp.write(os.urandom(random.choice([0, 1, 32])))
                with Testlib.TestServer(port) as srv:
                    srv.set_mode('report')
                    cl.report_result(
                        count=args['count'],
                        defect=args['defect'],
                        failure=args['failure'],
                        file_name=wu.test_file if not Testlib.Fuzz.dice() else
                        Testlib.Fuzz.string(None),
                        log=args['log'],
                        plugin=wu.plugin,
                        name=wu.test_name)
            finally:
                if os.path.isfile(wu.test_file):
                    os.remove(wu.test_file)