Skip to content

Commit f94468c

Browse files
committed
pruning centipede
Signed-off-by: Javan Lacerda <javanlacerda@google.com>
1 parent 218cd24 commit f94468c

File tree

5 files changed

+55
-139
lines changed

5 files changed

+55
-139
lines changed

src/clusterfuzz/_internal/bot/fuzzers/centipede/engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,8 +551,8 @@ def minimize_corpus(self,
551551
# Only remove this directory if it was created in this method.
552552
shutil.rmtree(full_corpus_workdir)
553553

554-
return engine.ReproduceResult(result.command, result.return_code,
555-
result.time_executed, result.output)
554+
return engine.FuzzResult(result.output, result.command, [], None,
555+
result.time_executed, result.timed_out)
556556

557557
def _get_smallest_crasher(self, workdir_path):
558558
"""Returns the path to the smallest crash in Centipede's |workdir_path|."""

src/clusterfuzz/_internal/bot/tasks/utasks/corpus_pruning_task.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ def minimize_corpus(self, arguments, input_dirs, output_dir, reproducers_dir,
392392
reproducers_dir, max_time)
393393

394394

395-
class GenericRunner(BaseRunner):
395+
class CentipedeRunner(BaseRunner):
396396
"""Runner implementation for Centipede fuzzing engine."""
397397

398398

@@ -520,8 +520,8 @@ def process_bad_units(self, bad_units_path, quarantine_corpus_path):
520520
return crashes
521521

522522

523-
class GenericPruner(CorpusPrunerBase):
524-
"""Generic pruner."""
523+
class CentipedePruner(CorpusPrunerBase):
524+
"""Centipede pruner."""
525525

526526

527527
class CrossPollinator:
@@ -632,8 +632,16 @@ def do_corpus_pruning(uworker_input, context, revision) -> CorpusPruningResult:
632632

633633
build_directory = environment.get_value('BUILD_DIR')
634634
start_time = datetime.datetime.utcnow()
635-
runner = LibFuzzerRunner(build_directory, context)
636-
pruner = LibFuzzerPruner(runner)
635+
match context.fuzz_target.engine:
636+
case 'libFuzzer':
637+
runner = LibFuzzerRunner(build_directory, context)
638+
pruner = LibFuzzerPruner(runner)
639+
case 'centipede':
640+
runner = CentipedeRunner(build_directory, context)
641+
pruner = CentipedePruner(runner)
642+
case _:
643+
raise Exception('Corpus pruner task does not support the given engine.')
644+
637645
fuzzer_binary_name = os.path.basename(runner.target_path)
638646

639647
# Get initial corpus to process from GCS.
@@ -1051,6 +1059,7 @@ def utask_main(uworker_input):
10511059
logs.error(f'Corpus pruning failed: {e}')
10521060
uworker_output = uworker_msg_pb2.Output( # pylint: disable=no-member
10531061
error_type=uworker_msg_pb2.CORPUS_PRUNING_ERROR) # pylint: disable=no-member
1062+
raise e
10541063
finally:
10551064
context.cleanup()
10561065

Binary file not shown.

src/clusterfuzz/_internal/tests/core/bot/tasks/utasks/corpus_pruning_task_test.py

Lines changed: 39 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from clusterfuzz._internal.bot.fuzzers import options
2626
from clusterfuzz._internal.bot.fuzzers.libFuzzer import \
2727
engine as libFuzzer_engine
28+
from clusterfuzz._internal.bot.fuzzers.centipede import engine as centipede_engine
2829
from clusterfuzz._internal.bot.tasks import commands
2930
from clusterfuzz._internal.bot.tasks.utasks import corpus_pruning_task
3031
from clusterfuzz._internal.bot.tasks.utasks import uworker_io
@@ -384,132 +385,49 @@ def test_prune(self):
384385
self.assertCountEqual(['crash-7a8dc3985d2a90fb6e62e94910fc11d31949c348'],
385386
quarantine)
386387

387-
388-
class CorpusPruningTestUntrusted(
389-
untrusted_runner_helpers.UntrustedRunnerIntegrationTest):
390-
"""Tests for corpus pruning (untrusted)."""
388+
@test_utils.supported_platforms('LINUX')
389+
@test_utils.with_cloud_emulators('datastore')
390+
class CorpusPruningTestCentipede(unittest.TestCase, BaseTest):
391+
"""Tests for centipede corpus pruning."""
391392

392393
def setUp(self):
393394
"""Set up."""
394-
super().setUp()
395-
environment.set_value('JOB_NAME', 'libfuzzer_asan_job')
396-
395+
BaseTest.setUp(self)
397396
helpers.patch(self, [
398-
'clusterfuzz._internal.bot.tasks.setup.get_fuzzer_directory',
399-
'clusterfuzz._internal.base.tasks.add_task',
400-
'clusterfuzz.fuzz.engine.get',
397+
'clusterfuzz._internal.build_management.build_manager.setup_build',
398+
'clusterfuzz._internal.base.utils.get_application_id',
399+
'clusterfuzz._internal.datastore.data_handler.update_task_status',
400+
'clusterfuzz._internal.datastore.data_handler.get_task_status',
401401
])
402-
self.mock.get.return_value = libFuzzer_engine.Engine()
403-
self.mock.get_fuzzer_directory.return_value = os.path.join(
404-
environment.get_value('ROOT_DIR'), 'src', 'clusterfuzz', '_internal',
405-
'bot', 'fuzzers', 'libFuzzer')
406-
self.corpus_bucket = os.environ['CORPUS_BUCKET']
407-
self.quarantine_bucket = os.environ['QUARANTINE_BUCKET']
408-
self.backup_bucket = os.environ['BACKUP_BUCKET']
409-
410-
job = data_types.Job(
411-
name='libfuzzer_asan_job',
412-
environment_string=('APP_NAME = test_fuzzer\n'
413-
f'CORPUS_BUCKET = {self.corpus_bucket}\n'
414-
f'QUARANTINE_BUCKET = {self.quarantine_bucket}\n'
415-
f'BACKUP_BUCKET={self.backup_bucket}\n'
416-
'RELEASE_BUILD_BUCKET_PATH = '
417-
'gs://clusterfuzz-test-data/test_libfuzzer_builds2/'
418-
'test-libfuzzer-build-([0-9]+).zip\n'
419-
'REVISION_VARS_URL = gs://clusterfuzz-test-data/'
420-
'test_libfuzzer_builds2/'
421-
'test-libfuzzer-build-%s.srcmap.json\n'))
422-
job.put()
423-
424-
job = data_types.Job(
425-
name='libfuzzer_asan_job2',
426-
environment_string=('APP_NAME = test2_fuzzer\n'
427-
f'BACKUP_BUCKET = {self.backup_bucket}\n'
428-
'CORPUS_FUZZER_NAME_OVERRIDE = libfuzzer\n'))
429-
job.put()
430-
431-
os.environ['PROJECT_NAME'] = 'oss-fuzz'
432-
data_types.FuzzTarget(
433-
engine='libFuzzer', project='test', binary='test_fuzzer').put()
434-
data_types.FuzzTargetJob(
435-
fuzz_target_name='libFuzzer_test_fuzzer',
436-
engine='libFuzzer',
437-
job='libfuzzer_asan_job',
438-
last_run=datetime.datetime.now()).put()
402+
self.mock.setup_build.side_effect = self._mock_setup_build
403+
self.mock.get_application_id.return_value = 'project'
404+
self.mock.get.return_value = centipede_engine.Engine()
405+
self.maxDiff = None
406+
self.backup_bucket = os.environ['BACKUP_BUCKET'] or ''
439407

440408
data_types.FuzzTarget(
441-
engine='libFuzzer', project='test2', binary='fuzzer').put()
409+
engine='centipede', binary='clusterfuzz_format_target', project='test-project').put()
442410
data_types.FuzzTargetJob(
443-
fuzz_target_name='libFuzzer_test2_fuzzer',
444-
engine='libFuzzer',
445-
job='libfuzzer_asan_job2',
446-
last_run=datetime.datetime.now()).put()
447-
448-
# Set up remote corpora.
449-
self.corpus = corpus_manager.FuzzTargetCorpus('libFuzzer', 'test_fuzzer')
450-
self.corpus.rsync_from_disk(os.path.join(TEST_DIR, 'corpus'), delete=True)
451-
452-
self.quarantine_corpus = corpus_manager.FuzzTargetCorpus(
453-
'libFuzzer', 'test_fuzzer', quarantine=True)
454-
self.quarantine_corpus.rsync_from_disk(
455-
os.path.join(TEST_DIR, 'quarantine'), delete=True)
456-
457-
data_types.DataBundle(
458-
name='bundle', bundle_name=TEST_GLOBAL_BUCKET,
459-
sync_to_worker=True).put()
460-
461-
self.fuzzer = data_types.Fuzzer(
462-
revision=1,
463-
file_size='builtin',
464-
source='builtin',
465-
name='libFuzzer',
466-
max_testcases=4,
467-
builtin=True,
468-
data_bundle_name='bundle')
469-
self.fuzzer.put()
470-
471-
self.temp_dir = tempfile.mkdtemp()
411+
fuzz_target_name='centipede_clusterfuzz_format_target',
412+
engine='centipede',
413+
job='centipede_asan_job').put()
472414

473-
# Copy corpus backup in the older date format.
474-
corpus_backup_date = (
475-
datetime.datetime.utcnow().date() -
476-
datetime.timedelta(days=data_types.CORPUS_BACKUP_PUBLIC_LOOKBACK_DAYS))
477-
gsutil.GSUtilRunner().run_gsutil([
478-
'cp',
479-
f'gs://{TEST2_BACKUP_BUCKET}/corpus/libfuzzer/test2_fuzzer/backup.zip',
480-
(f'gs://{self.backup_bucket}/corpus/libfuzzer/'
481-
f'test2_fuzzer/{corpus_backup_date}.zip')
482-
])
483-
484-
def tearDown(self):
485-
super().tearDown()
486-
shutil.rmtree(self.temp_dir, ignore_errors=True)
487-
488-
@unittest.skip('Non-deterministic, impossible to tell why failing.')
489415
def test_prune(self):
490416
"""Test pruning."""
491-
self._setup_env(job_type='libfuzzer_asan_job')
492417
uworker_input = corpus_pruning_task.utask_preprocess(
493-
job_type='libfuzzer_asan_job',
494-
fuzzer_name='libFuzzer_test_fuzzer',
418+
job_type='centipede_asan_job',
419+
fuzzer_name='centipede_clusterfuzz_format_target',
495420
uworker_env={})
496-
corpus_pruning_task.utask_main(uworker_input)
497-
498-
corpus_dir = os.path.join(self.temp_dir, 'corpus')
499-
os.mkdir(corpus_dir)
421+
# src/clusterfuzz/_internal/tests/core/bot/fuzzers/centipede/test_data/clusterfuzz_format_target
422+
output = corpus_pruning_task.utask_main(uworker_input)
423+
self.assertFalse(output.HasField('error_type'))
424+
output.uworker_input.CopyFrom(uworker_input)
425+
corpus_pruning_task.utask_postprocess(output)
500426

501-
self.corpus.rsync_to_disk(corpus_dir)
427+
corpus = os.listdir(self.corpus_dir)
502428
self.assertCountEqual([
503-
'31836aeaab22dc49555a97edb4c753881432e01d',
504-
'39e0574a4abfd646565a3e436c548eeb1684fb57',
505-
], os.listdir(corpus_dir))
506-
507-
quarantine_dir = os.path.join(self.temp_dir, 'quarantine')
508-
os.mkdir(quarantine_dir)
509-
self.quarantine_corpus.rsync_to_disk(quarantine_dir)
510-
511-
self.assertCountEqual(['crash-7acd6a2b3fe3c5ec97fa37e5a980c106367491fa'],
512-
os.listdir(quarantine_dir))
429+
'7acd6a2b3fe3c5ec97fa37e5a980c106367491fa',
430+
], corpus)
513431

514432
testcases = list(data_types.Testcase.query())
515433
self.assertEqual(1, len(testcases))
@@ -518,24 +436,23 @@ def test_prune(self):
518436
self.assertEqual(1337, testcases[0].crash_revision)
519437
self.assertEqual('test_fuzzer',
520438
testcases[0].get_metadata('fuzzer_binary_name'))
521-
522-
self.mock.add_task.assert_has_calls([
523-
mock.call('minimize', testcases[0].key.id(), 'libfuzzer_asan_job'),
524-
])
439+
self.assertEqual('label1,label2', testcases[0].get_metadata('issue_labels'))
525440

526441
today = datetime.datetime.utcnow().date()
442+
# get_coverage_information on test_fuzzer rather than centipede_test_fuzzer
443+
# since the centipede_ prefix is removed when saving coverage info.
527444
coverage_info = data_handler.get_coverage_information('test_fuzzer', today)
528-
coverage_info_without_backup = coverage_info.to_dict()
529-
del coverage_info_without_backup['corpus_backup_location']
530445

531446
self.assertDictEqual(
532447
{
448+
'corpus_backup_location':
449+
uworker_input.corpus_pruning_task_input.dated_backup_gcs_url,
533450
'corpus_location':
534-
f'gs://{self.corpus_bucket}/libFuzzer/test_fuzzer/',
451+
'gs://bucket/centipede/test_fuzzer/',
535452
'corpus_size_bytes':
536-
8,
537-
'corpus_size_units':
538453
4,
454+
'corpus_size_units':
455+
2,
539456
'date':
540457
today,
541458
# Coverage numbers are expected to be None as they come from fuzzer
@@ -550,20 +467,10 @@ def test_prune(self):
550467
None,
551468
'fuzzer':
552469
'test_fuzzer',
553-
'html_report_url':
554-
None,
555-
'quarantine_location':
556-
f'gs://{self.quarantine_bucket}/libFuzzer/test_fuzzer/',
557-
'quarantine_size_bytes':
558-
2,
559-
'quarantine_size_units':
560-
1,
561470
},
562-
coverage_info_without_backup)
471+
coverage_info.to_dict())
563472

564-
self.assertEqual(
565-
coverage_info.corpus_backup_location,
566-
f'gs://{self.backup_bucket}/corpus/libFuzzer/test_fuzzer/{today}.zip')
473+
self.assertEqual(self.mock.unpack_seed_corpus_if_needed.call_count, 1)
567474

568475
def get_mock_record_compare(self, project_qualified_name, sources,
569476
initial_corpus_size, corpus_size,

0 commit comments

Comments
 (0)