Author: fw Date: 2005-09-16 08:14:54 +0000 (Fri, 16 Sep 2005) New Revision: 2015 Modified: lib/python/bugs.py lib/python/security_db.py Log: lib/python/security_db.py (DB): Update schema versioning code. Replace table bugs_status with bug_status. Add bug_name and release columns to source_package_status and binary_package_status. (DB.calculateVulnerabilities): First attempt at bug status calculation. It''s rather broken, unfortunately. lib/python/bugs.py (BugFromDB.getStatus): New method, to get the results of the bug status calculation. Modified: lib/python/bugs.py ==================================================================--- lib/python/bugs.py 2005-09-16 05:02:08 UTC (rev 2014) +++ lib/python/bugs.py 2005-09-16 08:14:54 UTC (rev 2015) @@ -412,6 +412,16 @@ return result + def getStatus(self, cursor): + """Calculate bug status. + + Returns list of tuples (RELEASE, STATUS, REASON).""" + + return list(cursor.execute( + """SELECT release, status, reason + FROM bug_status WHERE bug_name = ?""", + (self.name,))) + class BugReservedCVE(BugBase): """Class for reserved CVE entries.""" def __init__(self, fname, lineno, name, comments=None): Modified: lib/python/security_db.py ==================================================================--- lib/python/security_db.py 2005-09-16 05:02:08 UTC (rev 2014) +++ lib/python/security_db.py 2005-09-16 08:14:54 UTC (rev 2015) @@ -96,7 +96,7 @@ ''sarge'' : ''stable'', ''woody'': ''oldstable''} - self.schema_version = 5 + self.schema_version = 8 c = self.cursor() for (v,) in c.execute("PRAGMA user_version"): @@ -139,6 +139,12 @@ """Creates the database schema.""" cursor = self.cursor() + # Set the schema version to an invalid value which is + # different from zero. We can use this to detect a partially + # created schema. + + cursor.execute("PRAGMA user_version = 1") + # This gives us better performance (it''s usually the file # system block size). @@ -229,17 +235,21 @@ normalized_target TEXT NOT NULL DEFAULT '''', PRIMARY KEY (source, target))""") - cursor.execute("""CREATE TABLE bugs_status + cursor.execute("""CREATE TABLE bug_status (bug_name TEXT NOT NULL, release TEXT NOT NULL, - note INTEGER NOT NULL, + status TEXT NOT NULL + CHECK (status IN (''vulnerable'', ''fixed'', ''unknown'', + ''partially-fixed'', ''todo'')), reason TEXT NOT NULL, - PRIMARY KEY (bug_name, release, note))""") + PRIMARY KEY (bug_name, release))""") cursor.execute("""CREATE TABLE source_package_status (note INTEGER NOT NULL, package INTEGER NOT NULL, vulnerable INTEGER NOT NULL, + bug_name TEXT NOT NULL, + release TEXT NOT NULL, PRIMARY KEY (note, package))""") cursor.execute( """CREATE INDEX source_package_status_package @@ -249,16 +259,16 @@ (note INTEGER NOT NULL, package INTEGER NOT NULL, vulnerable INTEGER NOT NULL, + bug_name TEXT NOT NULL, + release TEXT NOT NULL, PRIMARY KEY (note, package))""") cursor.execute( """CREATE INDEX binary_package_status_package ON binary_package_status(package)""") - - # Put this at the end. Any exception will leave the schema - # version at 0, so we automatically recreate the schema once - # the application is started after the underlying error has - # been fixed. - + cursor.execute( + """CREATE INDEX binary_package_status_bug_name + ON binary_package_status(bug_name)""") + cursor.execute("PRAGMA user_version = %d" % self.schema_version) def filePrint(self, filename): @@ -759,13 +769,13 @@ WHERE package_kind = ''unknown'' AND EXISTS (SELECT * FROM binary_packages AS p WHERE p.name = package_notes.package)""") - for (bug_name, s_package, b_package) in cursor.execute( + for (bug_name, s_package, b_package) in list(cursor.execute( """SELECT DISTINCT s.bug_name, s.package, b.package FROM package_notes AS s, package_notes AS b, binary_packages AS p WHERE s.bug_name = b.bug_name AND s.package_kind = ''source'' AND b.package_kind = ''binary'' - AND p.name = b.package AND p.source = s.package"""): + AND p.name = b.package AND p.source = s.package""")): b = bugs.BugFromDB(cursor, bug_name) result.append("%s:%d: source and binary package annotations" % (b.source_file, b.source_line)) @@ -774,10 +784,20 @@ result.append("%s:%d: binary package: %s" % (b.source_file, b.source_line, b_package)) + for (bug_name, pkg_name, release) in list(cursor.execute( + """SELECT DISTINCT bug_name, package, release FROM package_notes + WHERE package_kind = ''binary'' AND release <> ''''""")): + b = bugs.BugFromDB(cursor, bug_name) + result.append("%s:%d: binary package %s used with release %s" + % (b.source_file, b.source_line, `pkg_name`, + `release`)) + if self.verbose: print " remove old status" cursor.execute("DELETE FROM source_package_status") cursor.execute("DELETE FROM binary_package_status") + cursor.execute("DELETE FROM bug_status") + if self.verbose: print " calculate package status" print " source packages (unqualified)" @@ -785,7 +805,8 @@ cursor.execute( """INSERT INTO source_package_status SELECT n.id, p.rowid, - n.fixed_version IS NULL OR p.version_id < n.fixed_version_id + n.fixed_version IS NULL OR p.version_id < n.fixed_version_id, + n.bug_name, p.release FROM package_notes AS n, source_packages AS p WHERE n.release = '''' AND p.name = n.package""") @@ -797,7 +818,8 @@ cursor.execute( """INSERT OR REPLACE INTO source_package_status SELECT n.id, p.rowid, - n.fixed_version IS NULL OR p.version_id < n.fixed_version_id + n.fixed_version IS NULL OR p.version_id < n.fixed_version_id, + n.bug_name, p.release FROM package_notes AS n, source_packages AS p WHERE p.name = n.package AND p.release = n.release""") @@ -807,28 +829,34 @@ cursor.execute( """INSERT INTO binary_package_status SELECT n.id, p.rowid, - n.fixed_version IS NULL OR p.source_version_id < n.fixed_version_id + n.fixed_version IS NULL + OR p.source_version_id < n.fixed_version_id, + n.bug_name, p.release FROM package_notes AS n, binary_packages AS p WHERE n.release = '''' AND p.source = n.package""") cursor.execute( """INSERT OR REPLACE INTO binary_package_status SELECT n.id, p.rowid, - n.fixed_version IS NULL OR p.source_version_id < n.fixed_version_id + n.fixed_version IS NULL + OR p.source_version_id < n.fixed_version_id, + n.bug_name, p.release FROM package_notes AS n, binary_packages AS p WHERE p.source = n.package AND p.release = n.release""") - # Almost the same binary packages. We prefer source packages, - # so we skip all notes which have already source packages - # attached. (Of course, we do not have to add status - # information for binary package separately.) + # Almost the samefor binary packages. We prefer interpreting + # package names as source packages, so we only process the + # notes which refer to binary packages. (Of course, we do not + # have to add status information for binary package + # separately.) if self.verbose: print " binary packages (unqualified)" cursor.execute( """INSERT INTO binary_package_status SELECT n.id, p.rowid, - n.fixed_version IS NULL OR p.version_id < n.fixed_version_id + n.fixed_version IS NULL OR p.version_id < n.fixed_version_id, + n.bug_name, p.release FROM package_notes AS n, binary_packages AS p WHERE n.release = '''' AND p.name = n.package AND n.package_kind = ''binary''""") @@ -838,114 +866,178 @@ cursor.execute( """INSERT OR REPLACE INTO binary_package_status SELECT n.id, p.rowid, - n.fixed_version IS NULL OR p.version_id < n.fixed_version_id + n.fixed_version IS NULL OR p.version_id < n.fixed_version_id, + n.bug_name, p.release FROM package_notes AS n, binary_packages AS p WHERE p.name = n.package AND p.release = n.release AND n.package_kind = ''binary''""") - return result - + # Calculate the release-specific bug status. if self.verbose: - print " clearing old data" - cursor.execute("DELETE FROM bugs_status") + print " calculate release status" - def markVulnerable(bug, release, note, reason): - cursor.execute("""INSERT INTO bugs_status - (bug_name, release, note, reason) VALUES (?, ?, ?, ?)""", - (bug.name, release, note, reason)) + c = self.cursor() - 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: # assumes ''etch'' + # Packages relevant for testing. This includes the packages + # from unstable. + + package_by_release = {} + binary_packages_in_testing = {} + for x in (''etch'', ''sid''): + package_by_release[x] = {} + for (pkg_name, release) in cursor.execute( + """SELECT name, release FROM binary_packages + WHERE release IN (''etch'', ''sid'')"""): + package_by_release[release][pkg_name] = True + binary_packages_in_testing[pkg_name] = True + + for (bug_name,) in cursor.execute( + "SELECT name FROM bugs WHERE NOT not_for_us"): + + # The algorith below roughly proceeds as follows: + # + # For each package: + # Is this package in testing/unstable? If not, exit. + # + # Differentiate between the following cases: + # For all architectures with security support, the + # package in testing is not vulnerable (fully fixed) + # + # For all architectures with security support, the + # package is not vulnerable in testing or + # testing-security, and there exists a package (on a + # security support architecture) which is vulnerable + # in testing (partially fixed) + # + # There exists an architecture with security support + # where the package is fixed in testing, and there + # exists a non-vulnerable architecture in testing + # (should not happen, partially-fixed) + # + # Same as the preceding case, but including + # test-security; this can actually happen + # (partially-fixed, secure-testing is out-of-date on + # some architectures) + # + # There exists an architecture with security support + # where the package is fixed in unstable, and all + # packages in testing are vulnerable, and the + # package is in testing (fixed in unstable) + # + # For some supported architecture the package is in + # testing, and vulnerable. It is + # + # The package is not in testing on any supported + # architecture, + # + # At least this is the plan. The code below probably does + # something slightly different. 8-( + + available_archs = {} + vulnerable_in_other = {} + vulnerable_in_testing = {} + fixed_in_testing = {} + fixed_in_security = {} + + def record_archs_per_package(dict, pkg, archs): + if not dict.has_key(pkg): + dict[pkg] = {} + for arch in archs.split('',''): + dict[pkg][arch] = True + + for (pkg_name, release, subrelease, archs, vulnerable) \ + in c.execute( + """SELECT DISTINCT + p.name, p.release, p.subrelease, p.archs, vulnerable + FROM binary_package_status AS s, binary_packages AS p + WHERE s.bug_name = ? AND p.rowid = s.package""", (bug_name,)): + if not binary_packages_in_testing.has_key(pkg_name): 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: - # We found no matching note. Maybe all packages have - # been removed? - if bug.notes: - for n in bug.notes: - if self.releaseContainsPackage \ - (cursor, ''etch'', n.package): - markVulnerable(b, ''etch'', 0, - ''applicable package note for %s missing'' - % n.package) - vulnerable = True - else: - vulnerable = True - markVulnerable(b, ''etch'', 0, ''status is unclear'') + record_archs_per_package(available_archs, pkg_name, archs) - return vulnerable + if release == ''etch'': + if vulnerable: + record_archs_per_package(vulnerable_in_testing, + pkg_name, archs) + else: + if subrelease == '''': + record_archs_per_package(fixed_in_testing, + pkg_name, archs) + record_archs_per_package(fixed_in_security, + pkg_name, archs) + elif subrelease == ''security'': + record_archs_per_package(fixed_in_security, + pkg_name, archs) + elif vulnerable: + record_archs_per_package(vulnerable_in_other, + pkg_name, archs) - # First handle the DSAs. Cache results in DSA_status (used - # for CAN/CVE below). + def record(status, reason): + if status <> ''vulnerable'': + ((has_todo,),) = c.execute( + """SELECT EXISTS (SELECT * FROM bugs_notes + WHERE bug_name = ? AND typ = ''TODO'')""", + (bug_name,)) + if has_todo: + status = ''todo'' + reason = ''see notes below'' + + c.execute( + """INSERT INTO bug_status + (bug_name, release, status, reason) + VALUES (?, ''testing'', ?, ?)""", + (bug_name, status, reason)) - if self.verbose: - print " reading DSAs" - bug_names = list(cursor.execute( - """SELECT name FROM bugs - WHERE name LIKE ''DSA-%'' AND NOT not_for_us""")) - DSA_status = {} - if self.verbose: - print " rating DSAs" - 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. + if len(available_archs.keys()) == 0: + record(''fixed'', + ''package(s) neither in testing nor in unstable'') + continue - if self.verbose: - print " reading other entries" - bug_names = list(cursor.execute( - """SELECT name FROM bugs - WHERE (NOT not_for_us) - AND NOT (name LIKE ''DSA-%'' OR name LIKE ''DTSA-%'')""")) - if self.verbose: - print " rating other entries" - for (bug_name,) in bug_names: - b = bugs.BugFromDB(cursor, bug_name) - if b.notes: - calcVuln(b) + totally_unfixed_packages = [] + testing_missing_archs = {} + security_missing_archs = {} + for (pkg_name, archs) in vulnerable_in_other.items(): + fixed_somewhere = False + for arch in archs.keys(): + if fixed_in_testing.get(pkg_name, {}).has_key(arch): + fixed_somewhere = True + else: + testing_missing_archs[arch] = True + if fixed_in_security.get(pkg_name, {}).has_key(arch): + fixed_somewhere = True + else: + security_missing_archs[arch] = True + if not fixed_somewhere: + totally_unfixed_packages.append(pkg_name) + + if totally_unfixed_packages: + totally_unfixed_packages.sort() + if len(totally_unfixed_packages) == 1: + record(''vulnerable'', ''package %s is vulnerable'' + % totally_unfixed_packages[0]) + else: + record(''vulnerable'', ''packages %s are vulnerable'' + % '', ''.join(totally_unfixed_packages)) continue - if b.hasTODO(): - markVulnerable(b, ''etch'', 0, ''TODO items present'') + if security_missing_archs.keys(): + record(''partially-fixed'', + ''fixed via testing-security, '' + + ''but architectures out of date: '' + + '', ''.join(security_missing_archs.keys())) 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'') - - if self.verbose: - print " finished" + if testing_missing_archs.keys(): + record(''partially-fixed'', ''fixed in testing-security'') + continue + + record(''fixed'', ''packages are not vulnerable'') + return result + def check(self, cursor=None): """Runs a simple consistency check and prints the results."""