summaryrefslogtreecommitdiffstats
path: root/scripts/cvert-kernel
diff options
context:
space:
mode:
authorAndrii Bordunov via Openembedded-core <openembedded-core@lists.openembedded.org>2018-10-10 19:25:10 +0300
committerArmin Kuster <akuster808@gmail.com>2019-05-26 21:58:33 -0700
commit0fc645a94696d7643ac2bbce9f9682376b845ec6 (patch)
tree8d6d851f7f6592b448335f3f00151cb1438291e9 /scripts/cvert-kernel
parentfbc9b4607569520c92baf1352041c813606e8524 (diff)
downloadmeta-security-0fc645a94696d7643ac2bbce9f9682376b845ec6.tar.gz
cvert-kernel - generate CVE report for the Linux kernel
NVD entries for the Linux kernel are almost always outdated. For example, https://nvd.nist.gov/vuln/detail/CVE-2018-1065 is shown as matched for "versions up to (including) 4.15.7", however the patch 57ebd808a97d has been back ported for 4.14. By default, it checks NVD Resource entries for the patch URLs and looks for the commits in the local GIT tree. Additionaly ("--resource") it checks other resources, that may have up-to-date CVE data. You can combine resources and decide which one you want to be based on. Signed-off-by: grygorii tertychnyi <gtertych@cisco.com> Signed-off-by: Armin Kuster <akuster808@gmail.com>
Diffstat (limited to 'scripts/cvert-kernel')
-rwxr-xr-xscripts/cvert-kernel379
1 files changed, 379 insertions, 0 deletions
diff --git a/scripts/cvert-kernel b/scripts/cvert-kernel
new file mode 100755
index 0000000..adf2692
--- /dev/null
+++ b/scripts/cvert-kernel
@@ -0,0 +1,379 @@
1#!/usr/bin/env python3
2#
3# Copyright (c) 2018 by Cisco Systems, Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17#
18
19""" Generate CVE report for the given Linux kernel GIT branch
20"""
21
22import re
23import sys
24import argparse
25import textwrap
26import subprocess
27import logging
28import logging.config
29import cvert
30
31
32def report_kernel():
33 """Generate Linux kernel CVE report"""
34
35 parser = argparse.ArgumentParser(
36 formatter_class=argparse.RawDescriptionHelpFormatter,
37 description=textwrap.dedent("""
38 Generate CVE report for the Linux kernel.
39 Inspect Linux kernel GIT tree and find all CVE patches commits.
40 """),
41 epilog=textwrap.dedent("""
42 @ run examples:
43
44 # Download (update) NVD feeds in "nvdfeed" directory
45 # and prepare the report for the "kernel-sources" directory
46 %% %(prog)s --feed-dir nvdfeed --output report-kernel.txt kernel-sources
47
48 # Use existed NVD feeds in "nvdfeed" directory
49 # and prepare the report for the "kernel-sources" directory
50 %% %(prog)s --offline --feed-dir nvdfeed --output report-kernel.txt kernel-sources
51
52 # (faster) Restore CVE dump from "cvedump" (must exist)
53 # and prepare the report for the "kernel-sources" directory
54 %% %(prog)s --restore cvedump --output report-kernel.txt kernel-sources
55
56 # Restore CVE dump from "cvedump" (must exist)
57 # and prepare the extended report for the "kernel-sources" directory
58 %% %(prog)s --restore cvedump --show-description --show-reference --output report-kernel.txt kernel-sources
59
60 @ report example output (NVD resource):
61
62 # 2018-10-10 15:41:52,213 %% CVERT %% INFO %% kernel version: 4.9.132
63 . patched | 3.3 | CVE-2017-17807 | nvd: KEYS: add missing permission check for request_key() destination
64 unpatched | 3.3 | CVE-2017-17864 |
65 unpatched | 4.4 | CVE-2016-9604 |
66 unpatched | 4.4 | CVE-2017-12153 |
67 unpatched | 4.4 | CVE-2017-14051 |
68 . patched | 4.6 | CVE-2017-8924 | nvd: USB: serial: io_ti: fix information leak in completion handler
69 unpatched | 4.7 | CVE-2017-17449 |
70 . patched | 4.7 | CVE-2017-18203 | nvd: dm: fix race between dm_get_from_kobject() and __dm_destroy()
71 . patched | 4.7 | CVE-2017-18224 | nvd: ocfs2: ip_alloc_sem should be taken in ocfs2_get_block()
72 . patched | 4.7 | CVE-2018-1065 | nvd: netfilter: add back stackpointer size checks
73 ...
74
75 @ report example output (NVD+LKC resource):
76
77 # 2018-10-10 15:46:05,902 %% CVERT %% INFO %% kernel version: 4.9.132
78 . patched | 3.3 | CVE-2017-17807 | nvd: KEYS: add missing permission check for request_key() destination
79 unpatched | 3.3 | CVE-2017-17864 |
80 . patched | 4.4 | CVE-2016-9604 | lkc: a5c6e0a76817a3751f58d761aaff7c0b0c4001ff
81 . patched | 4.4 | CVE-2017-12153 | lkc: c820441a7a52e3626aede8df94069a50a9e4efdb
82 . patched | 4.4 | CVE-2017-14051 | lkc: 2a913aecc4f746ce15eb1bec98b134aff4190ae2
83 . patched | 4.6 | CVE-2017-8924 | nvd: USB: serial: io_ti: fix information leak in completion handler
84 . patched | 4.7 | CVE-2017-17449 | lkc: 0b18782288a2f1c2a25e85d2553c15ea83bb5802
85 . patched | 4.7 | CVE-2017-18203 | nvd: dm: fix race between dm_get_from_kobject() and __dm_destroy()
86 . patched | 4.7 | CVE-2017-18224 | nvd: ocfs2: ip_alloc_sem should be taken in ocfs2_get_block()
87 . patched | 4.7 | CVE-2018-1065 | nvd: netfilter: add back stackpointer size checks
88 ...
89 """))
90
91 group = parser.add_mutually_exclusive_group(required=True)
92 group.add_argument("-f", "--feed-dir", help="feeds directory")
93 group.add_argument("-d", "--restore", help="load CVE data structures from file",
94 metavar="FILENAME")
95 parser.add_argument("--offline", help="do not update from NVD site",
96 action="store_true")
97 parser.add_argument("-o", "--output", help="save report to the file")
98 parser.add_argument("-k", "--kernel-ver", help='overwrite kernel version, '
99 'default is "make kernelversion"',
100 metavar="VERSION")
101 parser.add_argument("--show-description", help='show "Description" in the report',
102 action="store_true")
103 parser.add_argument("--show-reference", help='show "Reference" in the report',
104 action="store_true")
105 parser.add_argument("-r", "--resource", help='resources: e.g.: "--resource nvd lkc"',
106 nargs="+", default=["nvd"])
107 parser.add_argument("--patched", help="patched CVE IDs",
108 nargs="+", default=[])
109 parser.add_argument("--debug", help="print debug messages",
110 action="store_true")
111
112 parser.add_argument("kernel_dir", help="kernel GIT directory",
113 metavar="kernel-dir")
114
115 args = parser.parse_args()
116
117 logging.config.dictConfig(cvert.logconfig(args.debug))
118
119 kernel_dir = args.kernel_dir
120
121 if args.restore:
122 cve_struct = cvert.load_cve(args.restore)
123 elif args.feed_dir:
124 cve_struct = cvert.update_feeds(args.feed_dir, args.offline)
125
126 if not cve_struct and args.offline:
127 parser.error("No CVEs found. Try to turn off offline mode or use other file to restore.")
128
129 if args.output:
130 output = open(args.output, "w")
131 else:
132 output = sys.stdout
133
134 if args.kernel_ver:
135 kernel_ver = args.kernel_ver
136 else:
137 kernel_ver = get_version(kernel_dir)
138
139 logging.info("kernel version: %s", kernel_ver)
140
141 if "lkc" in args.resource:
142 lkc_ctx = prepare_lkc()
143
144 commits = get_commits(kernel_dir)
145 report = cvert.generate_report({"linux_kernel": {kernel_ver: list(args.patched)}}, cve_struct)
146
147 for cve in report:
148 cve["comment"] = ""
149
150 if "nvd" in args.resource and cve["status"] == "unpatched":
151 process_nvd(cve, kernel_dir, commits)
152
153 if "lkc" in args.resource and cve["status"] == "unpatched":
154 process_lkc(cve, kernel_dir, commits, lkc_ctx)
155
156 print_report(report,
157 show_description=args.show_description,
158 show_reference=args.show_reference,
159 output=output)
160
161 if args.output:
162 output.close()
163
164
165def process_nvd(cve, kernel_dir, commits):
166 """Process NVD references.
167
168 Sometimes NVD "Reference" contains a link to the commit in the GIT
169 upstream mainline.
170
171 First, look if we have that commit ID in the current local GIT
172 branch. It happens if you created local branch after CVE has been
173 fixed in mainline.
174
175 Otherwise, don't give up, and try to find the same headline
176 commit. It works if local GIT tree has up-to-date mainline branch
177 fetched.
178
179 That is all we can do having only mainline commit ID.
180
181 """
182
183 for url in cve["reference"]:
184 iden = get_iden(url)
185
186 if iden and iden in commits["iden"]:
187 mark_patched(cve, "nvd: {0}".format(iden.decode()))
188 return
189
190 head = get_headline(kernel_dir, iden)
191
192 if head and head in commits["head"]:
193 mark_patched(cve, "nvd: {0}".format(head.decode()))
194 return
195
196
197def process_lkc(cve, kernel_dir, commits, ctx):
198 """Process Linux Kernel CVEs approach
199
200 [https://github.com/nluedtke/linux_kernel_cves]
201
202 "kernel" context contains mainline "fixes" commit ID. So, check
203 with that first.
204
205 "stream" context contains backported "cmd_id" for other upstream
206 branches. Check accordingly.
207
208 If no commit ID found in local current branch, than look for the
209 same headline commits. Rarely backported commit has different
210 headline, so look at the "cmt_msg" first.
211
212 """
213
214 iden_candidats = []
215
216 try:
217 iden = ctx["kernel"][cve["CVE"]]["fixes"].encode()
218 except KeyError:
219 logging.debug("%s: commit ID not found", cve["CVE"])
220 iden = None
221
222 if iden:
223 iden_candidats.append(iden)
224
225 if iden in commits["iden"]:
226 mark_patched(cve, "lkc: {0}".format(iden.decode()))
227 return
228
229 if cve["CVE"] in ctx["stream"]:
230 for kver in ctx["stream"][cve["CVE"]]:
231 try:
232 iden = ctx["stream"][cve["CVE"]][kver]["cmt_id"].encode()
233 except KeyError:
234 logging.debug("%s: commit ID not found", cve["CVE"])
235 iden = None
236
237 if iden:
238 iden_candidats.append(iden)
239
240 if iden in commits["iden"]:
241 mark_patched(cve, "lkc: {0}".format(iden.decode()))
242 return
243
244 try:
245 head = ctx["kernel"][cve["CVE"]]["cmt_msg"].encode()
246 except KeyError:
247 logging.debug("%s: commit message header not found", cve["CVE"])
248 head = None
249
250 if head and head in commits["head"]:
251 mark_patched(cve, "lkc: {0}".format(head.decode()))
252 return
253
254 # last chance
255 for iden in iden_candidats:
256 head = get_headline(kernel_dir, iden)
257
258 if head and head in commits["head"]:
259 mark_patched(cve, "lkc: {0}".format(head.decode()))
260 return
261
262
263def prepare_lkc():
264 """Prepare LKC context."""
265
266 import urllib.request
267 import json
268
269 ctx = {}
270 url_base = "https://github.com/nluedtke/linux_kernel_cves/raw/master"
271
272 with urllib.request.urlopen(url_base + "/kernel_cves.json") as url:
273 ctx["kernel"] = json.loads(url.read().decode())
274
275 with urllib.request.urlopen(url_base + "/stream_fixes.json") as url:
276 ctx["stream"] = json.loads(url.read().decode())
277
278 return ctx
279
280
281def mark_patched(cve, comment=""):
282 """Put a "patched" mark for the CVE."""
283
284 cve["status"] = "patched"
285 cve["comment"] = comment
286
287
288def get_version(kernel_dir):
289 """Return kernel version."""
290
291 return subprocess.check_output(
292 ["make", "kernelversion"],
293 cwd=kernel_dir,
294 stderr=subprocess.DEVNULL
295 ).decode().rstrip()
296
297
298def get_commits(kernel_dir):
299 """Return GIT commits dict."""
300
301 commits = {"iden": [], "head": []}
302
303 for gitlog in subprocess.check_output(["git", "log", "--format=%H %s"],
304 cwd=kernel_dir,
305 stderr=subprocess.DEVNULL
306 ).splitlines():
307 oneline = gitlog.split(maxsplit=1)
308
309 if len(oneline) > 1:
310 commits["head"].append(oneline[1])
311 else:
312 commits["head"].append(b"")
313
314 commits["iden"].append(oneline[0])
315
316 return commits
317
318
319def get_iden(url):
320 """Return kernel commit ID from URL."""
321
322 commit_re = [
323 r"^http://git\.kernel\.org/cgit/linux/kernel/git/torvalds/linux\.git/commit/\?id=(.+)$",
324 r"^https?://github\.com/torvalds/linux/commit/(.+)$"
325 ]
326
327 for regexp in commit_re:
328 matched = re.match(regexp, url)
329
330 if matched:
331 return matched.group(1)
332
333 return None
334
335
336def get_headline(kernel_dir, iden):
337 """Return commit headline."""
338
339 if not iden:
340 return None
341
342 try:
343 head = subprocess.check_output(["git", "show",
344 "--no-patch",
345 "--format=%s",
346 iden],
347 cwd=kernel_dir,
348 stderr=subprocess.DEVNULL
349 ).rstrip()
350 except subprocess.CalledProcessError:
351 logging.debug("%s: commit ID not found", iden)
352 head = None
353
354 return head
355
356
357def print_report(report, width=70, show_description=False, show_reference=False, output=sys.stdout):
358 """Output kernel report."""
359
360 for cve in report:
361 print("{0:>9s} | {1:>4s} | {2:18s} | {3}".format(cve["status"], cve["CVSS"],
362 cve["CVE"], cve["comment"]),
363 file=output)
364
365 if show_description:
366 print("{0:>9s} + {1}".format(" ", "Description"), file=output)
367
368 for lin in textwrap.wrap(cve["description"], width=width):
369 print("{0:>9s} {1}".format(" ", lin), file=output)
370
371 if show_reference:
372 print("{0:>9s} + {1}".format(" ", "Reference"), file=output)
373
374 for url in cve["reference"]:
375 print("{0:>9s} {1}".format(" ", url), file=output)
376
377
378if __name__ == "__main__":
379 report_kernel()