From 8cbd0b858ff3c68c949187eaa66a1df960425471 Mon Sep 17 00:00:00 2001 From: Neil Williams Date: Mon, 20 Dec 2021 12:39:20 +0000 Subject: Add remaining support and switch to using logging Add support to add a bug number. Add warnings in --help that each update must be merged before the same CVE can be updated again. --- bin/update-vuln | 167 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 21 deletions(-) diff --git a/bin/update-vuln b/bin/update-vuln index 9e3d710b5a..e5847baa4b 100755 --- a/bin/update-vuln +++ b/bin/update-vuln @@ -28,12 +28,14 @@ import os import argparse +import logging import sys import setup_paths # noqa # pylint: disable=unused-import from sectracker.parsers import ( sourcepackages, PackageAnnotation, + PackageBugAnnotation, StringAnnotation, Bug, cvelist, @@ -50,6 +52,14 @@ class ParseUpdates: def __init__(self): self.cves = [] self.bugs = {} + self.logger = logging.getLogger("update-vuln") + self.logger.setLevel(logging.DEBUG) + # console logging + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s") + ch.setFormatter(formatter) + self.logger.addHandler(ch) def _read_cvelist(self): os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) @@ -62,15 +72,13 @@ class ParseUpdates: def _add_annotation_after_line(self, cve, line, annotation): bug_list = list(self.bugs[cve].annotations) bug_list.append(annotation) - mod_bug = Bug(self.bugs[cve].file, self.bugs[cve].header, tuple(bug_list)) - return mod_bug + return Bug(self.bugs[cve].file, self.bugs[cve].header, tuple(bug_list)) def _replace_annotation_on_line(self, cve, line, mod_line): index = self.bugs[cve].annotations.index(line) bug_list = list(self.bugs[cve].annotations) bug_list[index] = mod_line - mod_bug = Bug(self.bugs[cve].file, self.bugs[cve].header, tuple(bug_list)) - return mod_bug + return Bug(self.bugs[cve].file, self.bugs[cve].header, tuple(bug_list)) def write_modified(self, modified, cve_file): if not modified: @@ -78,10 +86,13 @@ class ParseUpdates: if not isinstance(modified, list): return if os.path.exists(cve_file): - raise OSError("%s already exists" % cve_file) + self.logger.critical("%s already exists - appending", cve_file) + return -1 mods = [] for cve in modified: - print(f"Writing to ./{cve_file} with update for {cve.header.name}.") + self.logger.info( + "Writing to ./%s with update for %s", cve_file, cve.header.name + ) with open(cve_file, "a") as snippet: writecvelist(modified, snippet) @@ -109,16 +120,18 @@ class ParseUpdates: # need to define the allowed changes # if fixed, version would need to be undone too. if line.kind == "not-affected": - print(f"Nothing to do for {cve} in {suite}.") + self.logger.info("Nothing to do for %s in %s.", cve, suite) return mod_line = line._replace(kind="not-affected") - print(f"Modified {cve} for {src} in {release} to ") + self.logger.info( + "Modified %s for %s in %s to ", cve, src, release + ) if mod_line.version: - print(f"Removing version {line.version}") + self.logger.info("Removing version %s", line.version) ver_line = mod_line mod_line = ver_line._replace(version=None) if mod_line.description: - print(f"Removing description {line.description}") + self.logger.info("Removing description %s", line.description) desc_line = mod_line mod_line = desc_line._replace(description=None) # removing a bug annotation is not covered, yet. @@ -133,48 +146,160 @@ class ParseUpdates: Fails if the file already exists. """ # use _add_annotation_after_line to add a line - pass + modified = [] + cve = self.cves[0] + cve_file = f"{cve}.list" + existing = [ + note.description + for note in self.bugs[cve].annotations + if isinstance(note, StringAnnotation) + ] + lines = [ + note.line + for note in self.bugs[cve].annotations + if isinstance(note, StringAnnotation) + ] + if note in existing: + 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) + modified.append(mod_bug) + self.write_modified(modified, cve_file) - def add_bug_number(self, bug): + def add_bug_number(self, bug, itp=False): """ Writes out a CVE file snippet with the filename: ./.list Fails if the file already exists. """ - # need to work out how to manipulate releases - pass + # bugs only apply to unstable (or itp) + modified = [] + cve = self.cves[0] + cve_file = f"{cve}.list" + existing = [ + pkg.flags + for pkg in self.bugs[cve].annotations + if isinstance(pkg, PackageAnnotation) + if not pkg.release and pkg.kind != "removed" + ] + bugs = [bug for sublist in existing for bug in sublist] + if bugs: + self.logger.warning( + "%s already has a bug annotation for unstable: %s", cve, bugs[0].bug + ) + return -1 + pkgs = [ + pkg + for pkg in self.bugs[cve].annotations + if isinstance(pkg, PackageAnnotation) + if not pkg.release and pkg.kind != "removed" + ] + if itp: + # no useful entry will exist in pkgs + new_flags = [PackageBugAnnotation(bug)] + new_pkg = PackageAnnotation( + 0, + "package", + None, + itp, + "itp", + None, + None, + new_flags, + ) + others = [] + else: + if not pkgs: + self.logger.error("%s does not have a package annotation.", cve) + return -1 + old_pkg = pkgs[0] + if itp and old_pkg.kind == "fixed": + self.logger.error( + "%s is already marked as but --itp flag was set.", cve + ) + return -3 + new_flags = [PackageBugAnnotation(bug)] + new_pkg = PackageAnnotation( + old_pkg.line, + old_pkg.type, + old_pkg.release, + old_pkg.package, + old_pkg.kind, + old_pkg.version, + old_pkg.description, + new_flags, + ) + 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. + new_list = [new_pkg] + others + mod_bug = Bug(self.bugs[cve].file, self.bugs[cve].header, tuple(new_list)) + modified.append(mod_bug) + self.write_modified(modified, cve_file) def load_cve(self, cve): - print(f"Loading data for {cve}...") + self.logger.info("Loading data for %s...", cve) self.cves.append(cve) self._read_cvelist() def main(): + """ + This script does NOT reparse the output file - create, review and + merge ONE update at a time. + (For some operations, check-new-issues may be more suitable). + + For example, --bug 100 --itp intended_pkg_name + then, merge-cve-list, then: + --note "URL:" + """ parser = argparse.ArgumentParser( - description="Update a specified CVE data as not-affected, add bug number or add a note", + description="Make a single update to specified CVE data as " + "not-affected, add bug number or add a note", epilog="Data is written to a new .list " - "file which can be used with './bin/merge-cve-files'", + "file which can be used with './bin/merge-cve-files'. " + "Make sure the output file is merged and removed before " + "updating the same CVE again.", ) + required = parser.add_argument_group("Required arguments") required.add_argument("--cve", required=True, help="The CVE ID to update") - affected = parser.add_argument_group("Marking a CVE as not-affected") + + affected = parser.add_argument_group( + "Marking a CVE as not-affected - must use --src and --suite" + ) # 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 in SUITE" ) + buggy = parser.add_argument_group("Add a bug number to the CVE") buggy.add_argument("--number", help="Debian BTS bug number") + buggy.add_argument( + "--itp", + metavar="SRC", + help="Mark as an ITP bug for the specified source package name", + ) + notes = parser.add_argument_group("Add a NOTE: entry to the CVE") notes.add_argument("--note", help="Content of the NOTE: entry to add to the CVE") + args = parser.parse_args() parser = ParseUpdates() parser.load_cve(args.cve) + if not parser.bugs: - raise ValueError("Unable to parse CVE ID %s" % args.cve) - # print(parser.bugs[args.cve].header.description) - parser.mark_not_affected(args.suite, args.src) + self.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) + if args.note: + parser.add_note(args.note) + if args.number: + # to set itp properly, the source package name also needs to be set. + parser.add_bug_number(args.number, args.itp) return 0 -- cgit v1.2.3