Skip to content

Commit

Permalink
Reduce bug filing threshold for libfuzzer-based Android crashes
Browse files Browse the repository at this point in the history
Updated triage.py to file bugs for fuzzer crashes by lowering the
threshold limit. These fuzzers run on android platform and on
libfuzzer engine.
  • Loading branch information
AashutoshMurthy committed Dec 9, 2024
1 parent 089d9ef commit de495b2
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 3 deletions.
63 changes: 60 additions & 3 deletions src/clusterfuzz/_internal/cron/triage.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,60 @@ def _is_bug_filed(testcase):
return False


def _is_blocking_progress(testcase):
"""Checks the crash frequency if it is reported on libfuzzer"""
if testcase.job_type.startswith('libfuzzer'):
# Get crash statistics data on this unreproducible crash for last X days.
last_hour = crash_stats.get_last_successful_hour()
if not last_hour:
# No crash stats available, skip.
return False

_, rows = crash_stats.get(
end=last_hour,
block='day',
days=data_types.FILE_CONSISTENT_UNREPRODUCIBLE_TESTCASE_DEADLINE,
group_by='reproducible_flag',
where_clause=(
'crash_type = %s AND crash_state = %s AND security_flag = %s' %
(json.dumps(testcase.crash_type), json.dumps(testcase.crash_state),
json.dumps(testcase.security_flag))),
group_having_clause='',
sort_by='total_count',
offset=0,
limit=1)

# Calculate total crash count and crash days count.
crash_days_indices = set()
total_crash_count = 0
for row in rows:
if 'groups' not in row:
continue

total_crash_count += row['totalCount']
for group in row['groups']:
for index in group['indices']:
crash_days_indices.add(index['hour'])

crash_days_count = len(crash_days_indices)
# Considers an unreproducible testcase as important if the crash
# occurred atleast once everyday for the last 14 days and total
# crash count exceeded 14.
return (crash_days_count ==
data_types.FILE_CONSISTENT_UNREPRODUCIBLE_TESTCASE_DEADLINE and
total_crash_count >=
data_types.FILE_UNREPRODUCIBLE_TESTCASE_MIN_STARTUP_CRASH_THRESHOLD)

return False


def is_crash_important_android(testcase):
""""Indicate if the android crash is important to file."""
if _is_blocking_progress():
return True
return False


def _is_crash_important(testcase):
"""Indicate if the crash is important to file."""
if not testcase.one_time_crasher_flag:
Expand Down Expand Up @@ -388,9 +442,12 @@ def main():
# Check if the crash is important, i.e. it is either a reproducible crash
# or an unreproducible crash happening frequently.
if not _is_crash_important(testcase):
logs.info(
f'Skipping testcase {testcase_id}, since the crash is not important.')
continue
# Check if the crash is a startup crash, i.e. it is causing the fuzzer
# to crash on startup and not allowing the fuzzer to run longer
if if testcase.platform == "android" and not is_crash_important_android(testcase):
logs.info(
f'Skipping testcase {testcase_id}, since the crash is not important.')
continue

# Require that all tasks like minimizaton, regression testing, etc have
# finished.
Expand Down
3 changes: 3 additions & 0 deletions src/clusterfuzz/_internal/datastore/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
# Minimum number of unreproducible crashes to see before filing it.
FILE_UNREPRODUCIBLE_TESTCASE_MIN_CRASH_THRESHOLD = 100

# Minimum number of unreproducible crashes to see before filing it for android.
FILE_UNREPRODUCIBLE_TESTCASE_MIN_STARTUP_CRASH_THRESHOLD = 14

# Heartbeat wait interval.
HEARTBEAT_WAIT_INTERVAL = 10 * 60

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,67 @@
from clusterfuzz._internal.tests.test_libs import test_utils


@test_utils.with_cloud_emulators('datastore')
class AndroidCrashImportantTest(unittest.TestCase):
"""Tests for is_crash_important_android."""

def setUp(self):
helpers.patch(self, [
'clusterfuzz._internal.metrics.crash_stats.get_last_successful_hour',
'clusterfuzz._internal.metrics.crash_stats.get',
'clusterfuzz._internal.base.utils.utcnow',
])
self.mock.utcnow.return_value = test_utils.CURRENT_TIME

def test_is_crash_important_android_1(self):
"""If this unreproducible testcase (libfuzzer) is crashing frequently,
then it is an important crash."""
self.mock.get_last_successful_hour.return_value = 417325
indices = [{
'count': 1,
'hour': day_index
} for day_index in range(417325, 416989, -24)]

self.mock.get.return_value = (1, [{
'totalCount': 14,
'groups': [{
'indices': indices,
'name': 'false',
},]
}])
testcase = test_utils.create_generic_testcase()
testcase.job_type = 'libfuzzer_test'
testcase.platform = 'android'
testcase.one_time_crasher_flag = True
testcase.put()

self.assertTrue(triage.is_crash_important_android(testcase))

def test_is_crash_important_android_2(self):
"""If this unreproducible testcase (libfuzzer) is less than the
total crash threshold, then it is not important."""
self.mock.get_last_successful_hour.return_value = 417325
indices = [{
'count': day_index % 5 == 0,
'hour': day_index
} for day_index in range(417325, 416989, -24)]

self.mock.get.return_value = (1, [{
'totalCount': 3,
'groups': [{
'indices': indices,
'name': 'false',
},]
}])
testcase = test_utils.create_generic_testcase()
testcase.job_type = 'libfuzzer_test'
testcase.platform = 'android'
testcase.one_time_crasher_flag = True
testcase.put()

self.assertFalse(triage.is_crash_important_android(testcase))


@test_utils.with_cloud_emulators('datastore')
class CrashImportantTest(unittest.TestCase):
"""Tests for _is_crash_important."""
Expand Down

0 comments on commit de495b2

Please sign in to comment.