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."""


_______________________________________________
Secure-testing-commits mailing list
Secure-testing-commits@lists.alioth.debian.org
http://lists.alioth.debian.org/mailman/listinfo/secure-testing-commits

Reply via email to