summaryrefslogtreecommitdiffstats
path: root/bin/merge-cve-files
blob: 7ce47545b5aba26a716ce0569696c6258a0a4d9b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/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 <pochu@debian.org>
# Copyright (c) 2021-2022 Neil Williams <codehelp@debian.org>

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(bug, notes):
    """
    Special support for StringAnnotations.

    Merges notes into the bug's annotations.

    notes is a list of extra string annotations for this CVE (bug),
    and may be empty.
    """
    new_notes = []
    if not notes:
        # nothing to merge
        return
    tagged_notes = [note.description for note in notes]
    bug_notes = [ann.description for ann in bug.annotations if isinstance(ann, StringAnnotation)]
    # get the list items in tagged_notes which are not in bug_notes
    new_strings = list(set(tagged_notes) - set(bug_notes))
    if not new_strings:
        return
    for new_ann in notes:
        if new_ann.description in new_strings:
            new_notes.append(new_ann)
    bug_list = list(bug.annotations)
    bug_list.extend(new_notes)

    bug.annotations = bug_list


def merge_annotations(annotations, new_annotation):
    if not isinstance(new_annotation, PackageAnnotation):
        raise NotImplementedError(f"unsupported annotation of type {new_annotation.type} (line {new_annotation.line})")

    annotations = list(annotations)

    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 annotations


    # 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

    return annotations

def parse_list(path):
    data, messages = cvelist(path)

    for m in messages:
        sys.stderr.write(str(m) + "\n")

    return data

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 = parse_list(main_list)
extra_data = parse_list(extra_list)

for extra_bug in extra_data:
    bug = next(bug for bug in data if bug.header.name == extra_bug.header.name)

    notes = []
    new_annotations = bug.annotations
    for extra_annotation in extra_bug.annotations:
        if isinstance(extra_annotation, FlagAnnotation):
            continue
        if isinstance(extra_annotation, StringAnnotation):
            notes.append(extra_annotation)
            continue

        new_annotations = merge_annotations(new_annotations, extra_annotation)

    bug.annotations = new_annotations
    merge_notes(bug, notes)

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)

© 2014-2024 Faster IT GmbH | imprint | privacy policy