-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathreporter.py
134 lines (113 loc) · 4.41 KB
/
reporter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# Copyright 2023 Google LLC
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
"""Reporter module for managing Vanir report data structures."""
import collections
import dataclasses
import functools
import itertools
from typing import Optional, Sequence, Union
from vanir import vulnerability_manager
from vanir.scanners import scanner_base
@dataclasses.dataclass(frozen=True)
class Report:
"""Dataclass to contain an individual finding to report.
Each report corresponds to a mapping of one signature and one matched chunk.
Attributes:
signature_id: unique ID of the matched signature.
signature_target_file: original target file of the signature.
signature_target_function: original target function of the signature.
signature_source: the source of the patch used to generate the signature.
unpatched_file: the file matched the signature in the target system.
unpatched_function_name: the function matched the signature in the target
system.
is_non_target_match: whether this matches against a signature's target
file, or match against other files in the scanned code.
"""
signature_id: str
signature_target_file: str
signature_target_function: str
signature_source: str
unpatched_file: str
unpatched_function_name: str
is_non_target_match: bool
def get_simple_report(
self,
include_patch_source: bool = False,
use_html_link_for_patch_source: bool = False,
) -> str:
"""Returns unpatched file and optionally unpatched function name."""
simple_report = self.unpatched_file
if self.unpatched_function_name:
simple_report += '::%s()' % self.unpatched_function_name
if include_patch_source:
if use_html_link_for_patch_source:
simple_report += ' (<a href="%s">patch</a>)' % self.signature_source
else:
simple_report += ' (patch:%s)' % self.signature_source
return simple_report
@dataclasses.dataclass(frozen=True)
class ReportGroup:
"""Dataclass for managing multiple reports grouped by a vulnerability ID."""
osv_id: str
cve_ids: Sequence[str]
reports: Sequence[Report]
class ReportBook:
"""Class for managing multiple report groups."""
def __init__(
self,
reports: Sequence[Report],
vul_manager: vulnerability_manager.VulnerabilityManager,
):
"""Generates a report book for the given reports."""
self._report_group_dict = {}
reports_per_vul = collections.defaultdict(list)
for report in reports:
osv_id = vul_manager.sign_id_to_osv_id(report.signature_id)
reports_per_vul[osv_id].append(report)
for osv_id, reports in reports_per_vul.items():
report_group = ReportGroup(
osv_id, vul_manager.osv_id_to_cve_ids(osv_id), reports
)
self._report_group_dict[osv_id] = report_group
@property
def unpatched_vulnerabilities(self) -> Sequence[Union[str, None]]:
"""Returns a list of OSV IDs of vulns reported as not patched."""
return list(self._report_group_dict.keys())
@functools.cached_property
def unpatched_cves(self) -> Sequence[str]:
"""Returns a list of CVEs reported as not patched."""
cves = itertools.chain.from_iterable(
[rgroup.cve_ids for rgroup in self._report_group_dict.values()]
)
return sorted(set(cves))
def get_report_group(self, osv_id: str) -> Optional[ReportGroup]:
"""Returns a report group mapped to |osv_id|.
Args:
osv_id: the OSV ID string.
Returns:
Returns a report group mapped to |osv_id| or None if none matches.
"""
return self._report_group_dict.get(osv_id)
def generate_reports(
findings: scanner_base.Findings
) -> Sequence[Report]:
"""A helper function to convert a Scanner's Findings to a list of Reports."""
reports = []
for sign, chunks in findings.items():
for chunk in chunks:
is_non_target_match = not chunk.target_file.endswith(sign.target_file)
reports.append(
Report(
signature_id=sign.signature_id,
signature_target_file=sign.target_file,
signature_target_function=getattr(sign, 'target_function', ''),
signature_source=sign.source,
unpatched_file=chunk.target_file,
unpatched_function_name=getattr(chunk.base, 'name', ''),
is_non_target_match=is_non_target_match,
)
)
return reports