summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--classes/cve-report.bbclass216
1 files changed, 216 insertions, 0 deletions
diff --git a/classes/cve-report.bbclass b/classes/cve-report.bbclass
new file mode 100644
index 0000000..35d58d0
--- /dev/null
+++ b/classes/cve-report.bbclass
@@ -0,0 +1,216 @@
1# Class to inherit when you want to generate a CVE reports.
2#
3# Generates package list file and package CVE report.
4#
5# Example:
6# echo 'INHERIT += "cve-report"' >> conf/local.conf
7# bitbake -c report_cve core-image-minimal
8#
9# Variables to be passed to "cvert-*" scripts:
10#
11# CVE_REPORT_MODE[foss]
12# Path to the CVE FOSS report to be generated.
13#
14# CVE_REPORT_MODE[restore]
15# Path to the CVE dump data file.
16#
17# E.g. for multiple MACHINEs:
18# (1) generate CVE dump:
19# cvert-update --store /path/to/cvedump $TEMP/nvdfeed
20# (2) for mach in $(get_machine_list); do
21# (source oe-init-build-env "build-$mach";
22# echo 'CVE_REPORT_MODE[restore] = "/path/to/cvedump"' >> conf/local.conf;
23# echo 'CVE_REPORT_MODE[foss] = "/path/to/report-foss-'${mach}'"' >> conf/local.conf;
24# MACHINE=$mach bitbake -c report_cve core-image-minimal)
25# done
26#
27# CVE_REPORT_MODE[offline]
28# Either "0" or "1". Offline mode ("--offline" parameter for cvert-* scripts).
29#
30# CVE_REPORT_MODE[feeddir]
31# Path to the NVD feed directory.
32#
33# CVE_REPORT_MODE[packagelist]
34# Path to the package list file to be generated.
35#
36# CVE_REPORT_MODE[packageonly]
37# Either "0" or "1". Generate package list file, then stop.
38#
39# CVE_REPORT_MODE[blacklist]
40# Ignore specific class.
41#
42
43CVE_REPORT_MODE[foss] ?= "${LOG_DIR}/cvert/report-foss.txt"
44CVE_REPORT_MODE[offline] ?= "0"
45CVE_REPORT_MODE[feeddir] ?= "${LOG_DIR}/nvdfeeds"
46CVE_REPORT_MODE[packagelist] ?= "${LOG_DIR}/cvert/package.lst"
47CVE_REPORT_MODE[packageonly] ?= "0"
48CVE_REPORT_MODE[blacklist] ?= "native,nativesdk,cross,crosssdk,cross-canadian,packagegroup,image"
49
50CVE_PRODUCT ??= "${BPN}"
51CVE_VERSION ??= "${PV}"
52
53addhandler generate_report_handler
54generate_report_handler[eventmask] = "bb.event.BuildCompleted"
55
56def cvert_update(d):
57 """Update NVD storage and prepare CVE dump"""
58
59 import tempfile
60 import subprocess
61
62 bb.utils.export_proxies(d)
63
64 dump = os.path.join(d.getVar("LOG_DIR"), "cvedump")
65
66 bb.note("Updating CVE database: %s" % dump)
67
68 cmd = [
69 "cvert-update",
70 "--store", dump,
71 "--debug",
72 d.getVarFlag("CVE_REPORT_MODE", "feeddir")
73 ]
74
75 if d.getVarFlag("CVE_REPORT_MODE", "offline") != "0":
76 cmd.append("--offline")
77
78 try:
79 bb.debug(2, "Call '%s'" % " ".join(cmd))
80 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode()
81 bb.debug(2, "Output: %s" % output)
82 except subprocess.CalledProcessError as e:
83 bb.error("Failed to run cvert-update: '%s'\n%s: %s" % (" ".join(cmd), e, e.output))
84
85 return dump
86
87# copied from cve-check.bbclass
88def get_patches_cves(d):
89 """Get patches that solve CVEs using the "CVE: " tag"""
90
91 import re
92
93 pn = d.getVar("PN")
94 cve_match = re.compile("CVE:( CVE\-\d{4}\-\d+)+")
95
96 # Matches last CVE-1234-211432 in the file name, also if written
97 # with small letters. Not supporting multiple CVE id's in a single
98 # file name.
99 cve_file_name_match = re.compile(".*([Cc][Vv][Ee]\-\d{4}\-\d+)")
100
101 patched_cves = set()
102 bb.debug(2, "Looking for patches that solves CVEs for %s" % pn)
103 for url in src_patches(d):
104 patch_file = bb.fetch.decodeurl(url)[2]
105
106 # Check patch file name for CVE ID
107 fname_match = cve_file_name_match.search(patch_file)
108 if fname_match:
109 cve = fname_match.group(1).upper()
110 patched_cves.add(cve)
111 bb.debug(2, "Found CVE %s from patch file name %s" % (cve, patch_file))
112
113 with open(patch_file, "r", encoding="utf-8") as f:
114 try:
115 patch_text = f.read()
116 except UnicodeDecodeError:
117 bb.debug(1, "Failed to read patch %s using UTF-8 encoding"
118 " trying with iso8859-1" % patch_file)
119 f.close()
120 with open(patch_file, "r", encoding="iso8859-1") as f:
121 patch_text = f.read()
122
123 # Search for one or more "CVE: " lines
124 text_match = False
125 for match in cve_match.finditer(patch_text):
126 # Get only the CVEs without the "CVE: " tag
127 cves = patch_text[match.start()+5:match.end()]
128 for cve in cves.split():
129 bb.debug(2, "Patch %s solves %s" % (patch_file, cve))
130 patched_cves.add(cve)
131 text_match = True
132
133 if not fname_match and not text_match:
134 bb.debug(2, "Patch %s doesn't solve CVEs" % patch_file)
135
136 return patched_cves
137
138
139python generate_report_handler() {
140 if d.getVarFlag("CVE_REPORT_MODE", "packageonly") != "0":
141 return
142
143 import subprocess
144
145 restore = d.getVarFlag("CVE_REPORT_MODE", "restore")
146
147 if not restore:
148 restore = cvert_update(d)
149
150 if os.path.exists(d.getVarFlag("CVE_REPORT_MODE", "packagelist")):
151 report_foss = d.getVarFlag("CVE_REPORT_MODE", "foss")
152
153 bb.note("Generating CVE FOSS report: %s" % report_foss)
154
155 cmd = [
156 "cvert-foss",
157 "--restore", restore,
158 "--output", report_foss,
159 d.getVarFlag("CVE_REPORT_MODE", "packagelist")
160 ]
161
162 try:
163 bb.debug(2, "Call '%s'" % " ".join(cmd))
164 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode()
165 bb.debug(2, "Output: %s" % output)
166 except subprocess.CalledProcessError as e:
167 bb.error("Failed to run cvert-foss: '%s'\n%s: %s" % (" ".join(cmd), e, e.output))
168}
169
170addhandler build_started
171build_started[eventmask] = "bb.event.BuildStarted"
172
173python build_started() {
174 packagelist = d.getVarFlag("CVE_REPORT_MODE", "packagelist")
175 bb.utils.remove(packagelist)
176 bb.utils.mkdirhier(os.path.dirname(packagelist))
177 bb.note("Package list: ", packagelist)
178}
179
180addtask do_report_cve after do_report_patched
181
182do_report_cve[recrdeptask] = "do_report_cve do_report_patched"
183do_report_cve[recideptask] = "do_${BB_DEFAULT_TASK}"
184do_report_cve[nostamp] = "1"
185
186do_report_cve() {
187 :
188}
189
190python do_report_patched() {
191 if not d.getVar("SRC_URI"):
192 return
193
194 cve_product = d.getVar("CVE_PRODUCT")
195
196 if not cve_product:
197 return
198
199 cve_version = d.getVar("CVE_VERSION")
200 patched_cves = get_patches_cves(d)
201
202 with open(d.getVarFlag("CVE_REPORT_MODE", "packagelist"), "a") as fil:
203 fil.write("%s,%s,%s\n" % (cve_product, cve_version, " ".join(patched_cves)))
204 bb.debug(2, "Append to package-list: '%s,%s,%s'" % (cve_product, cve_version, " ".join(patched_cves)))
205}
206
207addtask do_report_patched after do_unpack before do_build
208
209do_report_patched[nostamp] = "1"
210
211python() {
212 for b in d.getVarFlag("CVE_REPORT_MODE", "blacklist").split(","):
213 if bb.data.inherits_class(b, d):
214 bb.build.deltask("do_report_patched", d)
215 break
216}