Skip to content

Commit 7e1ff72

Browse files
authored
Avoid overwriting minimized keys if minimization round fails. (#4626)
Previous logic would overwrite `minimized_keys` if say round 5 of libfuzzer minimization failed, even if round 4 succeeded. This resulted in tasks completing minimization successfully, but not storing `minimized_keys` to reflect that in the database. Instead, we should take the result of the last successful minimization round. This both aligns with the logic around crash results and testcase file names, and prevents another side effect of this bug: we were previously deleting blobs on GCS that had been uploaded during successful minimization rounds :/ Note: it seems a similar bug affects the cleanse step, but I did not change logic there as it is OSS-Fuzz specific and I was not sure whether or not it could be intentional. See [`minimize_task.py` line 1686](https://github.com/google/clusterfuzz/pull/4626/files#diff-e8255271eeeadc2bda46b215fbc7b0bb160ef1116f6e27ff895990d88caafa5fR1686). There are exceedingly few tests for minimize task, so I did not write a test for this either - the effort required seemed too high. Bug: https://crbug.com/389589679
1 parent 41e5571 commit 7e1ff72

File tree

2 files changed

+28
-18
lines changed

2 files changed

+28
-18
lines changed

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

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,9 +1370,9 @@ def do_js_minimization(test_function, get_temp_file, data, deadline, threads,
13701370

13711371

13721372
def _run_libfuzzer_testcase(fuzz_target,
1373-
testcase,
1374-
testcase_file_path,
1375-
crash_retries=1):
1373+
testcase: data_types.Testcase,
1374+
testcase_file_path: str,
1375+
crash_retries: int = 1) -> CrashResult:
13761376
"""Run libFuzzer testcase, and return the CrashResult."""
13771377
# Cleanup any existing application instances and temp directories.
13781378
process_handler.cleanup_stale_processes()
@@ -1429,8 +1429,16 @@ def _run_libfuzzer_tool(
14291429
expected_crash_state: str,
14301430
minimize_task_input: uworker_msg_pb2.MinimizeTaskInput, # pylint: disable=no-member
14311431
fuzz_target: Optional[data_types.FuzzTarget],
1432-
set_dedup_flags: bool = False):
1433-
"""Run libFuzzer tool to either minimize or cleanse."""
1432+
set_dedup_flags: bool = False
1433+
) -> tuple[str, CrashResult, str] | tuple[None, None, None]:
1434+
"""Run libFuzzer tool to either minimize or cleanse.
1435+
1436+
Returns (None, None, None) in case of failure.
1437+
Otherwise sets `testcase.minimized_keys` and returns:
1438+
1439+
(testcase_file_path, crash_result, minimized_keys)
1440+
1441+
"""
14341442
memory_tool_options_var = environment.get_current_memory_tool_var()
14351443
saved_memory_tool_options = environment.get_value(memory_tool_options_var)
14361444

@@ -1565,8 +1573,6 @@ def do_libfuzzer_minimization(
15651573
"""Use libFuzzer's built-in minimizer where appropriate."""
15661574
timeout = environment.get_value('LIBFUZZER_MINIMIZATION_TIMEOUT', 600)
15671575
rounds = environment.get_value('LIBFUZZER_MINIMIZATION_ROUNDS', 5)
1568-
current_testcase_path = testcase_file_path
1569-
last_crash_result = None
15701576

15711577
# Get initial crash state.
15721578
initial_crash_result = _run_libfuzzer_testcase(
@@ -1637,7 +1643,10 @@ def do_libfuzzer_minimization(
16371643
if env:
16381644
testcase.set_metadata('env', env, False)
16391645

1640-
minimized_keys = None
1646+
current_testcase_path = testcase_file_path
1647+
last_crash_result = None
1648+
last_minimized_keys = None
1649+
16411650
# We attempt minimization multiple times in case one round results in an
16421651
# incorrect state, or runs into another issue such as a slow unit.
16431652
for round_number in range(1, rounds + 1):
@@ -1653,6 +1662,7 @@ def do_libfuzzer_minimization(
16531662
set_dedup_flags=True)
16541663
if output_file_path:
16551664
last_crash_result = crash_result
1665+
last_minimized_keys = minimized_keys
16561666
current_testcase_path = output_file_path
16571667

16581668
if not last_crash_result:
@@ -1663,8 +1673,8 @@ def do_libfuzzer_minimization(
16631673
minimize_task_output = uworker_msg_pb2.MinimizeTaskOutput( # pylint: disable=no-member
16641674
last_crash_result_dict=crash_result_dict,
16651675
memory_tool_options=memory_tool_options)
1666-
if minimized_keys:
1667-
minimize_task_output.minimized_keys = str(minimized_keys)
1676+
if last_minimized_keys:
1677+
minimize_task_output.minimized_keys = str(last_minimized_keys)
16681678
return uworker_msg_pb2.Output( # pylint: disable=no-member
16691679
error_type=uworker_msg_pb2.ErrorType.LIBFUZZER_MINIMIZATION_FAILED, # pylint: disable=no-member
16701680
minimize_task_output=minimize_task_output)
@@ -1673,7 +1683,7 @@ def do_libfuzzer_minimization(
16731683

16741684
if utils.is_oss_fuzz():
16751685
# Scrub the testcase of non-essential data.
1676-
cleansed_testcase_path, minimized_keys = do_libfuzzer_cleanse(
1686+
cleansed_testcase_path, last_minimized_keys = do_libfuzzer_cleanse(
16771687
fuzz_target, testcase, current_testcase_path,
16781688
expected_state.crash_state, minimize_task_input)
16791689
if cleansed_testcase_path:
@@ -1690,8 +1700,8 @@ def do_libfuzzer_minimization(
16901700
minimize_task_output = uworker_msg_pb2.MinimizeTaskOutput( # pylint: disable=no-member
16911701
last_crash_result_dict=last_crash_result_dict,
16921702
memory_tool_options=memory_tool_options)
1693-
if minimized_keys:
1694-
minimize_task_output.minimized_keys = str(minimized_keys)
1703+
if last_minimized_keys:
1704+
minimize_task_output.minimized_keys = str(last_minimized_keys)
16951705
return uworker_msg_pb2.Output(minimize_task_output=minimize_task_output) # pylint: disable=no-member
16961706

16971707

src/clusterfuzz/_internal/bot/testcase_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -673,10 +673,10 @@ def _get_crash_state(self, round_number: int,
673673
return state
674674

675675
def reproduce_with_retries(self,
676-
retries,
677-
expected_state=None,
678-
expected_security_flag=None,
679-
flaky_stacktrace=False):
676+
retries: int,
677+
expected_state: CrashInfo | None = None,
678+
expected_security_flag: bool | None = None,
679+
flaky_stacktrace: bool = False) -> CrashResult:
680680
"""Try reproducing a crash with retries."""
681681
self._pre_run_cleanup()
682682
crash_result = None
@@ -775,7 +775,7 @@ def test_for_crash_with_retries(fuzz_target,
775775
http_flag=False,
776776
use_gestures=True,
777777
compare_crash=True,
778-
crash_retries=None):
778+
crash_retries=None) -> CrashResult:
779779
"""Test for a crash and return crash parameters like crash type, crash state,
780780
crash stacktrace, etc."""
781781
logs.info('Testing for crash.')

0 commit comments

Comments
 (0)