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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
# sectracker.analyzers -- vulnerability analysis
# Copyright (C) 2010 Florian Weimer <fw@deneb.enyo.de>
#
# 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 apt_pkg as _apt_pkg
import re as _re
from sectracker.xcollections import namedtuple as _namedtuple
# vercmp is the Debian version comparison algorithm
_apt_pkg.init()
try:
vercmp = _apt_pkg.version_compare
except AttributeError:
vercmp = _apt_pkg.VersionCompare
def mergelists(listfiles, diag):
"""Merge the (already parsed) list files in listfiles.
Returns a dictionary mapping bug names to bug tuples.
If duplicate bug names are encountered, an error is recorded
in diag."""
result = {}
for listfile in listfiles:
for bug in listfile.list:
header = bug.header
name = header.name
if name in result:
diag.error("duplicate bug %r" % name,
file=header.file, line=header.header.line)
diag.error("location of previous bug",
file=result[name].file, line=result[name].line)
continue
result[name] = bug
return result
def extractversions(config, bugdb, diag):
"""Extracts version information from list files.
Uses the repository configuration config to obtain a nested
dictionary, mapping release names to packages and sets of
versions. Then scans the bug database dictionary for additional
versions for those releases. If an unknown release is
encountered, an error message is added to diag."""
rpv = config.releasepackageversions()
for bug in bugdb.values():
for ann in bug.annotations:
if ann.type == "package" and ann.version is not None \
and ann.release is not None:
if ann.release not in rpv:
diag.error(file=bug.file, line=ann.line,
message="unknown release: %r" % ann.release)
else:
pv = rpv[ann.release]
if ann.package in pv:
pv[ann.package].add(ann.version)
else:
pv[ann.package] = set((ann.version,))
return rpv
_re_source = _re.compile("^DT?SA-")
def copysources(bugdb, diag):
"""Returns a dictionary, mapping bug names to their copy sources.
As a side effect, this checks cross-references. Errors found
there are recorded in diag."""
result = {}
for bug in bugdb.values():
copy_source = bug.header.name
if not _re_source.match(copy_source):
copy_source = None
for ann in bug.annotations:
if ann.type != "xref":
continue
for target in ann.bugs:
if target not in bugdb:
diag.error("reference to unknown bug %r" % target,
file=bug.file, line=ann.line)
continue
if copy_source is not None:
if target in result:
result[target].add(copy_source)
else:
result[target] = set((copy_source,))
return result
Vulnerability = _namedtuple("Vulnerability", "bug package fixed fixed_other")
def fixedversions(bugdb, copysrc, versions, diag):
"""Determine vulnerable versions.
Returns named tuples with fields "bug", "package", "fixed",
"fixed_other"."""
assert "sid" in versions # should come from extractversions()
def buildpackages1(bug, target=None):
packages = {}
xref = () # current {} contents
for ann in bug.annotations:
# only copy if target is listed in current {} list
if ann.type == "package" and (target is None or target in xref):
if ann.package not in packages:
packages[ann.package] = {}
pkg = packages[ann.package]
pkg[ann.release] = (bug, ann)
elif ann.type == "xref":
xref = ann.bugs
return packages
def buildpackages(bug):
packages = buildpackages1(bug)
if bug.header.name not in copysrc:
return packages
copiers = [buildpackages1(bugdb[b], target=bug.header.name)
for b in copysrc[bug.header.name]]
for c in copiers:
for pname, creleases in c.items():
if pname not in packages:
packages[pname] = creleases
continue
preleases = packages[pname]
for rel, cbugann in creleases.items():
if rel in preleases:
pbug, pann = preleases[rel]
cbug, cann = cbugann
if pbug is bug:
# Never override annotations in the CVE file.
continue
diag.warning("annotation on %s overridden"
% pbug.header.name,
file=pbug.file, line=pann.line)
diag.warning(" by annotation on %s via %s"
% (cbug.header.name, bug.header.name),
file=cbug.file, line=cann.line)
preleases[rel] = cbugann
return packages
def latentlyvulnerable(packages):
for pname, preleases in packages.items():
if None not in preleases:
diag.warning("package %s is latently vulnerable in unstable"
% pname,
file=bug.file, line=bug.header.line)
for (pbug, pann) in preleases.values():
diag.warning("%s vulnerability in %s"
% (pname, pann.release),
file=pbug.file, line=pann.line)
def convertversion(ann):
# None: unfixed
# version-string: fixed in that version
# True: never vulnerable
if ann.urgency == "unimportant" or ann.kind == "not-affected":
return True
ver = ann.version
if ver is not None:
return ver
return None
def extractunstable(preleases):
if None not in preleases:
return None
return convertversion(preleases[None][1])
def getversions(pname, version_items=versions.items()):
# FIXME: extractversions() should return flipped nested
# dictionary, to make the following easier.
for rel, pkgs in version_items:
if rel == "sid":
continue
if pname in pkgs:
for ver in pkgs[pname]:
yield rel, ver
result = []
for bug in bugdb.values():
if _re_source.match(bug.header.name):
# Copy sources are dealt with by copying their
# annotations.
continue
packages = buildpackages(bug)
latentlyvulnerable(packages)
for pname, preleases in packages.items():
unstable_fixed = extractunstable(preleases)
if unstable_fixed is True:
# unstable was never vulnerable, which overrides
# all other annoations
continue
other_versions = set()
for rel, ver in getversions(pname):
if unstable_fixed is not None \
and vercmp(ver, unstable_fixed) >= 0:
# This version is already covered by the
# unstable fix.
continue
if rel in preleases:
refver = convertversion(preleases[rel][1])
if refver is None:
continue
if refver is True:
# Annotations like <not-affected>.
other_versions.add(ver)
continue
if vercmp(ver, refver) >= 0:
other_versions.add(ver)
result.append(Vulnerability(bug.header.name, pname,
unstable_fixed, other_versions))
return result
def bestversion(config, codename, pkg, requested_members=None):
"""Returns the source package with the highest version among the
requested sub-repository members."""
members = config.distributions[codename]["members"]
fm = config.filemap()
bestver = None
bestpkg = None
for name, mems in members.items():
if requested_members is None or name in requested_members:
for mem in mems:
for comp in fm[mem].values():
if pkg in comp:
curpkg = comp[pkg]
curver = curpkg.version
if bestver is None or vercmp(curver, bestver) > 0:
bestver = curver
bestpkg = curpkg
return bestpkg
|