Author: fw Date: 2005-09-13 14:08:22 +0000 (Tue, 13 Sep 2005) New Revision: 1951 Added: bin/update-vulnerabilities Modified: Makefile bin/update-packages lib/python/bugs.py lib/python/debian_support.py lib/python/security_db.py Log: First step towards calculating sets of vulnerable packages. This is currently directed towards testing (but does not yet process the secure-testing archive). A new table is added, so "make clean" is required. The remaining problem (besides potential bugs in the code) is how to deal with kernel updates, IOW how to detect them and ignore them. bin/update-vulnerabilities: New script, updates the bugs_status table. lib/python/bugs.py (PackageNote.affects): Fix all kinds of errors. The code never ran before, it seems. 8-/ (PackageNote.fixedVersion): Add. (BugBase.hasTODO): Add. (BugReservedCVE, BugRejectedCVE): Mark as not-for-us. (FileBase.rawRecords): Mark all un-annotated bugs after STOP: field as not-for-us. lib/python/security_db.py (DB.initSchema): Add table bugs_status. (DB.finishBugs): Run to completion even if there are conflicting CAN/CVE entries. (DB.getVersion, calculateVulnerabilities): New methods. (test): Update. lib/python/debian_support.py (Version): Add a type check. Makefile: Add stamps/calc-vulns target. bin/update-packages: Fix typo in comment. Modified: Makefile ==================================================================--- Makefile 2005-09-13 13:53:07 UTC (rev 1950) +++ Makefile 2005-09-13 14:08:22 UTC (rev 1951) @@ -13,7 +13,8 @@ PACKAGE_FILES = $(wildcard data/packages/*_Sources) \ $(wildcard data/packages/*_Packages) -all: stamps/bug-lists-imported stamps/packages-imported +all: stamps/bug-lists-imported stamps/packages-imported \ + stamps/calc-vulns stamps/bug-lists-imported: bin/update-bug-list-db \ $(BUG_LISTS) $(PYTHON_MODULES) @@ -30,6 +31,12 @@ fi touch $@ +stamps/calc-vulns: stamps/bug-lists-imported stamps/packages-imported + if test -e stamps/packages-downloaded ; then \ + $(PYTHON) bin/update-vulnerabilities ; \ + fi + touch $@ + clean: -rm data/security.db -rm stamps/*-* Modified: bin/update-packages ==================================================================--- bin/update-packages 2005-09-13 13:53:07 UTC (rev 1950) +++ bin/update-packages 2005-09-13 14:08:22 UTC (rev 1951) @@ -1,6 +1,6 @@ #!/usr/bin/python -# This script download and imports Debian package files. +# This script downloads and imports Debian package files. import errno import os Added: bin/update-vulnerabilities ==================================================================--- bin/update-vulnerabilities 2005-09-13 13:53:07 UTC (rev 1950) +++ bin/update-vulnerabilities 2005-09-13 14:08:22 UTC (rev 1951) @@ -0,0 +1,32 @@ +#!/usr/bin/python + +# This script recalculates the vulnerability information in the +# security database. + +import errno +import os +import os.path +import string +import sys + +def setup_paths(): + check_file = ''lib/python/debian_support.py'' + path = os.getcwd() + while 1: + if os.path.exists("%s/%s" % (path, check_file)): + sys.path = [path + ''/lib/python''] + sys.path + return path + idx = string.rfind(path, ''/'') + if idx == -1: + raise ImportError, "could not setup paths" + path = path[0:idx] +root_path = setup_paths() + +import security_db + +db_file = root_path + ''/data/security.db'' +assert os.path.exists(db_file) +db = security_db.DB(db_file) +c = db.writeTxn() +db.calculateVulnerabilities(c) +db.commit(c) Property changes on: bin/update-vulnerabilities ___________________________________________________________________ Name: svn:executable + * Modified: lib/python/bugs.py ==================================================================--- lib/python/bugs.py 2005-09-13 13:53:07 UTC (rev 1950) +++ lib/python/bugs.py 2005-09-13 14:08:22 UTC (rev 1951) @@ -76,7 +76,7 @@ if type(version) == types.StringType: version = debian_support.Version(version) - if type(release) == types.ReleaseType: + if type(release) == types.StringType: release = Release(release) if release is None: @@ -89,8 +89,15 @@ # If there''s a release spec, it must match ours. return False # Standard version comparison if the releases match. - return self.version is None or version < self.version + return self.fixed_version is None or version < self.fixed_version + def fixedVersion(self): + """ Returns a string noting that the bug was fixed, or ''unfixed''.""" + if self.fixed_version: + return "fixed in %s" % self.fixed_version + else: + return "unfixed" + def writeDB(self, cursor, bug_name): """Writes the object to an SQLite database. @@ -209,6 +216,13 @@ else: return '''' + def hasTODO(self): + """Returns True if the bug has a TODO item.""" + for (t, c) in self.comments: + if t == "TODO": + return True + return False + def writeDB(self, cursor): """Writes the record to an SQLite3 database.""" @@ -302,6 +316,8 @@ if comments is None: comments = [] BugBase.__init__(self, fname, lineno, None, name, "RESERVED", comments) + # for-us bugs are upgraded to real Bug objects. + self.not_for_us = True def cveStatus(self): return ''RESERVED'' @@ -309,6 +325,8 @@ """Class for rejected CVE entries.""" def __init__(self, fname, lineno, name): BugBase.__init__(self, fname, lineno, None, name, "REJECTED", []) + # for-us bugs are upgraded to real Bug objects. + self.not_for_us = True def cveStatus(self): return ''REJECTED'' @@ -375,12 +393,12 @@ self.getLine() record = [] + after_stop = False while self.line: first_line = self.lineno if self.re_stop.match(self.line): - # Theoretically, we could stop here, but we want - # syntax checks for the remaining records, too. + after_stop = True self.getLine() continue @@ -408,6 +426,11 @@ break # line contains the next line at this point. + if after_stop and len(record) == 0: + # Patch in not-for-us field, so that bugs after STOP: + # are ignored. + record = [(first_line, ''NOTE: not-for-us (entry too old)'')] + yield (first_line, date, record_name, description, record) def __iter__(self): Modified: lib/python/debian_support.py ==================================================================--- lib/python/debian_support.py 2005-09-13 13:53:07 UTC (rev 1950) +++ lib/python/debian_support.py 2005-09-13 14:08:22 UTC (rev 1951) @@ -57,6 +57,7 @@ def __init__(self, version): """Creates a new Version object.""" + assert type(version) == types.StringType, `version` self.__asString = version self.__parsed = self.__parse(version) Modified: lib/python/security_db.py ==================================================================--- lib/python/security_db.py 2005-09-13 13:53:07 UTC (rev 1950) +++ lib/python/security_db.py 2005-09-13 14:08:22 UTC (rev 1951) @@ -148,6 +148,13 @@ normalized_target TEXT NOT NULL DEFAULT '''', PRIMARY KEY (source, target))""") + cursor.execute("""CREATE TABLE bugs_status + (bug_name TEXT NOT NULL, + release TEXT NOT NULL, + note INTEGER NOT NULL, + reason TEXT NOT NULL, + PRIMARY KEY (bug_name, release, note))""") + def updateSources(self, cursor, release, archive, packages): """Reads a Sources file and adds it to the database. @@ -347,12 +354,12 @@ found = False for (t,) in list(cursor.execute("""SELECT name FROM bugs WHERE name IN (?, ?)""", (can_target, cve_target))): - assert not found, t cursor.execute("""UPDATE bugs_xref SET normalized_target = ? WHERE source = ? AND target = ?""", (t, source, target)) found = True + break if not found: b = bugs.BugFromDB(cursor, source) warnings.append\ @@ -375,8 +382,111 @@ % (b.source_file, b.source_line, target)) return warnings + + def getVersion(self, cursor, release, package): + """Returns the version number for package in release. + + Package can be a source or binary package. Binary package + versions take precedence. + + Security updates etc. are not considered.""" + + versions = list(cursor.execute( + """SELECT version FROM binary_packages + WHERE package = ? AND release = ?""", (package, release))) + if versions: + return min(map(lambda (v,): debian_support.Version(v), versions)) + + versions = list(cursor.execute( + """SELECT version FROM source_packages + WHERE package = ? AND release = ?""", (package, release))) + if versions: + assert len(versions) == 1 + return debian_support.Version(versions[0][0]) + + return None + + def calculateVulnerabilities(self, cursor): + """Calculate vulnerable packages. + + To each package note, a release-specific vulnerability status + is attached. Currently, only etch/testing is processed. + """ + + cursor.execute("DELETE FROM bugs_status") + + def markVulnerable(bug, release, note, reason): + cursor.execute("""INSERT INTO bugs_status + (bug_name, release, note, reason) VALUES (?, ?, ?, ?)""", + (bug.name, release, note, reason)) + + def calcVuln(bug): + vulnerable = False + note_found = False + + for n in bug.notes: + # ignore all notes conditioned on releases. + if n.release is not None: + continue + note_found = True + v = self.getVersion(cursor, ''etch'', n.package) + if v is None: + # Package is not in testing, go on. + continue + if n.affects(v): + vulnerable = True + markVulnerable(b, ''etch'', n.id, + "%s (%s) is vulnerable, %s" + % (n.package, v, n.fixedVersion())) + + if bug.hasTODO(): + vulnerable = True + markVulnerable(b, ''etch'', 0, ''TODO items present'') + elif not note_found: + vulnerable = True + markVulnerable(b, ''etch'', 0, ''status is unclear'') + + return vulnerable + + # First handle the DSAs. Cache results in DSA_status (used + # for CAN/CVE below). + + bug_names = list(cursor.execute( + "SELECT name FROM bugs WHERE name LIKE ''DSA-%''")) + DSA_status = {} + for (bug_name,) in bug_names: + b = bugs.BugFromDB(cursor, bug_name) + DSA_status[bug_name] = calcVuln(b) + + # Process the CAN/CVE/FAKE entries. If an entry has no + # package annotations, but it references a non-vulnerable DSA, + # we assume that the current is not affect either. + + bug_names = list(cursor.execute( + """SELECT name FROM bugs + WHERE (NOT not_for_us) + AND NOT (name LIKE ''DSA-%'' OR name LIKE ''DTSA-%'')""")) + for (bug_name,) in bug_names: + b = bugs.BugFromDB(cursor, bug_name) + if b.notes: + calcVuln(b) + continue + + if b.hasTODO(): + markVulnerable(b, ''etch'', 0, ''TODO items present'') + continue - + dsa_found = False + for x in b.xref: + if x[0:4] == ''DSA-'': + dsa_found = True + if DSA_status[x]: + markVulnerable(b, ''etch'', 0, + ''vulnerability %s referenced'' % x) + break + if not dsa_found: + markVulnerable(b, ''etch'', 0, ''status is unclear'') + def check(self, cursor=None): """Runs a simple consistency check and prints the results.""" @@ -442,8 +552,14 @@ db.updatePackages(cursor, ''sarge'', ''main'', ''i386'', debian_support.PackageFile(data_prefix + ''sarge_main_i386_Packages'')) + db.updatePackages(cursor, ''sarge'', ''main'', ''ia64'', + debian_support.PackageFile(data_prefix + + ''sarge_main_ia64_Packages'')) db.commit(cursor) + assert str(db.getVersion(cursor, ''sarge'', ''ale'')) == ''0.7.1-1'', \ + db.getVersion(cursor, ''sarge'', ''ale'') + # db.check(cursor) cursor = db.writeTxn()