[CIFuzz] Fix handling of sanitizer artifacts (#5182)

Fixes #5175
1. Put sanitizer in artifact name.
2. Fix parsing of non-ASAN stacks.
This commit is contained in:
jonathanmetzman 2021-02-18 09:57:34 -08:00 committed by GitHub
parent d7164ef352
commit e93f222966
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 109 additions and 23 deletions

View File

@ -214,9 +214,9 @@ The results of CIFuzz can be found in two different places.
1. When a crash is found by CIFuzz the Upload Artifact event is triggered.
1. This will cause a pop up in the right hand corner, allowing
you to download a zip file called `artifacts`.
1. `artifacts` contains two files:
* `test_case` - a test case that can be used to reproduce the crash.
* `bug_summary` - the stack trace and summary of the crash.
1. `artifacts` contains two files for each crash:
* A test case that can be used to reproduce the crash.
* The sanitizer stack trace of the crash.
![Finding uploaded artifacts](../images/artifacts.png)

View File

@ -100,9 +100,12 @@ class BaseFuzzTargetRunner:
raise NotImplementedError('Child class must implement method')
def get_fuzz_target_artifact(self, target, artifact_name):
"""Returns the path of a fuzzing |artifact| named |artifact_name| for
|target|."""
artifact_name = target.target_name + '-' + artifact_name
"""Returns the path of a fuzzing artifact named |artifact_name| for
|fuzz_target|."""
artifact_name = '{target_name}-{sanitizer}-{artifact_name}'.format(
target_name=target.target_name,
sanitizer=self.config.sanitizer,
artifact_name=artifact_name)
return os.path.join(self.artifacts_dir, artifact_name)
def create_fuzz_target_obj(self, target_path, run_seconds):

View File

@ -227,7 +227,8 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase):
target.target_name = target_name
fuzz_target_artifact = runner.get_fuzz_target_artifact(
target, artifact_name)
expected_fuzz_target_artifact = 'artifacts-dir/target_name-artifact-name'
expected_fuzz_target_artifact = (
'artifacts-dir/target_name-address-artifact-name')
self.assertEqual(fuzz_target_artifact, expected_fuzz_target_artifact)
@ -263,7 +264,7 @@ class CiFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase):
magic_mock.target_name = 'target1'
mocked_create_fuzz_target_obj.return_value = magic_mock
self.assertTrue(runner.run_fuzz_targets())
self.assertIn('target1-testcase', os.listdir(runner.artifacts_dir))
self.assertIn('target1-address-testcase', os.listdir(runner.artifacts_dir))
self.assertEqual(mocked_run_fuzz_target.call_count, 1)
@ -312,7 +313,7 @@ class BatchFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase):
magic_mock.target_name = 'target1'
mocked_create_fuzz_target_obj.return_value = magic_mock
self.assertTrue(runner.run_fuzz_targets())
self.assertIn('target1-testcase', os.listdir(runner.artifacts_dir))
self.assertIn('target1-address-testcase', os.listdir(runner.artifacts_dir))
self.assertEqual(mocked_run_fuzz_target.call_count, 2)

View File

@ -13,6 +13,8 @@
# limitations under the License.
"""Module for parsing stacks from fuzz targets."""
import logging
# From clusterfuzz: src/python/crash_analysis/crash_analyzer.py
# Used to get the beginning of the stacktrace.
STACKTRACE_TOOL_MARKERS = [
@ -51,25 +53,33 @@ def parse_fuzzer_output(fuzzer_output, parsed_output_file_path):
parsed_output_file_path: The location to store the parsed output.
"""
# Get index of key file points.
begin_stack = None
for marker in STACKTRACE_TOOL_MARKERS:
marker_index = fuzzer_output.find(marker)
if marker_index:
if marker_index != -1:
begin_stack = marker_index
break
end_stack = -1
if begin_stack is None:
logging.error(
b'Could not find a begin stack marker (%s) in fuzzer output:\n%s',
STACKTRACE_TOOL_MARKERS, fuzzer_output)
return
end_stack = None
for marker in STACKTRACE_END_MARKERS:
marker_index = fuzzer_output.find(marker)
if marker_index:
if marker_index != -1:
end_stack = marker_index + len(marker)
break
if begin_stack is None or end_stack is None:
if end_stack is None:
logging.error(
b'Could not find an end stack marker (%s) in fuzzer output:\n%s',
STACKTRACE_END_MARKERS, fuzzer_output)
return
summary_str = fuzzer_output[begin_stack:end_stack]
if not summary_str:
return
# Write sections of fuzzer output to specific files.
with open(parsed_output_file_path, 'ab') as summary_handle:

View File

@ -14,7 +14,9 @@
"""Tests for stack_parser."""
import os
import unittest
from unittest import mock
import parameterized
from pyfakefs import fake_filesystem_unittest
import stack_parser
@ -33,33 +35,42 @@ class ParseOutputTest(fake_filesystem_unittest.TestCase):
def setUp(self):
self.setUpPyfakefs()
self.maxDiff = None # pylint: disable=invalid-name
def test_parse_valid_output(self):
@parameterized.parameterized.expand([('example_crash_fuzzer_output.txt',
'example_crash_fuzzer_bug_summary.txt'),
('msan_crash_fuzzer_output.txt',
'msan_crash_fuzzer_bug_summary.txt')])
def test_parse_valid_output(self, fuzzer_output_file, bug_summary_file):
"""Checks that the parse fuzzer output can correctly parse output."""
# Read the fuzzer output from disk.
fuzzer_output_path = os.path.join(TEST_FILES_PATH,
'example_crash_fuzzer_output.txt')
fuzzer_output_path = os.path.join(TEST_FILES_PATH, fuzzer_output_file)
self.fs.add_real_file(fuzzer_output_path)
with open(fuzzer_output_path, 'rb') as fuzzer_output_handle:
fuzzer_output = fuzzer_output_handle.read()
bug_summary_path = '/bug-summary.txt'
stack_parser.parse_fuzzer_output(fuzzer_output, bug_summary_path)
with mock.patch('logging.info') as mocked_info:
stack_parser.parse_fuzzer_output(fuzzer_output, bug_summary_path)
mocked_info.assert_not_called()
with open(bug_summary_path) as bug_summary_handle:
bug_summary = bug_summary_handle.read()
# Compare the bug to the expected one.
expected_bug_summary_path = os.path.join(TEST_FILES_PATH,
'bug_summary_example.txt')
expected_bug_summary_path = os.path.join(TEST_FILES_PATH, bug_summary_file)
self.fs.add_real_file(expected_bug_summary_path)
with open(expected_bug_summary_path) as expected_bug_summary_handle:
expected_bug_summary = expected_bug_summary_handle.read()
self.assertEqual(expected_bug_summary, bug_summary)
def test_parse_invalid_output(self):
"""Checks that no files are created when an invalid input was given."""
artifact_path = '/bug-summary.txt'
stack_parser.parse_fuzzer_output(b'not a valid output_string',
artifact_path)
with mock.patch('logging.error') as mocked_error:
stack_parser.parse_fuzzer_output(b'not a valid output_string',
artifact_path)
assert mocked_error.call_count
self.assertFalse(os.path.exists(artifact_path))

View File

@ -0,0 +1,22 @@
MemorySanitizer: use-of-uninitialized-value
#0 0x52675f in LLVMFuzzerTestOneInput /src/cifuzz-example/do_stuff_fuzzer.cpp:13:7
#1 0x45a431 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:599:15
#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3
#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3
#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6
#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#7 0x41eab8 in _start (out/do_stuff_fuzzer+0x41eab8)
DEDUP_TOKEN: LLVMFuzzerTestOneInput--fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&)
Uninitialized value was created by a heap allocation
#0 0x4d57ad in malloc /src/llvm-project/compiler-rt/lib/msan/msan_interceptors.cpp:901:3
#1 0x437c07 in operator new(unsigned long) (out/do_stuff_fuzzer+0x437c07)
#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3
#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3
#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6
#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
DEDUP_TOKEN: malloc--operator new(unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&)
SUMMARY:

View File

@ -0,0 +1,39 @@
Dictionary: 3 entries
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1337
INFO: Loaded 1 modules (184 inline 8-bit counters): 184 [0x829300, 0x8293b8),
INFO: Loaded 1 PC tables (184 PCs): 184 [0x5dc910,0x5dd490),
INFO: 5 files found in /tmp/do_stuff_fuzzer_corpus
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
==13==WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x52675f in LLVMFuzzerTestOneInput /src/cifuzz-example/do_stuff_fuzzer.cpp:13:7
#1 0x45a431 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:599:15
#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3
#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3
#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6
#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#7 0x41eab8 in _start (out/do_stuff_fuzzer+0x41eab8)
DEDUP_TOKEN: LLVMFuzzerTestOneInput--fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&)
Uninitialized value was created by a heap allocation
#0 0x4d57ad in malloc /src/llvm-project/compiler-rt/lib/msan/msan_interceptors.cpp:901:3
#1 0x437c07 in operator new(unsigned long) (out/do_stuff_fuzzer+0x437c07)
#2 0x45ba46 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:792:3
#3 0x45bed9 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:845:3
#4 0x44a4bc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:906:6
#5 0x474432 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#6 0x7eff5562683f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
DEDUP_TOKEN: malloc--operator new(unsigned long)--fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&)
SUMMARY: MemorySanitizer: use-of-uninitialized-value /src/cifuzz-example/do_stuff_fuzzer.cpp:13:7 in LLVMFuzzerTestOneInput
Unique heap origins: 65
Stack depot allocated bytes: 4424
Unique origin histories: 29
History depot allocated bytes: 696
Exiting
MS: 0 ; base unit: 0000000000000000000000000000000000000000
artifact_prefix='./'; Test unit written to ./crash-da39a3ee5e6b4b0d3255bfef95601890afd80709
Base64: