Skip to content

Commit 5d59e5c

Browse files
Upload crashes to logs bucket in permissionless way (#4400)
1 parent 2dafa39 commit 5d59e5c

File tree

6 files changed

+69
-51
lines changed

6 files changed

+69
-51
lines changed

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,11 +1530,17 @@ def do_engine_fuzzing(self, engine_impl):
15301530
crash_result_obj = crash_result.CrashResult(
15311531
return_code, result.time_executed, result.logs)
15321532
output = crash_result_obj.get_stacktrace()
1533-
self.fuzz_task_output.engine_outputs.append(
1534-
_to_engine_output(output, return_code, log_time))
1533+
# TODO(metzman): Consider uploading this with a signed URL.
1534+
if result.crashes:
1535+
# We only upload the first, because they will clobber each other if we
1536+
# upload more.
1537+
result_crash = result.crashes[0].input_path
1538+
else:
1539+
result_crash = None
15351540

1536-
for crash in result.crashes:
1537-
testcase_manager.upload_testcase(crash.input_path, log_time)
1541+
engine_output = _to_engine_output(output, result_crash, return_code,
1542+
log_time)
1543+
self.fuzz_task_output.engine_outputs.append(engine_output)
15381544

15391545
add_additional_testcase_run_data(testcase_run,
15401546
self.fuzz_target.fully_qualified_name(),
@@ -2051,7 +2057,7 @@ def save_fuzz_targets(output):
20512057
output.uworker_input.job_type)
20522058

20532059

2054-
def _to_engine_output(output: str, return_code: int,
2060+
def _to_engine_output(output: str, crash_path: str, return_code: int,
20552061
log_time: datetime.datetime):
20562062
"""Returns an EngineOutput proto."""
20572063
truncated_output = truncate_fuzzer_output(output, ENGINE_OUTPUT_LIMIT)
@@ -2063,13 +2069,22 @@ def _to_engine_output(output: str, return_code: int,
20632069
output=bytes(truncated_output, 'utf-8'),
20642070
return_code=return_code,
20652071
timestamp=proto_timestamp)
2072+
2073+
if crash_path is None:
2074+
return engine_output
2075+
if os.path.getsize(crash_path) > 10 * 1024**2:
2076+
return engine_output
2077+
with open(crash_path, 'rb') as fp:
2078+
engine_output.testcase = fp.read()
2079+
20662080
return engine_output
20672081

20682082

2069-
def _upload_engine_output_log(engine_output):
2083+
def _upload_engine_output(engine_output):
20702084
timestamp = uworker_io.proto_timestamp_to_timestamp(engine_output.timestamp)
20712085
testcase_manager.upload_log(engine_output.output.decode(),
20722086
engine_output.return_code, timestamp)
2087+
testcase_manager.upload_testcase(None, engine_output.testcase, timestamp)
20732088

20742089

20752090
def utask_postprocess(output):
@@ -2087,4 +2102,4 @@ def utask_postprocess(output):
20872102
# TODO(b/374776013): Refactor this code so the uploads happen during
20882103
# utask_main.
20892104
for engine_output in output.fuzz_task_output.engine_outputs:
2090-
_upload_engine_output_log(engine_output)
2105+
_upload_engine_output(engine_output)

src/clusterfuzz/_internal/bot/testcase_manager.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -424,21 +424,22 @@ def _get_testcase_time(testcase_path):
424424
return None
425425

426426

427-
def upload_testcase(testcase_path, log_time):
427+
def upload_testcase(testcase_path, testcase_data, log_time):
428428
"""Uploads testcase so that a log file can be matched with it folder."""
429429
fuzz_logs_bucket = environment.get_value('FUZZ_LOGS_BUCKET')
430430
if not fuzz_logs_bucket:
431431
return
432432

433-
if not os.path.exists(testcase_path):
434-
return
435-
436-
with open(testcase_path, 'rb') as file_handle:
437-
testcase_contents = file_handle.read()
433+
assert not (testcase_path and testcase_data)
434+
if testcase_path:
435+
if not os.path.exists(testcase_path):
436+
return
437+
with open(testcase_path, 'rb') as file_handle:
438+
testcase_data = file_handle.read()
438439

439440
fuzzer_logs.upload_to_logs(
440441
fuzz_logs_bucket,
441-
testcase_contents,
442+
testcase_data,
442443
time=log_time,
443444
file_extension='.testcase')
444445

@@ -534,7 +535,7 @@ def _do_run_testcase_and_return_result_in_queue(crash_queue,
534535
# Don't upload uninteresting testcases (no crash) or if there is no log to
535536
# correlate it with (not upload_output).
536537
if upload_output:
537-
upload_testcase(file_path, log_time)
538+
upload_testcase(file_path, log_time, None)
538539

539540
if upload_output:
540541
# Include full output for uploaded logs (crash output, merge output, etc).

src/clusterfuzz/_internal/protos/uworker_msg.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ message EngineOutput {
240240
optional bytes output = 1;
241241
optional int64 return_code = 2;
242242
google.protobuf.Timestamp timestamp = 3;
243+
optional bytes testcase = 4;
243244
}
244245

245246
message FuzzTaskOutput {

src/clusterfuzz/_internal/protos/uworker_msg_pb2.py

Lines changed: 28 additions & 28 deletions
Large diffs are not rendered by default.

src/clusterfuzz/_internal/protos/uworker_msg_pb2.pyi

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,23 +1199,28 @@ class EngineOutput(google.protobuf.message.Message):
11991199
OUTPUT_FIELD_NUMBER: builtins.int
12001200
RETURN_CODE_FIELD_NUMBER: builtins.int
12011201
TIMESTAMP_FIELD_NUMBER: builtins.int
1202+
TESTCASE_FIELD_NUMBER: builtins.int
12021203
output: builtins.bytes
12031204
return_code: builtins.int
12041205
@property
12051206
def timestamp(self) -> google.protobuf.timestamp_pb2.Timestamp: ...
1207+
testcase: builtins.bytes
12061208
def __init__(
12071209
self,
12081210
*,
12091211
output: builtins.bytes | None = ...,
12101212
return_code: builtins.int | None = ...,
12111213
timestamp: google.protobuf.timestamp_pb2.Timestamp | None = ...,
1214+
testcase: builtins.bytes | None = ...,
12121215
) -> None: ...
1213-
def HasField(self, field_name: typing_extensions.Literal["_output", b"_output", "_return_code", b"_return_code", "output", b"output", "return_code", b"return_code", "timestamp", b"timestamp"]) -> builtins.bool: ...
1214-
def ClearField(self, field_name: typing_extensions.Literal["_output", b"_output", "_return_code", b"_return_code", "output", b"output", "return_code", b"return_code", "timestamp", b"timestamp"]) -> None: ...
1216+
def HasField(self, field_name: typing_extensions.Literal["_output", b"_output", "_return_code", b"_return_code", "_testcase", b"_testcase", "output", b"output", "return_code", b"return_code", "testcase", b"testcase", "timestamp", b"timestamp"]) -> builtins.bool: ...
1217+
def ClearField(self, field_name: typing_extensions.Literal["_output", b"_output", "_return_code", b"_return_code", "_testcase", b"_testcase", "output", b"output", "return_code", b"return_code", "testcase", b"testcase", "timestamp", b"timestamp"]) -> None: ...
12151218
@typing.overload
12161219
def WhichOneof(self, oneof_group: typing_extensions.Literal["_output", b"_output"]) -> typing_extensions.Literal["output"] | None: ...
12171220
@typing.overload
12181221
def WhichOneof(self, oneof_group: typing_extensions.Literal["_return_code", b"_return_code"]) -> typing_extensions.Literal["return_code"] | None: ...
1222+
@typing.overload
1223+
def WhichOneof(self, oneof_group: typing_extensions.Literal["_testcase", b"_testcase"]) -> typing_extensions.Literal["testcase"] | None: ...
12191224

12201225
global___EngineOutput = EngineOutput
12211226

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,12 +1356,6 @@ def test_basic(self):
13561356
13571357
}, fuzzer_metadata)
13581358

1359-
log_time = datetime.datetime(1970, 1, 1, 0, 0)
1360-
self.mock.upload_testcase.assert_has_calls([
1361-
mock.call('/input', log_time),
1362-
mock.call('/input', log_time),
1363-
])
1364-
13651359
self.assertEqual(2, len(crashes))
13661360
for i in range(2):
13671361
self.assertEqual('/input', crashes[i].file_path)
@@ -1383,6 +1377,8 @@ def test_basic(self):
13831377
'strategy_strategy_2': 50,
13841378
'timestamp': 0.0,
13851379
}, testcase_run)
1380+
# TODO(metzman): We need a test for fuzzing end to end with
1381+
# preprocess/main/postprocess.
13861382

13871383

13881384
class UntrustedRunEngineFuzzerTest(

0 commit comments

Comments
 (0)