From 3f7b15a386c80b586fe462d6efd80db2ea0b5f2d Mon Sep 17 00:00:00 2001 From: Bjoern Tropf Date: Wed, 18 Nov 2009 15:48:13 +0100 Subject: Implement distutils --- VERSION | 1 + bin/kernel-check | 26 ++ kernel-check.py | 274 ----------------- lib/__init__.py | 0 lib/guidexml.py | 311 -------------------- lib/kernellib.py | 616 --------------------------------------- setup.py | 21 ++ src/kernelcheck/__init__.py | 0 src/kernelcheck/kernelcheck.py | 274 +++++++++++++++++ src/kernelcheck/lib/__init__.py | 0 src/kernelcheck/lib/kernellib.py | 616 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 938 insertions(+), 1201 deletions(-) create mode 100644 VERSION create mode 100644 bin/kernel-check delete mode 100755 kernel-check.py delete mode 100644 lib/__init__.py delete mode 100644 lib/guidexml.py delete mode 100644 lib/kernellib.py create mode 100644 setup.py create mode 100644 src/kernelcheck/__init__.py create mode 100755 src/kernelcheck/kernelcheck.py create mode 100644 src/kernelcheck/lib/__init__.py create mode 100644 src/kernelcheck/lib/kernellib.py diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..2080591 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.11 diff --git a/bin/kernel-check b/bin/kernel-check new file mode 100644 index 0000000..bc19907 --- /dev/null +++ b/bin/kernel-check @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# kernel-check -- Kernel security information +# Copyright 2009-2009 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +import sys +from kernelcheck import kernelcheck + +try: + import signal + + def exithandler(signum,frame): + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + print + sys.exit(1) + + signal.signal(signal.SIGINT, exithandler) + signal.signal(signal.SIGTERM, exithandler) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + +except KeyboardInterrupt: + print + sys.exit(1) + +kernelcheck.main(sys.argv[1:]) diff --git a/kernel-check.py b/kernel-check.py deleted file mode 100755 index 1587b98..0000000 --- a/kernel-check.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python -# kernel-check -- Kernel security information -# Copyright 2009-2009 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 - -import getopt -import portage -import sys -import textwrap -import os - -import lib.kernellib as lib - -info = portage.output.EOutput().einfo -warn = portage.output.EOutput().ewarn -error = portage.output.EOutput().eerror -color = portage.output.colorize -term = portage.output.get_term_size() - -def main(argv): - 'Main function' - - try: - opts, args = getopt.getopt(argv, 'dhnr:s:v', - ['debug', 'help', 'nocolor', 'report=', 'show=', 'verbose']) - except getopt.GetoptError: - usage() - return - - for opt, arg in opts: - if opt in ('-d', '--debug'): - lib.DEBUG = True - elif opt in ('-h', '--help'): - usage() - return - elif opt in ('-n', '--nocolor'): - portage.output.nocolor() - elif opt in ('-r', '--report'): - error('--report not yet implemented') - return - elif opt in ('-s', '--show'): - print_bug(arg) - return - elif opt in ('-v', '--verbose'): - lib.VERBOSE = True - - print '>>> Gathering system information' - - uname = os.uname() - if uname[0] != 'Linux': - error('This tool currently only works for Linux kernels.') - error('Apparantly you are using "%s".' % uname[0]) - sys.exit() - - kernel = lib.extract_version(uname[2]) - if kernel is None: - error('No kernel information found!') - return - - info('Kernel version : %s' % (color('GOOD', '%s-%s' % - (kernel.version, kernel.revision)))) - info('Kernel source : %s' % color('GOOD', kernel.source)) - - kernel.genpatch = lib.get_genpatch(lib.PORTDIR, kernel) - - if kernel.genpatch is not None: - info('Gen(too)patch : %s' % color('GOOD', '%s %s' % - (kernel.genpatch.version, repr(kernel.genpatch)))) - elif kernel.source == 'gentoo': - warn('No genpatch information found!') - - arch = portage.settings['ARCH'] - if arch: - info('Architecture : %s' % color('GOOD', arch)) - else: - error('No architecture found!') - return - - print '\n>>> Reading all kernel vulnerabilities' - - """ - supported = list() - for item in lib.SUPPORTED: - best = (lib.all_version(item)) - if best and best is not None: - for i in best: - if item == 'gentoo': - i.genpatch = lib.get_genpatch(lib.read_genpatch_file( - lib.DIR['out']), i) - supported.append(i) - """ - - kernel_eval = lib.eval_cve_files(lib.DIR['out'], kernel, arch) - if not kernel_eval: - error('No kernel vulnerability files found!') - return - - info('%s vulnerabilities read.' % - color('GOOD', str(kernel_eval.read))) - info('%s apply to this architecture.' % - color('GOOD', str(kernel_eval.arch))) - info('%s do not affect this kernel.' % - color('GOOD', str(len(kernel_eval.unaffected)))) - - if (len(kernel_eval.affected) is 0): - info('Your kernel is not affected by any known vulnerabilites!') - return - - error('%s affect this kernel: ' % - color('BAD', str(len(kernel_eval.affected)))) - print_summary(kernel_eval.affected) - - """ - info('You have the following choices: ') - print '' - - info('[1] Recommended') - info('Keep your current kernel: %s' % color('BRACKET', - 'sys-kernel/%s-sources-%s-%s' % ( - kernel.source, kernel.version, kernel.revision))) - print '' - - choice = 1 - for item in supported: - supported_eval = lib.eval_cve_files(lib.DIR['out'], item, arch) - - if not supported_eval or kernel == item: - continue - - else: - comparison = lib.compare_evaluation(kernel_eval, supported_eval) - - if comparison is not None: - choice += 1; - score = 0 - for fix in comparison.fixed: - for cve in fix.cves: - score += float(cve.score) - - for new in comparison.new: - for cve in new.cves: - score -= float(cve.score) - - info('[%s] Recommended: (Score %s)' % (str(choice), score)) - info('Upgrade to this kernel: %s' % color('BRACKET', - 'sys-kernel/%s-sources-%s-%s' % ( - item.source, item.version, item.revision))) - info('which fixes %s of %s vulnerabilities and introduces %s' \ - ' new' % (color('GOOD', str(len(comparison.fixed))), - color('BAD', str(len(kernel_eval.affected))), - color('BAD', str(len(comparison.new))))) - print '' - """ - - print_information() - print_beta() - - -def print_summary(vullist): - 'Prints the vulnerability summary' - - for item in vullist: - - whiteboard = str() - for interval in item.affected: - whiteboard += '[' + str(interval) + '] ' - - if item.cves: - print '' - - for cve in item.cves: - severity = 'BAD' - if cve.severity == 'Low': - severity = 'GOOD' - elif cve.severity == 'Medium': - severity = 'WARN' - - print '\nBugid %s %-32s %s %s\n"%s..."' % (item.bugid, - color(severity, cve.severity + ' (' + cve.score + ')'), - cve.cve, whiteboard, cve.desc[:term[1]-6]) - - print '\n' - - -def print_bug(bugid): - 'Prints information about a particular bugid' - - if 'cve' in bugid.lower(): - print_cve(bugid.upper()) - return - - whiteboard = str() - cves = str() - vul = lib.read_cve_file(lib.DIR['out'], bugid) - - if vul is None: - error('Could not find bugid: %s' % bugid) - return - - for i, interval in enumerate(vul.affected): - if i is not 0: - whiteboard += ' ' * 14 - whiteboard += '[' + str(interval) + ']\n' - - info('Bugid : %s - %s' % (vul.bugid, vul.status.capitalize())) - info('Reported : %s - %s' % (vul.reporter, vul.reported[:-5])) - info('Affected : %s' % whiteboard[:-1]) - #TODO arch = str() - - for cve in vul.cves: - print '' - print_cve(cve.cve) - - -def print_cve(cveid): - 'Prints information about a cve' - - cve = lib.Cve('cveid') - - vul = lib.find_cve(cveid, lib.DIR['out']) - if vul is None: - error('Could not find cve: %s' % cveid) - return - else: - for item in vul.cves: - if item.cve == cveid: - cve = item - - info('Cve : %s - %s - Bugid %s' % (cve.cve, cve.published, vul.bugid)) - info('Severity : %s %s - %s' % (cve.severity, cve.score, cve.vector)) - #TODO print cve.refs - - for i, string in enumerate(textwrap.wrap('"%s"' % cve.desc , - (term[1] - 15))): - if i is 0: - info('Desc : %s' % string) - else: - print ' ' * 15 + string - - return - - -def print_beta(): - 'Prints a beta warning message' - - print('') - error('%s You are using an early version of kernel-check.' % - color('BAD', 'IMPORTANT')) - error('Please note that this tool might not operate as expected.') - - -def print_information(): - 'Prints an information message' - - info('To print more information about a vulnerability try:') - info(' $ %s -s [bugid|cve]' % sys.argv[0]) - - -def usage(): - 'Prints the usage screen' - - print 'Usage: %s [OPTION]...' % sys.argv[0][:-3] - print 'Kernel security information %s\r\n' % lib.VERSION - print ' -d, --debug display debugging information' - print ' -h, --help display help information' - print ' -n, --nocolor disable colors' - print ' -r, --report [file] create a security report' - print ' -s, --show [bugid|cve] display information about a bug or cve' - print ' -v, --verbose display additional information' - - -if __name__ == '__main__': - main(sys.argv[1:]) - diff --git a/lib/__init__.py b/lib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/guidexml.py b/lib/guidexml.py deleted file mode 100644 index d6babb8..0000000 --- a/lib/guidexml.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/env python -# guidexml -- guidexml class for python -# Copyright 2009-2009 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 - -import datetime -import os -import xml.etree.cElementTree - -MAXLENGTH = 80 - -LANGUAGES = ['en', 'de'] - -LICENSE = ''.join(['\n', -'\n']) - -TAGTYPES = ['p', 'c', 'b', 'e', 'i', 'pre', 'path', 'uri', 'note', 'warn', 'impo', - 'comment', 'sub', 'sup', 'keyword', 'ident', 'const', 'stmt', 'var'] - -#pre only in first body -#
-
-class Document(object):
-    title      = str()
-    abstract   = str()
-    docversion = str()
-    encoding   = str()
-    doctype    = str()
-    date       = str()
-    version    = str()
-    license    = str()
-    link       = str()
-    lang       = str()
-
-    chapters   = list()
-    authors    = list()
-
-
-    def __init__(self, title, version, abstract, lang, link, docversion='1.0',
-        encoding='UTF-8', doctype='/dtd/guide.dtd', date=None, license=None):
-
-        if type(title) is not str:
-            raise TypeError
-        if type(abstract) is not str:
-            raise TypeError
-        if type(docversion) is not str:
-            raise TypeError
-        if type(encoding) is not str:
-            raise TypeError
-        if type(doctype) is not str:
-            raise TypeError
-        if type(version) is not str:
-            raise TypeError
-        if type(date) is not str and date is not None:
-            raise TypeError
-        if type(license) is not str and license is not None:
-            raise TypeError
-        if type(link) is not str:
-            raise TypeError
-        if type(lang) is not str:
-            raise TypeError
-        if lang not in LANGUAGES:
-            raise ValueError
-
-        self.title = title
-        self.abstract = abstract
-        self.docversion = docversion
-        self.encoding = encoding
-        self.doctype = doctype
-        self.version = version
-        self.link = link
-        self.lang = lang
-
-        if date is None:
-            self.date = datetime.datetime.now().isoformat()[:10]
-        else:
-            self.date = date #TODO check YYYY-MM-DD
-
-        if license is None:
-            self.license = LICENSE
-        else:
-            self.license = license
-
-
-    def addAuthor(self, title, name, email=None):
-        self.authors.append(Author(title, name, email))
-
-
-    def append(self, chapter):
-        if type(chapter) is not Chapter:
-            raise TypeError
-        else:
-            self.chapters.append(chapter)
-
-
-    def create(self, path, filename):
-        xmlfile = os.path.join(path, filename) #TODO
-
-        output = list()
-        
-        if len(self.authors) == 0:
-            raise ValueError
-
-        output.append('\n' % (self.docversion, self.encoding))
-        output.append('\n\n\n' % (self.doctype))
-        output.append('\n%s\n\n' % (self.title))
-
-        for item in self.authors:
-            output.append('\n' % (item.title))
-            if item.email is not None:
-                output.append('  %s\n' % (item.email, item.name))
-            else:
-                output.append('  %s\n' % (item.name))
-
-        output.append('\n\n\n%s\n' % (linebreak(self.abstract)))
-        output.append('\n\n%s\n\n%s\n' % (self.license, self.version))
-        output.append('%s\n\n' % (self.date))
-
-        if len(self.chapters) > 0:
-            for chapter in self.chapters:
-                output.append('\n')
-                if len(chapter.sections) > 0:
-                    for section in chapter.sections:
-                        output.append('
\n') - output.append('\n') - for tag in section.tags: - if tag.tagtype in ['pre', 'p']: - output.append('\n%s\n' % repr(tag)) - else: - output.append(repr(tag)) - - output.append('\n\n') - output.append('
\n') - output.append('
\n') - - output.append('
') - - print ''.join(output) - - -#TODO -def linebreak(text): - if len(text) <= MAXLENGTH: - return text - - linebreak = str() - i = 0; - while i < len(text): - if i + MAXLENGTH < len(text): - linebreak += ''.join([text[i:i + MAXLENGTH], '\n']) - else: - linebreak += text[i:i + MAXLENGTH] - i += MAXLENGTH - return linebreak - - -class Author(object): - title = str() - name = str() - email = str() - - def __init__(self, title, name, email=None): - if type(title) is not str: - raise TypeError - if type(name) is not str: - raise TypeError - if type(email) is not str and email is not None: - raise TypeError - - self.title = title - self.name = name - self.email = email - - -class Chapter(object): - title = None - sections = list() - - def __init__(self, title): - if type(title) is not str: - raise TypeError - - self.title = title - - - def append(self, section): - if type(section) is not Section: - raise TypeError - else: - self.sections.append(section) - - -class Section(object): - title = None - bodys = list() - - def __init__(self, title): - if type(title) is not str: - raise TypeError - - self.title = title - - - tags = list() - - def append(self, tag): - if type(tag) is Tag: - self.tags.append(tag) - elif type(tag) is list: - for item in tag: - self.append(item) - elif type(tag) is str: - self.tags.append(Tag('text', tag)) - else: - raise TypeError - - -class Tag(object): - tagtype = str() - text = str() - - def __init__(self, tagtype, text): - if tagtype not in TAGTYPES: - raise TypeError - if type(text) is not str: - raise TypeError - - self.tagtype = tagtype - self.text = text - - def __repr__(self): - if (len(self.tagtype) * 2 + 5 + len(self.text)) > MAXLENGTH: - return '<%s>\n%s\n' % (self.tagtype, self.text, self.tagtype) - else: - return '<%s>%s' % (self.tagtype, self.text, self.tagtype) - -def preserve(text): - return Tag('pre', text) - - -def paragraph(text): - return Tag('p', text) - - -def warning(text): - return Tag('warn', text) - - -def important(text): - return Tag('impo', text) - - -def note(text): - return Tag('note', text) - - -def comment(text): - return Tag('comment', text) - - -def path(text): - return Tag('path', text) - - -def command(text): - return Tag('c', text) - - -def userinput(text): - return Tag('i', text) - - -def keyword(text): - return Tag('keyword', text) - - -def identifier(text): - return Tag('ident', text) - - -def constant(text): - return Tag('const', text) - - -def statement(text): - return Tag('stmt', text) - - -def variable(text): - return Tag('var', text) - - -def bold(text): - return Tag('b', text) - - -def emphasize(text): - return Tag('e', text) - - -def subscript(text): - return Tag('sub', text) - - -def superscript(text): - return Tag('sup', text) - - -def uri(text): - return Tag('uri', text) - diff --git a/lib/kernellib.py b/lib/kernellib.py deleted file mode 100644 index cf2e6ab..0000000 --- a/lib/kernellib.py +++ /dev/null @@ -1,616 +0,0 @@ -#!/usr/bin/env python -# kernel-check -- Kernel security information -# Copyright 2009-2009 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 - -from __future__ import with_statement -from contextlib import closing -import cStringIO -import datetime -import inspect -import logging -import mmap -import os -import portage -import re -import urllib -import xml.etree.cElementTree - - -ARCHES = [ - 'all', 'alpha', 'amd64', 'amd64-fbsd', 'arm', 'hppa', 'ia64', 'm68k', - 'mips', 'ppc', 'ppc64', 's390', 'sh', 'sparc', 'sparc-fbsd', 'x86', - 'x86-fbsd' -] - -REGEX = { - 'gp_version' : re.compile(r'(?<=K_GENPATCHES_VER\=\").+(?=\")'), - 'gp_want' : re.compile(r'(?<=K_WANT_GENPATCHES\=\").+(?=\")'), - 'k_version' : re.compile(r'^((?:\d{1,2}\.){0,4}\d{1,2})(-.*)?$'), - 'rc_kernel' : re.compile(r'^rc\d{1,3}$'), - 'git_kernel' : re.compile(r'^git(\d{1,3})$'), - 'r_kernel' : re.compile(r'^r\d{1,3}$') -} - -SUPPORTED = ['gentoo', 'vanilla', 'hardened'] - -KERNEL_TYPES = [ - 'aa', 'acpi', 'ac', 'alpha', 'arm', 'as', 'cell', 'ck', 'compaq', 'crypto', - 'development', 'gaming','gentoo-dev', 'gentoo', 'gentoo-test', 'gfs', - 'git', 'grsec', 'gs', 'hardened-dev', 'hardened', 'hppa-dev', 'hppa', - 'ia64', 'kurobox', 'linux', 'lolo', 'mips-prepatch', 'mips', 'mjc', 'mm', - 'mosix', 'openblocks', 'openmosix','openvz', 'pac', 'pegasos-dev', - 'pegasos', 'pfeifer', 'planet-ccrma', 'ppc64', 'ppc-development', - 'ppc-dev', 'ppc', 'redhat', 'rsbac-dev', 'rsbac', 'selinux', 'sh', - 'sparc-dev', 'sparc', 'suspend2', 'systrace', 'tuxonice', 'uclinux', - 'usermode', 'vanilla-prepatch', 'vanilla', 'vanilla-tiny', 'vserver-dev', - 'vserver', 'win4lin', 'wolk-dev', 'wolk', 'xbox', 'xen', 'xfs' -] - -VERSION = '0.3.10' -DEBUG = False -FILEPATH = os.path.dirname(os.path.realpath(__file__)) -PORTDIR = portage.settings['PORTDIR'] -DIR = { - 'tmp' : os.path.join(FILEPATH, 'tmp'), - 'out' : os.path.join(PORTDIR, 'metadata', 'kernel'), - 'bug' : os.path.join(FILEPATH, 'tmp', 'bug'), - 'nvd' : os.path.join(FILEPATH, 'tmp', 'nvd') -} - -def BUG_ON(msg, e): - if DEBUG: - print 'DEBUG line %s in %s(): %s -> %s' % (inspect.stack()[1][2], - inspect.stack()[1][3], msg, e) - - -class Evaluation: - """Evaluation class - - Provides information about the vulnerability of a kernel. - """ - - read = int() - arch = int() - affected = list() - unaffected = list() - - def __init__(self): - self.affected = list() - self.unaffected = list() - - -class Comparison: #TODO Check if deprecated - """Comparison class - """ - - fixed = int() - new = list() - #TODO add more information - - def __init__(self): - self.fixed = list() - self.new = list() - - -class Cve: - """Common vulnerabilities and exposures class - - Contains all important information about a cve. - - Attributes: - cve: a string represeting the cve number of the class. - desc: a string providing a detailed description for the cve. - published: a string representing the original cve release date. - refs: a list of external references. - severity: a string representing the cve severity. - score: a floating point representing cvss base score. - vector: a string providing the cve access vector. - """ - - cve = str() - desc = str() - published = str() - refs = list() - severity = str() - score = float() - vector = str() - - def __init__(self, cve): - self.cve = cve - - def __eq__(self, diff): - return (self.cve == diff.cve) #FIXME is this enough? - - def __ne__(self, diff): - return not self.__eq__(diff) - - -class Genpatch: - 'Genpatch class' - - base = bool() - extras = bool() - kernel = None - version = str() - - def __init__(self, version): - self.version = version - - - def __repr__(self): - if self.base and self.extras: - return 'base extras' - if self.base: - return 'base' - if self.extras: - return 'extras' - - - def __eq__(self, diff): - if self.kernel == diff.kernel: - return (''.join((str(self.base), str(self.extras), self.version)) - == ''.join((str(diff.base), str(diff.extras), diff.version))) - else: - return False - - - def __ne__(self, diff): - return not self.__eq__(diff) - - -class Kernel: - 'Kernel class' - - revision = str() - source = str() - version = str() - genpatch = None - - def __init__(self, source): - self.source = source - - - def __repr__(self): - return str(self.version + '-' + self.source + '-' + self.revision) - - - def __eq__(self, diff): - return (''.join((self.revision, self.source, - self.version, str(self.genpatch))) - == ''.join((diff.revision, diff.source, - diff.version, str(diff.genpatch)))) - - - def __ne__(self, diff): - return not self.__eq__(diff) - - -class Vulnerability: - 'Vulnerability class' - - arch = str() - bugid = int() - cvelist = list() - cves = list() - affected = list() - reported = str() - reporter = str() - status = str() - - def __init__(self, bugid): - self.bugid = bugid - - def __eq__(self, diff): - return (self.bugid == diff.bugid) #FIXME is this enough? - - def __ne__(self, diff): - return not self.__eq__(diff) - - -class Interval: - """Interval class - - Provides one interval entry for a vulnerability - - Attributes: - name: a string representing the name of the kernel release - lower: a string representing the lower boundary of the interval - upper: a string representing the upper boundary of the interval - lower_i: a boolean indicating if the lower boundary is inclusive - upper_i: a boolean indicating if the upper boundary is inclusive - """ - - name = str() - lower = str() - upper = str() - lower_i = bool() - upper_i = bool() - - def __init__(self, name, lower, upper, lower_i, upper_i): - if name == 'linux' or name == 'genpatches': - pass - elif name == 'gp': - name = 'genpatches' - - name = name.replace('-sources', '') - - self.name = name - self.lower_i = lower_i - self.upper_i = upper_i - if name == 'genpatches': - if lower: - self.lower = lower.replace('-','.') - else: - self.lower = lower - if upper: - self.upper = upper.replace('-','.') - else: - self.upper = upper - else: - self.lower = lower - self.upper = upper - - - def __repr__(self): - interval = str(self.name) - interval += ' ' - if self.lower and self.lower_i: - interval += '>=%s ' % (self.lower) - if self.lower and not self.lower_i: - interval += '>%s ' % (self.lower) - if self.upper and self.upper_i: - interval += '<=%s' % (self.upper) - if self.upper and not self.upper_i: - interval += '<%s' % (self.upper) - - return interval - - -def interval_from_xml(root): - 'Returns an interval from xml' - - name = root.get('source') - - lower = '' - upper = '' - lower_i = False - upper_i = False - - if root.find('lower') is not None: - lower = root.find('lower').text - lower_i = (root.find('lower').get('inclusive') == 'true') - - if root.find('upper') is not None: - upper = root.find('upper').text - upper_i = (root.find('upper').get('inclusive') == 'true') - - return Interval(name, lower, upper, lower_i, upper_i) - - -#TODO Use exceptions -def is_in_interval(interval, kernel, bugid=None): - 'Returns True if the given version is inside our specified interval' - - version = str() - - if interval.name == 'linux': - version = kernel.version - - elif interval.name == 'genpatches': - version = kernel.version.replace('-', '.') - - elif interval.name == 'hardened': - version = kernel.version #TODO is this correct? - - elif interval.name == 'xen': - version = kernel.version #TODO is this correct? - - elif interval.name == 'vserver': - return False - - else: - BUG_ON(interval.name + ' ' + bugid.bugid) - return False - - for item in ['lower', 'upper']: - if getattr(interval, item): - result = portage.versions.vercmp(version, getattr(interval, item)) - - if result == None: - BUG_ON('Could not compare %s and %s' % - (getattr(interval, item),version)) - - if result == 0 and not getattr(interval, item + '_i'): - return False - - if result == 0 and getattr(interval, item + '_i'): - return True - - if item == 'lower' and result < 0: - return False - - if item == 'upper' and result > 0: - return False - - return True - - -def get_genpatch(directory, kernel): - 'Returns a list containing all genpatches from portage' - - patches = list() - directory = os.path.join(directory, 'sys-kernel') - - for sources in os.listdir(directory): - if '-sources' in sources: - for ebuild in os.listdir(os.path.join(directory, sources)): - if '.ebuild' in ebuild: - genpatch = extract_genpatch(ebuild, directory, sources) - - if genpatch is not None: - if genpatch.kernel == kernel: - return genpatch - - return None - - -def extract_genpatch(ebuild, directory, sources): - 'Returns a genpatch from an ebuild' - - pkg = portage.versions.catpkgsplit('sys-kernel/%s' % ebuild[:-7]) - - with open(os.path.join(directory, sources, ebuild), 'r') as ebuild_file: - content = ebuild_file.read() - - try: - genpatch_v = REGEX['gp_version'].findall(content)[0] - genpatch_w = REGEX['gp_want'].findall(content)[0] - except: - return None - - kernel = Kernel(pkg[1].replace('-sources', '')) - kernel.version = pkg[2] - kernel.revision = pkg[3] - - genpatch = Genpatch(pkg[2] + '-' + genpatch_v) - genpatch.kernel = kernel - genpatch.base = ('base' in genpatch_w) - genpatch.extras = ('extras' in genpatch_w) - - return genpatch - - -def parse_cve_files(directory): - 'Returns all bug files as list' - - files = list() - - if (os.path.exists(directory)): - for item in os.listdir(directory): - try: - cve_file = read_cve_file(directory, item[:-4]) - if cve_file is not None: - files.append(cve_file) - - except AttributeError, e: - BUG_ON(item, e) - - return files - - -def find_cve(cve, directory): - 'Returns a bug containing the cve' - - for item in parse_cve_files(directory): - for cves in item.cves: - if cve == cves.cve: - return item - - return None - - -def eval_cve_files(directory, kernel, arch): - 'Returns a vulnerabilty evaluation' - - files = parse_cve_files(directory) - - if not files: - return None - - evaluation = Evaluation() - - for item in files: - evaluation.read += 1 - - if item.arch not in ARCHES: - BUG_ON('[Error] Wrong architecture %s in bugid: %s' % - (item.arch, item.bugid)) - - if item.arch != arch and item.arch != 'all': - evaluation.unaffected.append(item) - else: - evaluation.arch += 1 - - if is_affected(item.affected, kernel, item): - evaluation.affected.append(item) - else: - evaluation.unaffected.append(item) - - return evaluation - -#TODO Remove item -def is_affected(interval_list, kernel, item): - 'Returns true if a kernel is affected' - - kernel_gentoo = (kernel.source == 'gentoo' and kernel.genpatch is not None) - kernel_affected = False - kernel_linux_affected = False - kernel_gp_affected = False - linux_interval = False - gentoo_interval = False - - for interval in interval_list: - if interval.name == 'genpatches': - gentoo_interval = True - if kernel_gentoo: - if is_in_interval(interval, kernel.genpatch, item): - kernel_gp_affected = True - - elif interval.name == 'linux': - linux_interval = True - if is_in_interval(interval, kernel, item): - kernel_linux_affected = True - - else: - pass #TODO - - if linux_interval: - if kernel_linux_affected: - if gentoo_interval and kernel_gentoo: - if kernel_gp_affected: - kernel_affected = True - else: - kernel_affected = False - else: - kernel_affected = True - else: - kernel_affected = False - else: - if kernel_gentoo and gentoo_interval: - if kernel_gp_affected: - kernel_affected = True - else: - kernel_affected = False - #TODO Implement else for hardend/xen/expand - - return kernel_affected - - -def compare_evaluation(kernel, compare): - 'Creates a comparison out of two evaluation instances' - - comparison = Comparison() - - if kernel.read != compare.read or kernel.arch != compare.arch: - BUG_ON('Kernels do not match: %s %s' % (kernel1.read, kernel2.read)) - return - - for item in kernel.affected: - if item not in compare.affected: - comparison.fixed.append(item) - - for item in compare.affected: - if item not in kernel.affected: - comparison.new.append(item) - - return comparison - - -def read_cve_file(directory, bugid): - 'Read a bug file created by collector' - - cves = list() - affected = list() - - filename = os.path.join(directory, bugid + '.xml') - - try: - with open(filename, 'r+') as xml_data: - memory_map = mmap.mmap(xml_data.fileno(), 0) - root = xml.etree.cElementTree.parse(memory_map).getroot() - except IOError, e: - BUG_ON(filename, e) - return None - - bugroot = root.find('bug') - - vul = Vulnerability(bugroot.find('bugid').text) - - for elem in ['arch', 'reported', 'reporter', 'status']: - setattr(vul, elem, bugroot.find(elem).text) - - affectedroot = bugroot.find('affected') - for item in affectedroot: - interval = interval_from_xml(item) - affected.append(interval) - - vul.affected = affected - - for item in root: - if item.tag == 'cve': - cve = Cve(item.find('cve').text) - if cve is None: - return None - - for elem in ['desc', 'published', 'refs', - 'severity', 'score', 'vector']: - element = item.find(elem) - if element is not None: - setattr(cve, elem, element.text) - else: - BUG_ON(filename, '(%s, \'No such element\')' % elem) - - cves.append(cve) - vul.cves = cves - - return vul - -#TODO Use Exceptions -def extract_version(release): - 'Extracts revision, source and version out of a release tag' - - match = REGEX['k_version'].match(release) - if not match: - BUG_ON('[Error] Release %s does not contain any valid information' % - release) - return None - - version, rest = match.groups() - - kernel = Kernel('vanilla') - kernel.revision = 'r0' - kernel.version = version - - for elem in (rest or '').split('-'): - if elem == 'sources': - pass - elif REGEX['rc_kernel'].match(elem): - kernel.version += '_' + elem - elif REGEX['git_kernel'].match(elem): - kernel.source = 'git' - kernel.revision = 'r' + REGEX['gitd'].match(elem).groups()[0] - elif REGEX['r_kernel'].match(elem): - kernel.revision = elem - elif elem in KERNEL_TYPES: - kernel.source = elem - elif elem != '': - BUG_ON('[Error] Dropping unknown version component \'%s\', \ - probably local tag.' % elem) - - return kernel - - -def all_version(source): - """ Given a kernel source name (e.g. vanilla), returns a Kernel object - for the latest revision in the tree, or None if none exists. """ - - versions = list() - - porttree = portage.db[portage.root]['porttree'] - matches = porttree.dbapi.xmatch('match-all', - 'sys-kernel/%s-sources' % source) - - for item in matches: - best = portage.versions.catpkgsplit(item) - if not best: - continue - - kernel = Kernel(best[1].replace('-sources', '')) - kernel.version = best[2] - kernel.revision = best[3] - - versions.append(kernel) - - return versions - diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b28221e --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# kernel-check -- Kernel security information +# Copyright 2009-2009 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +from distutils.core import setup + +__version__ = open("VERSION").read().strip() + +setup( + name='Kernel-check', + version=__version__, + description='Gentoo kernel tracking tool', + author='Bjoern Trop', + author_email='asym@gentoo.org', + url='http://www.python.org/sigs/distutils-sig/', #TODO + package_dir={'': 'src'}, + packages=['kernelcheck', 'kernelcheck.lib'], + scripts=['bin/kernel-check'] +) + diff --git a/src/kernelcheck/__init__.py b/src/kernelcheck/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/kernelcheck/kernelcheck.py b/src/kernelcheck/kernelcheck.py new file mode 100755 index 0000000..1587b98 --- /dev/null +++ b/src/kernelcheck/kernelcheck.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python +# kernel-check -- Kernel security information +# Copyright 2009-2009 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +import getopt +import portage +import sys +import textwrap +import os + +import lib.kernellib as lib + +info = portage.output.EOutput().einfo +warn = portage.output.EOutput().ewarn +error = portage.output.EOutput().eerror +color = portage.output.colorize +term = portage.output.get_term_size() + +def main(argv): + 'Main function' + + try: + opts, args = getopt.getopt(argv, 'dhnr:s:v', + ['debug', 'help', 'nocolor', 'report=', 'show=', 'verbose']) + except getopt.GetoptError: + usage() + return + + for opt, arg in opts: + if opt in ('-d', '--debug'): + lib.DEBUG = True + elif opt in ('-h', '--help'): + usage() + return + elif opt in ('-n', '--nocolor'): + portage.output.nocolor() + elif opt in ('-r', '--report'): + error('--report not yet implemented') + return + elif opt in ('-s', '--show'): + print_bug(arg) + return + elif opt in ('-v', '--verbose'): + lib.VERBOSE = True + + print '>>> Gathering system information' + + uname = os.uname() + if uname[0] != 'Linux': + error('This tool currently only works for Linux kernels.') + error('Apparantly you are using "%s".' % uname[0]) + sys.exit() + + kernel = lib.extract_version(uname[2]) + if kernel is None: + error('No kernel information found!') + return + + info('Kernel version : %s' % (color('GOOD', '%s-%s' % + (kernel.version, kernel.revision)))) + info('Kernel source : %s' % color('GOOD', kernel.source)) + + kernel.genpatch = lib.get_genpatch(lib.PORTDIR, kernel) + + if kernel.genpatch is not None: + info('Gen(too)patch : %s' % color('GOOD', '%s %s' % + (kernel.genpatch.version, repr(kernel.genpatch)))) + elif kernel.source == 'gentoo': + warn('No genpatch information found!') + + arch = portage.settings['ARCH'] + if arch: + info('Architecture : %s' % color('GOOD', arch)) + else: + error('No architecture found!') + return + + print '\n>>> Reading all kernel vulnerabilities' + + """ + supported = list() + for item in lib.SUPPORTED: + best = (lib.all_version(item)) + if best and best is not None: + for i in best: + if item == 'gentoo': + i.genpatch = lib.get_genpatch(lib.read_genpatch_file( + lib.DIR['out']), i) + supported.append(i) + """ + + kernel_eval = lib.eval_cve_files(lib.DIR['out'], kernel, arch) + if not kernel_eval: + error('No kernel vulnerability files found!') + return + + info('%s vulnerabilities read.' % + color('GOOD', str(kernel_eval.read))) + info('%s apply to this architecture.' % + color('GOOD', str(kernel_eval.arch))) + info('%s do not affect this kernel.' % + color('GOOD', str(len(kernel_eval.unaffected)))) + + if (len(kernel_eval.affected) is 0): + info('Your kernel is not affected by any known vulnerabilites!') + return + + error('%s affect this kernel: ' % + color('BAD', str(len(kernel_eval.affected)))) + print_summary(kernel_eval.affected) + + """ + info('You have the following choices: ') + print '' + + info('[1] Recommended') + info('Keep your current kernel: %s' % color('BRACKET', + 'sys-kernel/%s-sources-%s-%s' % ( + kernel.source, kernel.version, kernel.revision))) + print '' + + choice = 1 + for item in supported: + supported_eval = lib.eval_cve_files(lib.DIR['out'], item, arch) + + if not supported_eval or kernel == item: + continue + + else: + comparison = lib.compare_evaluation(kernel_eval, supported_eval) + + if comparison is not None: + choice += 1; + score = 0 + for fix in comparison.fixed: + for cve in fix.cves: + score += float(cve.score) + + for new in comparison.new: + for cve in new.cves: + score -= float(cve.score) + + info('[%s] Recommended: (Score %s)' % (str(choice), score)) + info('Upgrade to this kernel: %s' % color('BRACKET', + 'sys-kernel/%s-sources-%s-%s' % ( + item.source, item.version, item.revision))) + info('which fixes %s of %s vulnerabilities and introduces %s' \ + ' new' % (color('GOOD', str(len(comparison.fixed))), + color('BAD', str(len(kernel_eval.affected))), + color('BAD', str(len(comparison.new))))) + print '' + """ + + print_information() + print_beta() + + +def print_summary(vullist): + 'Prints the vulnerability summary' + + for item in vullist: + + whiteboard = str() + for interval in item.affected: + whiteboard += '[' + str(interval) + '] ' + + if item.cves: + print '' + + for cve in item.cves: + severity = 'BAD' + if cve.severity == 'Low': + severity = 'GOOD' + elif cve.severity == 'Medium': + severity = 'WARN' + + print '\nBugid %s %-32s %s %s\n"%s..."' % (item.bugid, + color(severity, cve.severity + ' (' + cve.score + ')'), + cve.cve, whiteboard, cve.desc[:term[1]-6]) + + print '\n' + + +def print_bug(bugid): + 'Prints information about a particular bugid' + + if 'cve' in bugid.lower(): + print_cve(bugid.upper()) + return + + whiteboard = str() + cves = str() + vul = lib.read_cve_file(lib.DIR['out'], bugid) + + if vul is None: + error('Could not find bugid: %s' % bugid) + return + + for i, interval in enumerate(vul.affected): + if i is not 0: + whiteboard += ' ' * 14 + whiteboard += '[' + str(interval) + ']\n' + + info('Bugid : %s - %s' % (vul.bugid, vul.status.capitalize())) + info('Reported : %s - %s' % (vul.reporter, vul.reported[:-5])) + info('Affected : %s' % whiteboard[:-1]) + #TODO arch = str() + + for cve in vul.cves: + print '' + print_cve(cve.cve) + + +def print_cve(cveid): + 'Prints information about a cve' + + cve = lib.Cve('cveid') + + vul = lib.find_cve(cveid, lib.DIR['out']) + if vul is None: + error('Could not find cve: %s' % cveid) + return + else: + for item in vul.cves: + if item.cve == cveid: + cve = item + + info('Cve : %s - %s - Bugid %s' % (cve.cve, cve.published, vul.bugid)) + info('Severity : %s %s - %s' % (cve.severity, cve.score, cve.vector)) + #TODO print cve.refs + + for i, string in enumerate(textwrap.wrap('"%s"' % cve.desc , + (term[1] - 15))): + if i is 0: + info('Desc : %s' % string) + else: + print ' ' * 15 + string + + return + + +def print_beta(): + 'Prints a beta warning message' + + print('') + error('%s You are using an early version of kernel-check.' % + color('BAD', 'IMPORTANT')) + error('Please note that this tool might not operate as expected.') + + +def print_information(): + 'Prints an information message' + + info('To print more information about a vulnerability try:') + info(' $ %s -s [bugid|cve]' % sys.argv[0]) + + +def usage(): + 'Prints the usage screen' + + print 'Usage: %s [OPTION]...' % sys.argv[0][:-3] + print 'Kernel security information %s\r\n' % lib.VERSION + print ' -d, --debug display debugging information' + print ' -h, --help display help information' + print ' -n, --nocolor disable colors' + print ' -r, --report [file] create a security report' + print ' -s, --show [bugid|cve] display information about a bug or cve' + print ' -v, --verbose display additional information' + + +if __name__ == '__main__': + main(sys.argv[1:]) + diff --git a/src/kernelcheck/lib/__init__.py b/src/kernelcheck/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/kernelcheck/lib/kernellib.py b/src/kernelcheck/lib/kernellib.py new file mode 100644 index 0000000..cf2e6ab --- /dev/null +++ b/src/kernelcheck/lib/kernellib.py @@ -0,0 +1,616 @@ +#!/usr/bin/env python +# kernel-check -- Kernel security information +# Copyright 2009-2009 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +from __future__ import with_statement +from contextlib import closing +import cStringIO +import datetime +import inspect +import logging +import mmap +import os +import portage +import re +import urllib +import xml.etree.cElementTree + + +ARCHES = [ + 'all', 'alpha', 'amd64', 'amd64-fbsd', 'arm', 'hppa', 'ia64', 'm68k', + 'mips', 'ppc', 'ppc64', 's390', 'sh', 'sparc', 'sparc-fbsd', 'x86', + 'x86-fbsd' +] + +REGEX = { + 'gp_version' : re.compile(r'(?<=K_GENPATCHES_VER\=\").+(?=\")'), + 'gp_want' : re.compile(r'(?<=K_WANT_GENPATCHES\=\").+(?=\")'), + 'k_version' : re.compile(r'^((?:\d{1,2}\.){0,4}\d{1,2})(-.*)?$'), + 'rc_kernel' : re.compile(r'^rc\d{1,3}$'), + 'git_kernel' : re.compile(r'^git(\d{1,3})$'), + 'r_kernel' : re.compile(r'^r\d{1,3}$') +} + +SUPPORTED = ['gentoo', 'vanilla', 'hardened'] + +KERNEL_TYPES = [ + 'aa', 'acpi', 'ac', 'alpha', 'arm', 'as', 'cell', 'ck', 'compaq', 'crypto', + 'development', 'gaming','gentoo-dev', 'gentoo', 'gentoo-test', 'gfs', + 'git', 'grsec', 'gs', 'hardened-dev', 'hardened', 'hppa-dev', 'hppa', + 'ia64', 'kurobox', 'linux', 'lolo', 'mips-prepatch', 'mips', 'mjc', 'mm', + 'mosix', 'openblocks', 'openmosix','openvz', 'pac', 'pegasos-dev', + 'pegasos', 'pfeifer', 'planet-ccrma', 'ppc64', 'ppc-development', + 'ppc-dev', 'ppc', 'redhat', 'rsbac-dev', 'rsbac', 'selinux', 'sh', + 'sparc-dev', 'sparc', 'suspend2', 'systrace', 'tuxonice', 'uclinux', + 'usermode', 'vanilla-prepatch', 'vanilla', 'vanilla-tiny', 'vserver-dev', + 'vserver', 'win4lin', 'wolk-dev', 'wolk', 'xbox', 'xen', 'xfs' +] + +VERSION = '0.3.10' +DEBUG = False +FILEPATH = os.path.dirname(os.path.realpath(__file__)) +PORTDIR = portage.settings['PORTDIR'] +DIR = { + 'tmp' : os.path.join(FILEPATH, 'tmp'), + 'out' : os.path.join(PORTDIR, 'metadata', 'kernel'), + 'bug' : os.path.join(FILEPATH, 'tmp', 'bug'), + 'nvd' : os.path.join(FILEPATH, 'tmp', 'nvd') +} + +def BUG_ON(msg, e): + if DEBUG: + print 'DEBUG line %s in %s(): %s -> %s' % (inspect.stack()[1][2], + inspect.stack()[1][3], msg, e) + + +class Evaluation: + """Evaluation class + + Provides information about the vulnerability of a kernel. + """ + + read = int() + arch = int() + affected = list() + unaffected = list() + + def __init__(self): + self.affected = list() + self.unaffected = list() + + +class Comparison: #TODO Check if deprecated + """Comparison class + """ + + fixed = int() + new = list() + #TODO add more information + + def __init__(self): + self.fixed = list() + self.new = list() + + +class Cve: + """Common vulnerabilities and exposures class + + Contains all important information about a cve. + + Attributes: + cve: a string represeting the cve number of the class. + desc: a string providing a detailed description for the cve. + published: a string representing the original cve release date. + refs: a list of external references. + severity: a string representing the cve severity. + score: a floating point representing cvss base score. + vector: a string providing the cve access vector. + """ + + cve = str() + desc = str() + published = str() + refs = list() + severity = str() + score = float() + vector = str() + + def __init__(self, cve): + self.cve = cve + + def __eq__(self, diff): + return (self.cve == diff.cve) #FIXME is this enough? + + def __ne__(self, diff): + return not self.__eq__(diff) + + +class Genpatch: + 'Genpatch class' + + base = bool() + extras = bool() + kernel = None + version = str() + + def __init__(self, version): + self.version = version + + + def __repr__(self): + if self.base and self.extras: + return 'base extras' + if self.base: + return 'base' + if self.extras: + return 'extras' + + + def __eq__(self, diff): + if self.kernel == diff.kernel: + return (''.join((str(self.base), str(self.extras), self.version)) + == ''.join((str(diff.base), str(diff.extras), diff.version))) + else: + return False + + + def __ne__(self, diff): + return not self.__eq__(diff) + + +class Kernel: + 'Kernel class' + + revision = str() + source = str() + version = str() + genpatch = None + + def __init__(self, source): + self.source = source + + + def __repr__(self): + return str(self.version + '-' + self.source + '-' + self.revision) + + + def __eq__(self, diff): + return (''.join((self.revision, self.source, + self.version, str(self.genpatch))) + == ''.join((diff.revision, diff.source, + diff.version, str(diff.genpatch)))) + + + def __ne__(self, diff): + return not self.__eq__(diff) + + +class Vulnerability: + 'Vulnerability class' + + arch = str() + bugid = int() + cvelist = list() + cves = list() + affected = list() + reported = str() + reporter = str() + status = str() + + def __init__(self, bugid): + self.bugid = bugid + + def __eq__(self, diff): + return (self.bugid == diff.bugid) #FIXME is this enough? + + def __ne__(self, diff): + return not self.__eq__(diff) + + +class Interval: + """Interval class + + Provides one interval entry for a vulnerability + + Attributes: + name: a string representing the name of the kernel release + lower: a string representing the lower boundary of the interval + upper: a string representing the upper boundary of the interval + lower_i: a boolean indicating if the lower boundary is inclusive + upper_i: a boolean indicating if the upper boundary is inclusive + """ + + name = str() + lower = str() + upper = str() + lower_i = bool() + upper_i = bool() + + def __init__(self, name, lower, upper, lower_i, upper_i): + if name == 'linux' or name == 'genpatches': + pass + elif name == 'gp': + name = 'genpatches' + + name = name.replace('-sources', '') + + self.name = name + self.lower_i = lower_i + self.upper_i = upper_i + if name == 'genpatches': + if lower: + self.lower = lower.replace('-','.') + else: + self.lower = lower + if upper: + self.upper = upper.replace('-','.') + else: + self.upper = upper + else: + self.lower = lower + self.upper = upper + + + def __repr__(self): + interval = str(self.name) + interval += ' ' + if self.lower and self.lower_i: + interval += '>=%s ' % (self.lower) + if self.lower and not self.lower_i: + interval += '>%s ' % (self.lower) + if self.upper and self.upper_i: + interval += '<=%s' % (self.upper) + if self.upper and not self.upper_i: + interval += '<%s' % (self.upper) + + return interval + + +def interval_from_xml(root): + 'Returns an interval from xml' + + name = root.get('source') + + lower = '' + upper = '' + lower_i = False + upper_i = False + + if root.find('lower') is not None: + lower = root.find('lower').text + lower_i = (root.find('lower').get('inclusive') == 'true') + + if root.find('upper') is not None: + upper = root.find('upper').text + upper_i = (root.find('upper').get('inclusive') == 'true') + + return Interval(name, lower, upper, lower_i, upper_i) + + +#TODO Use exceptions +def is_in_interval(interval, kernel, bugid=None): + 'Returns True if the given version is inside our specified interval' + + version = str() + + if interval.name == 'linux': + version = kernel.version + + elif interval.name == 'genpatches': + version = kernel.version.replace('-', '.') + + elif interval.name == 'hardened': + version = kernel.version #TODO is this correct? + + elif interval.name == 'xen': + version = kernel.version #TODO is this correct? + + elif interval.name == 'vserver': + return False + + else: + BUG_ON(interval.name + ' ' + bugid.bugid) + return False + + for item in ['lower', 'upper']: + if getattr(interval, item): + result = portage.versions.vercmp(version, getattr(interval, item)) + + if result == None: + BUG_ON('Could not compare %s and %s' % + (getattr(interval, item),version)) + + if result == 0 and not getattr(interval, item + '_i'): + return False + + if result == 0 and getattr(interval, item + '_i'): + return True + + if item == 'lower' and result < 0: + return False + + if item == 'upper' and result > 0: + return False + + return True + + +def get_genpatch(directory, kernel): + 'Returns a list containing all genpatches from portage' + + patches = list() + directory = os.path.join(directory, 'sys-kernel') + + for sources in os.listdir(directory): + if '-sources' in sources: + for ebuild in os.listdir(os.path.join(directory, sources)): + if '.ebuild' in ebuild: + genpatch = extract_genpatch(ebuild, directory, sources) + + if genpatch is not None: + if genpatch.kernel == kernel: + return genpatch + + return None + + +def extract_genpatch(ebuild, directory, sources): + 'Returns a genpatch from an ebuild' + + pkg = portage.versions.catpkgsplit('sys-kernel/%s' % ebuild[:-7]) + + with open(os.path.join(directory, sources, ebuild), 'r') as ebuild_file: + content = ebuild_file.read() + + try: + genpatch_v = REGEX['gp_version'].findall(content)[0] + genpatch_w = REGEX['gp_want'].findall(content)[0] + except: + return None + + kernel = Kernel(pkg[1].replace('-sources', '')) + kernel.version = pkg[2] + kernel.revision = pkg[3] + + genpatch = Genpatch(pkg[2] + '-' + genpatch_v) + genpatch.kernel = kernel + genpatch.base = ('base' in genpatch_w) + genpatch.extras = ('extras' in genpatch_w) + + return genpatch + + +def parse_cve_files(directory): + 'Returns all bug files as list' + + files = list() + + if (os.path.exists(directory)): + for item in os.listdir(directory): + try: + cve_file = read_cve_file(directory, item[:-4]) + if cve_file is not None: + files.append(cve_file) + + except AttributeError, e: + BUG_ON(item, e) + + return files + + +def find_cve(cve, directory): + 'Returns a bug containing the cve' + + for item in parse_cve_files(directory): + for cves in item.cves: + if cve == cves.cve: + return item + + return None + + +def eval_cve_files(directory, kernel, arch): + 'Returns a vulnerabilty evaluation' + + files = parse_cve_files(directory) + + if not files: + return None + + evaluation = Evaluation() + + for item in files: + evaluation.read += 1 + + if item.arch not in ARCHES: + BUG_ON('[Error] Wrong architecture %s in bugid: %s' % + (item.arch, item.bugid)) + + if item.arch != arch and item.arch != 'all': + evaluation.unaffected.append(item) + else: + evaluation.arch += 1 + + if is_affected(item.affected, kernel, item): + evaluation.affected.append(item) + else: + evaluation.unaffected.append(item) + + return evaluation + +#TODO Remove item +def is_affected(interval_list, kernel, item): + 'Returns true if a kernel is affected' + + kernel_gentoo = (kernel.source == 'gentoo' and kernel.genpatch is not None) + kernel_affected = False + kernel_linux_affected = False + kernel_gp_affected = False + linux_interval = False + gentoo_interval = False + + for interval in interval_list: + if interval.name == 'genpatches': + gentoo_interval = True + if kernel_gentoo: + if is_in_interval(interval, kernel.genpatch, item): + kernel_gp_affected = True + + elif interval.name == 'linux': + linux_interval = True + if is_in_interval(interval, kernel, item): + kernel_linux_affected = True + + else: + pass #TODO + + if linux_interval: + if kernel_linux_affected: + if gentoo_interval and kernel_gentoo: + if kernel_gp_affected: + kernel_affected = True + else: + kernel_affected = False + else: + kernel_affected = True + else: + kernel_affected = False + else: + if kernel_gentoo and gentoo_interval: + if kernel_gp_affected: + kernel_affected = True + else: + kernel_affected = False + #TODO Implement else for hardend/xen/expand + + return kernel_affected + + +def compare_evaluation(kernel, compare): + 'Creates a comparison out of two evaluation instances' + + comparison = Comparison() + + if kernel.read != compare.read or kernel.arch != compare.arch: + BUG_ON('Kernels do not match: %s %s' % (kernel1.read, kernel2.read)) + return + + for item in kernel.affected: + if item not in compare.affected: + comparison.fixed.append(item) + + for item in compare.affected: + if item not in kernel.affected: + comparison.new.append(item) + + return comparison + + +def read_cve_file(directory, bugid): + 'Read a bug file created by collector' + + cves = list() + affected = list() + + filename = os.path.join(directory, bugid + '.xml') + + try: + with open(filename, 'r+') as xml_data: + memory_map = mmap.mmap(xml_data.fileno(), 0) + root = xml.etree.cElementTree.parse(memory_map).getroot() + except IOError, e: + BUG_ON(filename, e) + return None + + bugroot = root.find('bug') + + vul = Vulnerability(bugroot.find('bugid').text) + + for elem in ['arch', 'reported', 'reporter', 'status']: + setattr(vul, elem, bugroot.find(elem).text) + + affectedroot = bugroot.find('affected') + for item in affectedroot: + interval = interval_from_xml(item) + affected.append(interval) + + vul.affected = affected + + for item in root: + if item.tag == 'cve': + cve = Cve(item.find('cve').text) + if cve is None: + return None + + for elem in ['desc', 'published', 'refs', + 'severity', 'score', 'vector']: + element = item.find(elem) + if element is not None: + setattr(cve, elem, element.text) + else: + BUG_ON(filename, '(%s, \'No such element\')' % elem) + + cves.append(cve) + vul.cves = cves + + return vul + +#TODO Use Exceptions +def extract_version(release): + 'Extracts revision, source and version out of a release tag' + + match = REGEX['k_version'].match(release) + if not match: + BUG_ON('[Error] Release %s does not contain any valid information' % + release) + return None + + version, rest = match.groups() + + kernel = Kernel('vanilla') + kernel.revision = 'r0' + kernel.version = version + + for elem in (rest or '').split('-'): + if elem == 'sources': + pass + elif REGEX['rc_kernel'].match(elem): + kernel.version += '_' + elem + elif REGEX['git_kernel'].match(elem): + kernel.source = 'git' + kernel.revision = 'r' + REGEX['gitd'].match(elem).groups()[0] + elif REGEX['r_kernel'].match(elem): + kernel.revision = elem + elif elem in KERNEL_TYPES: + kernel.source = elem + elif elem != '': + BUG_ON('[Error] Dropping unknown version component \'%s\', \ + probably local tag.' % elem) + + return kernel + + +def all_version(source): + """ Given a kernel source name (e.g. vanilla), returns a Kernel object + for the latest revision in the tree, or None if none exists. """ + + versions = list() + + porttree = portage.db[portage.root]['porttree'] + matches = porttree.dbapi.xmatch('match-all', + 'sys-kernel/%s-sources' % source) + + for item in matches: + best = portage.versions.catpkgsplit(item) + if not best: + continue + + kernel = Kernel(best[1].replace('-sources', '')) + kernel.version = best[2] + kernel.revision = best[3] + + versions.append(kernel) + + return versions + -- cgit v1.2.3-65-gdbad