diff options
author | Neil Williams <codehelp@debian.org> | 2022-01-06 14:04:39 +0000 |
---|---|---|
committer | Neil Williams <codehelp@debian.org> | 2022-01-27 09:08:15 +0000 |
commit | 099786adeb2b043ac107ba73279a1f3473b32354 (patch) | |
tree | 0ce50ca2aa9985abde522f3456457c481cd267a0 /bin/update-vuln | |
parent | 7e554b137e4c078b9722318b272a2b9705cb4da9 (diff) |
Update grab-cve-in-fix for known examples
Support catching errors in the d.changelog
Add support for forcing a specific version
Fix typo in new support in bin/merge-cve-files
Update support in update-vuln to insert new
PackageAnnotations in specific order.
Diffstat (limited to 'bin/update-vuln')
-rwxr-xr-x | bin/update-vuln | 114 |
1 files changed, 101 insertions, 13 deletions
diff --git a/bin/update-vuln b/bin/update-vuln index e5847baa4b..fd3bd0ad5f 100755 --- a/bin/update-vuln +++ b/bin/update-vuln @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ update-vuln - #1001453 @@ -8,8 +8,22 @@ - add a bug number to an existing CVE entry - add a NOTE: entry to an existing CVE +Only make one change to one CVE at a time. Review and merge that +change and delete the merged file before updating the same CVE. + +The workflow would be: +./bin/update-vuln --cve CVE-YYYY-NNNNN ... +# on exit zero: +./bin/merge-cve-files ./CVE-YYYY-NNNNN.list +# review change to data/CVE/list +git diff data/CVE/list +rm ./CVE-YYYY-NNNNN.list +# .. repeat +git add data/CVE/list +git commit + """ -# Copyright 2021 Neil Williams <codehelp@debian.org> +# Copyright 2021-2022 Neil Williams <codehelp@debian.org> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,6 +42,7 @@ import os import argparse +import bisect import logging import sys @@ -52,6 +67,9 @@ class ParseUpdates: def __init__(self): self.cves = [] self.bugs = {} + self.marker = ( + "aaaaaaaaaaaaa" # replacement for NoneType to always sort first + ) self.logger = logging.getLogger("update-vuln") self.logger.setLevel(logging.DEBUG) # console logging @@ -62,6 +80,7 @@ class ParseUpdates: self.logger.addHandler(ch) def _read_cvelist(self): + """Build a list of Bug items for the CVE from data/CVE/list""" os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) data, _ = cvelist("data/CVE/list") for cve in self.cves: @@ -69,9 +88,52 @@ class ParseUpdates: if bug.header.name == cve: self.bugs[cve] = bug - def _add_annotation_after_line(self, cve, line, annotation): - bug_list = list(self.bugs[cve].annotations) - bug_list.append(annotation) + def _add_annotation_to_cve(self, cve, annotation): + """ + Adds an annotation to a CVE entry. + + StringAnnotation - appended to the end + PackageAnnotation - inserted in alphabetical order by release + + Accounts for PackageAnnotation.release == None for unstable. + """ + if isinstance(annotation, PackageAnnotation): + store = { + ann.release: ann + for ann in self.bugs[cve].annotations + if isinstance(ann, PackageAnnotation) + } + store[annotation.release] = annotation + # this is needed despite python3.7 having ordered dicts + # which would need a copied list anyway. + existing = [ + ann.release + for ann in self.bugs[cve].annotations + if isinstance(ann, PackageAnnotation) + ] + if None in existing: + # release == None for unstable + index = existing.index(None) + existing[index] = self.marker + insertion = annotation.release if annotation.release else self.marker + + # bisect cannot work with NoneType + bisect.insort(existing, insertion) + + if self.marker in existing: + index = existing.index(self.marker) + existing[index] = None + + bug_list = [] + for item in existing: + bug_list.append(store[item]) + + elif isinstance(annotation, StringAnnotation): + bug_list = list(self.bugs[cve].annotations) + bug_list.append(annotation) + else: + raise ValueError(f"Unsupported annotation type: {type(annotation)}") + return Bug(self.bugs[cve].file, self.bugs[cve].header, tuple(bug_list)) def _replace_annotation_on_line(self, cve, line, mod_line): @@ -86,7 +148,10 @@ class ParseUpdates: if not isinstance(modified, list): return if os.path.exists(cve_file): - self.logger.critical("%s already exists - appending", cve_file) + self.logger.critical( + "%s already exists - merge the update and remove the file first.", + cve_file, + ) return -1 mods = [] for cve in modified: @@ -96,7 +161,7 @@ class ParseUpdates: with open(cve_file, "a") as snippet: writecvelist(modified, snippet) - def mark_not_affected(self, suite, src): + def mark_not_affected(self, suite, src, description): """ Writes out a CVE file snippet with the filename: ./<cve>.list @@ -110,6 +175,18 @@ class ParseUpdates: modified = [] cve = self.cves[0] cve_file = f"{cve}.list" + existing = [ + line.release + for line in self.bugs[cve].annotations + if isinstance(line, PackageAnnotation) + ] + if suite not in existing: + # line type release package kind version description flags + line = PackageAnnotation( + 0, "package", suite, src, "not-affected", None, description, [] + ) + mod_bug = self._add_annotation_to_cve(cve, line) + modified.append(mod_bug) for line in self.bugs[cve].annotations: if not isinstance(line, PackageAnnotation): continue # skip notes etc. @@ -130,7 +207,11 @@ class ParseUpdates: self.logger.info("Removing version %s", line.version) ver_line = mod_line mod_line = ver_line._replace(version=None) - if mod_line.description: + if description: + self.logger.info("Replacing description %s", line.description) + desc_line = mod_line + mod_line = desc_line._replace(description=description) + elif mod_line.description: self.logger.info("Removing description %s", line.description) desc_line = mod_line mod_line = desc_line._replace(description=None) @@ -145,7 +226,7 @@ class ParseUpdates: ./<cve>.list Fails if the file already exists. """ - # use _add_annotation_after_line to add a line + # use _add_annotation_to_cve to add the note modified = [] cve = self.cves[0] cve_file = f"{cve}.list" @@ -163,7 +244,7 @@ class ParseUpdates: self.logger.info("Note already exists, ignoring") return new_note = StringAnnotation(line=0, type="NOTE", description=note) - mod_bug = self._add_annotation_after_line(cve, 0, new_note) + mod_bug = self._add_annotation_to_cve(cve, new_note) modified.append(mod_bug) self.write_modified(modified, cve_file) @@ -230,6 +311,7 @@ class ParseUpdates: old_pkg.description, new_flags, ) + bug_list = list(self.bugs[cve].annotations) others = [pkg for pkg in bug_list if pkg.line != old_pkg.line] bug_list = list(self.bugs[cve].annotations) # may need to retain the original order. @@ -267,13 +349,18 @@ def main(): required.add_argument("--cve", required=True, help="The CVE ID to update") affected = parser.add_argument_group( - "Marking a CVE as not-affected - must use --src and --suite" + "Marking a CVE as not-affected - must use --src and --suite " + "Optionally add a description or omit to remove the current description" ) # needs to specify the src_package as well as suite to cope with removed etc. affected.add_argument("--src", help="Source package name in SUITE") affected.add_argument( "--suite", default="unstable", help="Mark the CVE as <not-affected> in SUITE" ) + affected.add_argument( + "--description", + help="Optional description of why the SRC is unaffected in SUITE", + ) buggy = parser.add_argument_group("Add a bug number to the CVE") buggy.add_argument("--number", help="Debian BTS bug number") @@ -290,11 +377,12 @@ def main(): parser = ParseUpdates() parser.load_cve(args.cve) + logger = logging.getLogger("update-vuln") if not parser.bugs: - self.logger.critical("Unable to parse CVE ID %s", args.cve) + logger.critical("Unable to parse CVE ID %s", args.cve) return -1 if args.src and args.suite: - parser.mark_not_affected(args.suite, args.src) + parser.mark_not_affected(args.suite, args.src, args.description) if args.note: parser.add_note(args.note) if args.number: |