Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/clusterfuzz/_internal/base/tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,23 @@ def get_regular_task(queue=None):
return None

task = get_task_from_message(messages[0], queue)
if task:
return task
if not task:
continue

if task.command == 'fuzz' and not environment.is_tworker():
fuzzer = data_types.Fuzzer.query(
data_types.Fuzzer.name == task.argument).get()
if not fuzzer:
logs.error(
f'Fuzzer {task.argument} not found. Discarding invalid task.')
task.dont_retry()
continue
if not fuzzer.trusted:
logs.info(
f'Skipping untrusted fuzzer {task.argument} on long-lived bot.')
continue

return task


def get_machine_template_for_queue(queue_name):
Expand Down
10 changes: 9 additions & 1 deletion src/clusterfuzz/_internal/bot/tasks/utasks/fuzz_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,13 @@ def create_testcase(group: uworker_msg_pb2.FuzzTaskCrashGroup,
comment = (f'Fuzzer {fully_qualified_fuzzer_name} generated testcase crashed '
f'in {crash.crash_time} seconds '
f'(r{fuzz_task_output.crash_revision})')

is_trusted = True
if uworker_input.setup_input.HasField('fuzzer'):
fuzzer = uworker_io.entity_from_protobuf(uworker_input.setup_input.fuzzer,
data_types.Fuzzer)
is_trusted = fuzzer.trusted

testcase_id = data_handler.store_testcase(
crash=crash,
fuzzed_keys=crash.fuzzed_key or None,
Expand All @@ -1194,7 +1201,7 @@ def create_testcase(group: uworker_msg_pb2.FuzzTaskCrashGroup,
minimized_arguments=crash.arguments,
# TODO(https://github.com/google/clusterfuzz/issues/4175): Before enabling
# oss-fuzz-on-demand change this.
trusted=True)
trusted=is_trusted)
testcase = data_handler.get_testcase_by_id(testcase_id)
events.emit(
events.TestcaseCreationEvent(
Expand Down Expand Up @@ -2125,6 +2132,7 @@ def run(self):
time.sleep(failure_wait_interval)
return uworker_msg_pb2.Output( # pylint: disable=no-member
error_type=uworker_msg_pb2.ErrorType.FUZZ_NO_FUZZER) # pylint: disable=no-member
uworker_io.check_running_fuzzer_safe(self.fuzzer)

# Update the session's test_timeout to use the fuzzer's timeout (if any).
# When the fuzzer has a specified timeout, `update_fuzzer_and_data_bundles`
Expand Down
23 changes: 19 additions & 4 deletions src/clusterfuzz/_internal/bot/tasks/utasks/uworker_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,25 @@ def check_handling_testcase_safe(testcase):
safely."""
if testcase.trusted:
return
if not environment.get_value('UNTRUSTED_UTASK'):
# TODO(https://b.corp.google.com/issues/328691756): Change this to
# log_fatal_and_exit once we are handling untrusted tasks properly.
logs.warning(f'Cannot handle {testcase.key.id()} in trusted task.')
if environment.is_uworker():
return

logs.log_fatal_and_exit(
f'Security Violation: Cannot handle untrusted testcase '
f'{testcase.key.id()} in long-lived bot.')


def check_running_fuzzer_safe(fuzzer):
"""Exits when the fuzzer is untrusted but the execution environment is
trusted."""
if fuzzer.trusted:
return True
if environment.is_uworker():
return True
logs.log_fatal_and_exit(
f'Security Violation: Cannot run untrusted fuzzer {fuzzer.name} '
f'in trusted environment.')
return False


def timestamp_to_proto_timestamp(pydt) -> Timestamp:
Expand Down
13 changes: 13 additions & 0 deletions src/clusterfuzz/_internal/fuzzing/fuzzer_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,19 @@ def get_fuzz_task_payload(platform=None):
jobs = get_job_list(jobs_selection)
selected_mappings = [entity for entity in mappings if entity.job in jobs]

if not environment.is_uworker():
untrusted_fuzzers = {
fuzzer.name for fuzzer in ndb_utils.get_all_from_query(
data_types.Fuzzer.query(
ndb_utils.is_false(data_types.Fuzzer.trusted)))
}

if untrusted_fuzzers:
selected_mappings = [
mapping for mapping in selected_mappings
if mapping.fuzzer not in untrusted_fuzzers
]

if not selected_mappings:
return None, None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,7 @@ def _setup_utask_env(self):
os.environ['DATA_BUNDLES_DIR'] = '/tmp/data-bundles'
os.environ['FUZZ_DATA'] = '/tmp/fuzz-data'
os.environ['BOT_NAME'] = 'test-bot'
os.environ['UWORKER'] = 'True'

self.fs.create_dir('/tmp/fuzz-data')
self.fs.create_dir('/tmp/data-bundles')
Expand Down Expand Up @@ -2110,6 +2111,48 @@ def test_create_testcase_event_emit(self):
creation_origin=events.TestcaseOrigin.FUZZ_TASK,
uploader=None))

def test_create_testcase_trusted_fuzzer(self):
"""Test creating a testcase with a trusted fuzzer."""
fuzzer = data_types.Fuzzer(name='engine', trusted=True)
fuzzer.put()

self.uworker_input.setup_input.CopyFrom(uworker_msg_pb2.SetupInput())
self.uworker_input.setup_input.fuzzer.CopyFrom(
uworker_io.entity_to_protobuf(fuzzer))

self.mock.store_testcase.side_effect = _store_generic_testcase

fuzz_task.create_testcase(
group=self.group,
uworker_input=self.uworker_input,
uworker_output=self.uworker_output,
fully_qualified_fuzzer_name='engine')

self.mock.store_testcase.assert_called_once()
kwargs = self.mock.store_testcase.call_args[1]
self.assertTrue(kwargs.get('trusted'))

def test_create_testcase_untrusted_fuzzer(self):
"""Test creating a testcase with an untrusted fuzzer."""
fuzzer = data_types.Fuzzer(name='engine', trusted=False)
fuzzer.put()

self.uworker_input.setup_input.CopyFrom(uworker_msg_pb2.SetupInput())
self.uworker_input.setup_input.fuzzer.CopyFrom(
uworker_io.entity_to_protobuf(fuzzer))

self.mock.store_testcase.side_effect = _store_generic_testcase

fuzz_task.create_testcase(
group=self.group,
uworker_input=self.uworker_input,
uworker_output=self.uworker_output,
fully_qualified_fuzzer_name='engine')

self.mock.store_testcase.assert_called_once()
kwargs = self.mock.store_testcase.call_args[1]
self.assertFalse(kwargs.get('trusted'))


def _store_generic_testcase(*args, **kwargs): # pylint: disable=unused-argument
"""Store a generic testcase and return its id."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def setUp(self):
"""Set up."""
super().setUp()
environment.set_value('JOB_NAME', 'libfuzzer_asan_job')
environment.set_value('UWORKER', True)

patcher = mock.patch(
'clusterfuzz._internal.bot.fuzzers.libFuzzer.fuzzer.LibFuzzer.fuzzer_directory',
Expand Down Expand Up @@ -384,7 +385,7 @@ def test_check_app_path_exit(self, setup_testcase, preprocess_setup_testcase,
setup_testcase.return_value = ([], '/path', None)
del setup_build
del check_app_path
testcase = data_types.Testcase()
testcase = data_types.Testcase(trusted=True)
testcase.put()
environment.set_value('FAIL_WAIT', 10)
uworker_input = uworker_msg_pb2.Input(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,32 @@ def test_issue_metadata_field(self):
wire_format = uworker_io.serialize_uworker_input(output)
deserialized = uworker_io.deserialize_uworker_output(wire_format)
self.assertEqual(json.loads(deserialized.issue_metadata), metadata)


class TestCheckRunningFuzzerSafe(unittest.TestCase):
"""Tests check_running_fuzzer_safe."""

def setUp(self):
helpers.patch(self, [
'clusterfuzz._internal.system.environment.is_uworker',
])
self.fuzzer = mock.MagicMock(spec=data_types.Fuzzer)
self.fuzzer.name = 'test_fuzzer'

def test_trusted_fuzzer(self):
"""Test that trusted fuzzer passes without checks."""
self.fuzzer.trusted = True
self.assertTrue(uworker_io.check_running_fuzzer_safe(self.fuzzer))

def test_untrusted_fuzzer_uworker(self):
"""Test that untrusted fuzzer on uworker passes."""
self.fuzzer.trusted = False
self.mock.is_uworker.return_value = True
self.assertTrue(uworker_io.check_running_fuzzer_safe(self.fuzzer))

def test_untrusted_fuzzer_not_uworker_raises(self):
"""Test that untrusted fuzzer not on uworker raises SystemExit."""
self.fuzzer.trusted = False
self.mock.is_uworker.return_value = False
with self.assertRaises(SystemExit):
uworker_io.check_running_fuzzer_safe(self.fuzzer)
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,38 @@ def test_platform_restriction(self, local_development):
argument, job = fuzzer_selection.get_fuzz_task_payload('ANDROID:PIXEL6')
self.assertEqual(('pixel_fuzzer', 'job_pixel6'), (argument, job))

@parameterized.parameterized.expand([
('False', 'trusted_fuzzer', 'job_2'),
('True', 'untrusted_fuzzer', 'job_1'),
])
def test_untrusted_fuzzer_exclusion(self, is_uworker, expected_fuzzer,
expected_job):
"""Ensure that untrusted fuzzers are filtered out on long-lived bots."""
os.environ['UWORKER'] = is_uworker

data_types.Fuzzer(name='untrusted_fuzzer', trusted=False).put()
data_types.Fuzzer(name='trusted_fuzzer', trusted=True).put()

untrusted_mapping = data_types.FuzzerJob()
untrusted_mapping.fuzzer = 'untrusted_fuzzer'
untrusted_mapping.job = 'job_1'
untrusted_mapping.platform = 'linux'
untrusted_mapping.put()

trusted_mapping = data_types.FuzzerJob()
trusted_mapping.fuzzer = 'trusted_fuzzer'
trusted_mapping.job = 'job_2'
trusted_mapping.platform = 'linux'
trusted_mapping.put()

data_types.FuzzerJobs(
platform='linux', fuzzer_jobs=[untrusted_mapping,
trusted_mapping]).put()

argument, job = fuzzer_selection.get_fuzz_task_payload('linux')

self.assertEqual((expected_fuzzer, expected_job), (argument, job))


@test_utils.with_cloud_emulators('datastore')
class UpdatePlatformForJobTest(unittest.TestCase):
Expand Down
1 change: 1 addition & 0 deletions src/clusterfuzz/_internal/tests/test_libs/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def create_generic_testcase(created_days_ago=28):
testcase.timestamp = CURRENT_TIME - datetime.timedelta(days=created_days_ago)
testcase.project_name = 'project'
testcase.platform = 'linux'
testcase.trusted = True
testcase.put()

return testcase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def setUp(self):
self.uworker_env = commands.update_environment_for_job(environment_string)
job.put()
self.fuzz_target = 'test_fuzzer'
self.testcase = data_types.Testcase(job_type=self.job_type)
self.testcase = data_types.Testcase(job_type=self.job_type, trusted=True)
self.testcase.fuzzed_keys = blobs.write_blob(
os.path.join(TEST_LIBS_DATA_DIR,
'crash-adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'))
Expand Down
Loading