#!/usr/bin/env python # -*- coding: utf-8 -*- """ update-vuln - #1001453 - mark a given released suite (stable/oldstable/LTS) as for a specific CVE ID - add a bug number to an existing CVE entry - add a NOTE: entry to an existing CVE """ # Copyright 2021 Neil Williams # # 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 # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. import os import argparse import sys import setup_paths # noqa # pylint: disable=unused-import from sectracker.parsers import ( sourcepackages, PackageAnnotation, StringAnnotation, Bug, cvelist, writecvelist, ) class ParseUpdates: """ Update a CVE with requested changes and produce a file for manual review and use with merge-cve-files. """ def __init__(self): self.cves = [] self.bugs = {} def _read_cvelist(self): os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) data, _ = cvelist("data/CVE/list") for cve in self.cves: for bug in data: 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) mod_bug = Bug(self.bugs[cve].file, self.bugs[cve].header, tuple(bug_list)) return mod_bug 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 def write_modified(self, modified, cve_file): if not modified: return if not isinstance(modified, list): return if os.path.exists(cve_file): raise OSError("%s already exists" % cve_file) mods = [] for cve in modified: print(f"Writing to ./{cve_file} with update for {cve.header.name}.") with open(cve_file, "a") as snippet: writecvelist(modified, snippet) def mark_not_affected(self, suite, src): """ Writes out a CVE file snippet with the filename: ./.list Fails if the file already exists. """ release = suite if suite == "unstable" or suite == "sid": # special handling for unstable suite = None release = "unstable" modified = [] cve = self.cves[0] cve_file = f"{cve}.list" for line in self.bugs[cve].annotations: if not isinstance(line, PackageAnnotation): continue # skip notes etc. if line.release != suite: continue if line.package != src: continue # 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}.") return mod_line = line._replace(kind="not-affected") print(f"Modified {cve} for {src} in {release} to ") if mod_line.version: print(f"Removing version {line.version}") ver_line = mod_line mod_line = ver_line._replace(version=None) if mod_line.description: print(f"Removing description {line.description}") desc_line = mod_line mod_line = desc_line._replace(description=None) # removing a bug annotation is not covered, yet. mod_bug = self._replace_annotation_on_line(cve, line, mod_line) modified.append(mod_bug) self.write_modified(modified, cve_file) def add_note(self, note): """ Writes out a CVE file snippet with the filename: ./.list Fails if the file already exists. """ # use _add_annotation_after_line to add a line pass def add_bug_number(self, bug): """ 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 def load_cve(self, cve): print(f"Loading data for {cve}...") self.cves.append(cve) self._read_cvelist() def main(): parser = argparse.ArgumentParser( description="Update a 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'", ) 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") # 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") 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) return 0 if __name__ == "__main__": sys.exit(main())