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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
|
#!/usr/bin/python
# Copyright 1999-2007 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# Written by Robert Buchholz <rbu@gentoo.org>
import string
import sys
import os
import portage
import portage_versions
import re
genpatcheslist="./output/genpversions.txt"
"""
Bugzilla Kernel Version specification
The whiteboard field on the bug should be used to specify the vulnerable
versions of all kernel sources for this bug. A bug can affect a package in three
ways (and can therefore be fixed in three ways):
(1) by affecting the kernel.org release ("linux"),
(2) by affecting a certian set of Gentoo Patchsets ("gp")
(3) by affecting a specific set of Gentoo kernel sources ("*-sources").
The priorities of these levels override each other with 3 having the highest
priority (2 second and 1 lowest). Note that priority does not mean severity of
the bug. Rather, the priority level is a scale of generality with 1 having the
highest generality. A whiteboard entry of the type [linux] affects all kernels
based off that version until a higher priority entry is added.
Higher levels (2, 3) should normally only mark unaffected versions that are
affected in lower levels. To override this and expand the "affected" interval
over the boundaries giving by lower levels, version specifiers should be
prefixed with a "+".
Intervals specify the affected versions and can, for each level, be specified
open (with upper or lower boundary only), or closed, either inclusive or not.
Spaces are discarded.
The order in which interval are specified is irrelevant.
Examples:
[linux > 2.6] -- means all Linux releases since 2.6 are affected
[linux < 2.6.24.3] -- means all Linux versions prior to 2.6.24.3 are affected.
[linux >= 2.6.24 < 2.6.24.3] -- means all Linux versions greater than, and
including, 2.6.24, except if they are equal or greater than .3
Complex examples:
[linux >= 2.6.18 < 2.6.24.3] [gp < 2.6.23-8]
This means: affected is every kernel based on a linux release higher/equal than
2.6.18, but not those based on 2.6.24.3 or later. Kernels using a genpatches
version 2.6.23-8 or later are also not affected. 2.6.17 or earlier kernels
using genpatches are not affected.
[linux >= 2.6.18 < 2.6.24.3] [gp +< 2.6.23-8]
Same as before, except even 2.6.17 and earlier genpatched kernerls are also
affected (because of the +).
[linux >= 2.6.18 < 2.6.24.3] [gp >= 2.6.15 +<= 2.6.23-8]
Similar to the previous example, except kernels using genpatches are
affected from versions 2.6.15 (inclusive) up to 2.6.23-8 (inclusive).
[linux >= 2.6.18] [gp >= 2.6.23 < 2.6.23-8] [gp < 2.6.22-10]
All Linuxes since 2.6.18, unaffected are all Genpatched kernels between
2.6.22-10 and (not including) 2.6.23, plus those after 2.6.23-8.
[linux >= 2.6.18 < 2.6.24.3] [gp < 2.6.23-8] [xen < 2.6.18-r9] [xen >= 2.6.19]
Same as the first example, except the 2.6.18 series of xen-kernels was fixed in 2.6.18-r9.
"""
class BugError(Exception):
def __init__(self, message, bug = None):
self.message = message
self.bug = bug
def __str__(self):
return repr(self.message)
class SourcesError(Exception):
def __init__(self, message, ebuild):
self.message = message
self.ebuild = ebuild
def __str__(self):
return repr(self.message)
class KernelAtom:
def __init__(self, name, version, release, gpver = None, has_base = None, has_extra = None, keywords = ""):
self.name = name
self.version = version
self.release = release
self.gpver = gpver
self.has_base = has_base
self.has_extra = has_extra
self.keywords = keywords
self.affected_by = []
def __repr__(self):
if self.gpver:
return "%s-%s,\tRelease: %s,\tGenpatches: %s\t(Base: %s,\tExtra: %s) " % (self.name, self.version, self.release, self.gpver, self.has_base, self.has_extra)
else:
return "%s-%s,\tRelease: %s,\tGenpatches: No" % (self.name, self.version, self.release)
class SecurityMatrix:
def __init__(self):
self.localtree = portage.portagetree()
self.fill_genpatches()
self.kernel_atoms = []
self.fill_kernel_atoms()
supported_kernels=( "gentoo-sources",
"hardened-sources",
"tuxonice-sources",
"openvz-sources",
"usermode-sources",
"xen-sources",
"cell-sources",
"hppa-sources",
"mips-sources",
"rsbac-sources",
"sparc-sources")
# "vserver-sources",
unsupported_kernels=("git-sources",
"mm-sources",
"sh-sources",
"xbox-sources",
"vanilla-sources")
def fill_genpatches(self):
""" Read the genpatch'd kernels from a file and prepare KernelAtom objects for them. """
file = open(genpatcheslist)
kernelatoms = {}
for line in file:
splitline = line.strip().split(':')
if len(splitline) != 4:
print "Error reading line: %s" % (line)
else:
name = splitline[0]
version = splitline[1]
release = release_for_version(splitline[1])
gpver = splitline[2]
has_base = splitline[3].find("base") != -1
has_extra = splitline[3].find("extra") != -1
cpv = "sys-kernel/%s-%s" % (name, version)
atom = KernelAtom(name, version, release, gpver, has_base, has_extra, self.get_keywords_for(cpv))
kernelatoms[cpv] = atom
file.close()
self.genpatches = kernelatoms
def fill_kernel_atoms(self):
""" Fills the kernel atoms list will all kernel CPV's in the tree """
for source in self.supported_kernels:
cp = "sys-kernel/%s" % (source)
all_cpvs = self.localtree.dbapi.cp_list(cp)
for cpv in all_cpvs:
self.build_and_add_kernelatom(cpv)
for source in self.unsupported_kernels:
cp = "sys-kernel/%s" % (source)
all_cpvs = self.localtree.dbapi.cp_list(cp)
for cpv in all_cpvs:
self.build_and_add_kernelatom(cpv)
def build_and_add_kernelatom(self, cpv):
""" Build a KernelAtom object with the given cpv string and add it to our list of atoms """
if self.genpatches.has_key(cpv):
self.kernel_atoms.append(self.genpatches[cpv])
else:
cpvr = portage_versions.catpkgsplit(cpv)
if len(cpvr) != 4:
return
v = cpvr[2]
if cpvr[3] != "r0":
v = "%s-%s" % (cpvr[2], cpvr[3])
cpv = "%s/%s-%s" % (cpvr[0], cpvr[1], v)
new_atom = KernelAtom(cpvr[1], v, release_for_version(cpvr[2]), keywords = self.get_keywords_for(cpv))
self.kernel_atoms.append(new_atom)
def get_keywords_for(self, cpv):
try:
result = self.localtree.dbapi.aux_get(cpv, ("KEYWORDS",))
if result != None and len(result) == 1:
return result[0]
except:
pass
#TODO: raise SourcesError here
return ""
def check_bug(self, bug):
if len(bug.affected) == 0:
raise BugError("No intervals for affected kernel versions were found.", bug)
for ka in self.kernel_atoms:
if bug.affects(ka):
ka.affected_by.append(bug)
def release_for_version(version):
""" Given an ebuild version, gives the Kernel release it is probably based upon.
Examples: 2.6.23-r3 -> 2.6.23
2.6.23.12 -> 2.6.23.12
2.6.23 -> 2.6.23
2.6.25_rc5 -> 2.6.25_rc5
moo -> None """
matcher = re.compile("(\d\.\d+\.\d+(:?\.\d+)?(:?_rc\d+)?)")
match = matcher.match(version)
if not match:
# TODO: raise SourcesError in caller
return None
else:
return match.group(1)
class IntervalEntry:
""" Defines """
def __init__(self, name, lower_inclusive, upper_inclusive, lower, upper, expand, bug):
if name == "gp":
name = "genpatches"
elif name != "linux" and name != "genpatches" and name[-7:] != "sources":
name = "%s-sources" % (name)
self.name = name # string, describing the package
self.lower_inclusive = lower_inclusive # Defines whether the lower boundary is inclusive
self.upper_inclusive = upper_inclusive # Defines whether the upper boundary is inclusive
if name == "genpatches":
self.lower = dashdot(lower) # Lower boundary
self.upper = dashdot(upper) # Upper boundary
else:
self.lower = lower # Lower boundary
self.upper = upper # Upper boundary
self.expand = expand # Defines whether the entry is expanding "lower" entries
self.bug = bug
def __repr__(self):
val = "%s " % (self.name)
if self.expand:
val += "+"
if self.lower and self.lower_inclusive:
val += ">=%s " % (self.lower)
if self.lower and not self.lower_inclusive:
val += ">%s " % (self.lower)
if self.upper and self.upper_inclusive:
val += "<=%s" % (self.upper)
if self.upper and not self.upper_inclusive:
val += "<%s" % (self.upper)
return val
def is_in_interval(self, version):
""" Returns True if the given version is inside our specified interval, False otherwise.
Note: 'name' is discarded in the comparison. """
if version == None:
return True # TODO: why?
if self.lower: # We actually have a lower boundary set
result = portage_versions.vercmp(version, self.lower)
if result == None:
raise BugError("Could not compare %s and %s, on %s" % (self.lower, version, str(self)), self.bug)
"""" We check the lower boundary. Two things will lead to False:
(1) The Result is "equal" and the lower boundary is not inclusive
aka: version = 2.6.24 on "> 2.6.24"
(2) The Result is "lower":
aka: version = 2.6.18 on ">= 2.6.24" """
if result == 0 and not self.lower_inclusive:
return False
if result == 0 and self.lower_inclusive:
return True
if result < 0:
return False
if self.upper: # We actually have an upper boundary set
result = portage_versions.vercmp(version, self.upper)
if result == None:
raise BugError("Could not compare %s and %s, on %s" % (self.upper, version, str(self)), self.bug)
"""" We check the upper boundary. Two things will lead to False:
(1) The Result is "equal" and the upper boundary is not inclusive
aka: version = 2.6.24 on "< 2.6.24"
(2) The Result is "lower":
aka: version = 2.6.24 on "<= 2.6.18" """
if result == 0 and not self.upper_inclusive:
return False
if result == 0 and self.upper_inclusive:
return True
if result > 0:
return False
# Seems we're outa luck, we fell into the vulnerable versions
return True
class Bug:
def __init__(self, bugno, title = "", severity = "normal", affected = ()):
self.bugno = bugno
self.title = title
self.severity = severity
self.affected = affected #(Entry("linux", "<", "2.6.23"),Entry("gp", "<", "2.6.20-14"),Entry("hardened", ">", "2.6"))
def affects(self, kernelatom):
""" Returns True if this bug affects the given KernelAtom, False otherwise. """
affected = False
linux_empty = True
for entry in self.affected:
if entry.name == "linux":
linux_empty = False
# Our linux base version is affected if it falls into any of the intervals
affected = affected or entry.is_in_interval(dashdot(kernelatom.release))
if kernelatom.gpver:
genpatches_affected = False
genpatches_exist = False
for entry in self.affected:
if entry.name == "gp" or entry.name == "genpatches":
genpatches_exist = True
if entry.is_in_interval(dashdot(kernelatom.gpver)):
# Our genpatches version is within the affected Genpatches.
genpatches_affected = True
if linux_empty:
affected = True
#elif affected:
# affected = True
elif not affected and entry.expand:
affected = True
#elif not affected and not entry.expand:
# affected = False
if affected and genpatches_exist and not genpatches_affected:
# We went through all the genpatches entries, but none marked this affected
affected = False
entry_affected = False
entry_exist = False
for entry in self.affected:
if entry.name == kernelatom.name:
entry_exist = True
if entry.is_in_interval(kernelatom.version):
# Our entry version is within the affected entry range.
entry_affected = True
if linux_empty:
affected = True
#elif affected:
# affected = True
elif not affected and entry.expand:
affected = True
#elif not affected and not entry.expand:
# affected = False
if affected and entry_exist and not entry_affected:
# We went through all the entries, but none marked this affected
affected = False
return affected
def set_from_whiteboard(self, whiteboard):
""" Set the Bug's values given reading a Status Whiteboard string from a Bug. """
if whiteboard == None:
raise BugError("Whiteboard empty")
rest = whiteboard
affected = []
matcher = re.compile("\s*\[\s*([^ +<=>]+)\s*(\+?[<=>]{1,2})\s*([^ +<=>\]]+)\s*(?:(\+?[<=>]{1,2})\s*([^ \]]+))?\s*\]\s*(.*)")
while len(rest.strip()) > 0:
match = matcher.match(rest)
if not match:
raise Exception("Illegal whiteboard: '%s'" % (rest))
name = match.group(1)
comp1 = match.group(2)
vers1 = match.group(3)
comp2 = match.group(4)
vers2 = match.group(5)
rest = match.group(6)
# calculate entry values
expand = False
upper_inclusive = None
upper = None
lower_inclusive = None
lower = None
if comp1[0] == "+":
comp1 = comp1[1:]
expand = True
if comp2 != None and comp2[0] == "+":
comp2 = comp2[1:]
expand = True
if comp1 == "=" or comp1 == "==":
lower_inclusive = True
upper_inclusive = True
lower = vers1
upper = vers1
for (c, v) in ((comp1, vers1), (comp2, vers2)):
if c == "<":
upper_inclusive = False
upper = v
elif c == "<=" or c == "=<" :
upper_inclusive = True
upper = v
elif c == ">":
lower_inclusive = False
lower = v
elif c == ">=" or c == "=>" :
lower_inclusive = True
lower = v
affected.append(IntervalEntry(name, lower_inclusive, upper_inclusive, lower, upper, expand, self))
self.affected = affected
def __repr__(self):
return str(self.bugno)
class Bugzilla:
def __init__(self):
import bugz
self.bz = bugz.Bugz(base = "https://bugs.gentoo.org")
# search bugzilla for kernel bugs
self.bugs_raw = self.bz.search("", product = ("Gentoo Security",),
component = ("Kernel",),
status = ('NEW', 'ASSIGNED', 'REOPENED'))
self.bugs = []
self.failed_bugs = []
for bug_raw in self.bugs_raw:
bugid = bug_raw['bugid']
bug_xml = self.bz.get(bugid)
bug = Bug(bugid, bug_raw['desc'], bug_raw['severity'])
try:
bug.set_from_whiteboard(bug_xml.find('//status_whiteboard').text)
self.bugs.append(bug)
except:
#print sys.exc_value
self.failed_bugs.append(bug)
def dashdot(s):
if s == None:
return None
return s.replace("-",".")
def main():
m = SecurityMatrix()
b = Bugzilla()
bug_errors = []
for bug in b.failed_bugs:
bug_errors.append(BugError("No whiteboard status set.", bug))
succeeded_bugs = []
for bug in b.bugs:
try:
m.check_bug(bug)
succeeded_bugs.append(bug)
except BugError, b_ex:
bug_errors.append(b_ex)
import kissoutput
kissoutput.write_xml("./out.xml", m, bug_errors, succeeded_bugs)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print '\n ! Exiting.'
|