summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrii Bordunov via Openembedded-core <openembedded-core@lists.openembedded.org>2018-10-10 19:25:11 +0300
committerArmin Kuster <akuster808@gmail.com>2019-05-26 21:58:33 -0700
commit7c08edc7f5d3a1e4c4552509b907baf9cadb279a (patch)
treeca19cd7c4e30fdea1785d5e1b7e8635cd8a2a7ad
parent0fc645a94696d7643ac2bbce9f9682376b845ec6 (diff)
downloadmeta-security-mut.tar.gz
cve-report.bbclass: add classmut
Implements "report_cve" and "report_patched" tasks. "report_patched" prepares image manifest with patched CVE info. "report_cve" runs cvert-* scripts to generate kernel and package CVE reports. You can configure it to set report filenames, reuse NVD feeds, stop after manifest generation and ignore specific classes, like native, nativesdk, etc. Signed-off-by: grygorii tertychnyi <gtertych@cisco.com> Signed-off-by: Armin Kuster <akuster808@gmail.com>
-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}