#!/usr/bin/python3 # # Merge a separate CVE file (such as data/next-point-update.txt) back into # the main one. # # Copyright © 2020-2023 Emilio Pozuelo Monfort # Copyright (c) 2021-2022 Neil Williams import os import sys import setup_paths # noqa from debian_support import internRelease from sectracker.parsers import ( Bug, cvelist, writecvelist, PackageAnnotation, FlagAnnotation, StringAnnotation, XrefAnnotation ) def merge_notes(annotations, new_annotation): """ Special support for StringAnnotations. Merges a note into the bug's annotations, taking care not to add duplicate notes. new_annotation is a new string annotation for this CVE (bug), """ old_descriptions = [ann.description for ann in annotations if isinstance(ann, StringAnnotation)] # prevent adding duplicate notes if not new_annotation.description in old_descriptions: annotations.append(new_annotation) def merge_annotations(annotations, new_annotation): """ Adds new_annotation to the annotations list """ if not isinstance(new_annotation, PackageAnnotation): raise NotImplementedError(f"unsupported annotation of type {new_annotation.type} (line {new_annotation.line})") annotations_for_pkg = [ann for ann in annotations \ if isinstance(ann, PackageAnnotation) \ and ann.package == new_annotation.package] if not annotations_for_pkg: if new_annotation.release: raise ValueError(f"new annotation for {new_annotation.package}/{new_annotation.release} " "but there is no annotation for sid") # new package, add it at the top for idx, annotation in enumerate(annotations): if isinstance(annotation, FlagAnnotation) \ or isinstance(annotation, XrefAnnotation): continue annotations.insert(idx, new_annotation) return # append/substitute the new one at the right place for idx, annotation in enumerate(annotations): if not isinstance(annotation, PackageAnnotation) \ or annotation.package != new_annotation.package: continue # if the annotation is for the same package/release, replace it if annotation.package == new_annotation.package \ and annotation.release == new_annotation.release: annotations[idx] = new_annotation break # if we found an experimental annotation, it will be followed by a 'sid' # one, so next_annotation.release will be None in the next case. That # comparison will break, so we avoid it by continuing. If new_annotation # was for experimental, we would have already replaced it in the above check. if annotation.release == 'experimental': continue # if the next annotation's release is the same, we continue to replace # it in the next iteration. otherwise if we found the right place, we # insert the new annotation next_annotation = annotations[idx + 1] if len(annotations) > (idx + 1) else None if next_annotation and isinstance(next_annotation, PackageAnnotation) \ and next_annotation.package == new_annotation.package \ and internRelease(new_annotation.release) <= internRelease(next_annotation.release): continue annotations.insert(idx + 1, new_annotation) break if len(sys.argv) not in (2, 3): print(f"Usage: {os.path.basename(sys.argv[0])} (CVE/list) extra-cve-list") sys.exit(1) if len(sys.argv) == 3: main_list = sys.argv[1] else: main_list = os.path.dirname(__file__) + '/../data/CVE/list' extra_list = sys.argv[-1] data = cvelist(main_list) extra_data = cvelist(extra_list) for extra_bug in extra_data: bug = next(bug for bug in data if bug.header.name == extra_bug.header.name) for extra_annotation in extra_bug.annotations: if isinstance(extra_annotation, FlagAnnotation): continue if isinstance(extra_annotation, StringAnnotation): merge_notes(bug.annotations, extra_annotation) continue merge_annotations(bug.annotations, extra_annotation) with open(main_list, 'w') as f: writecvelist(data, f) # check for and erase an .xpck file built from the merge xpck = f"{extra_list}.xpck" if os.path.exists(xpck): os.unlink(xpck)