aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAvatar Danny van Dyk <dvandyk@exherbo.org> 2006-06-15 18:58:45 +0000
committerAvatar Danny van Dyk <dvandyk@exherbo.org> 2006-06-15 18:58:45 +0000
commit6ebb6cf8a7ab33fd0acee995d272807dc75532ca (patch)
treeab45022b97fb2f94b7afd91923bcbb75f810fb9f
parentad4f48cc5d8bc29d29e2e075c143d1d702f1cb75 (diff)
downloadpaludis-6ebb6cf8a7ab33fd0acee995d272807dc75532ca.tar.gz
paludis-6ebb6cf8a7ab33fd0acee995d272807dc75532ca.tar.xz
Add advisory specs, GLSA conversion utility. Fix AdvisoryFile to parse files according to specs.
-rw-r--r--doc/Makefile.am3
-rw-r--r--doc/doc_security_advisories.doxygen154
-rw-r--r--paludis/config_file.cc31
-rwxr-xr-xutils/glsa2txt.py178
4 files changed, 355 insertions, 11 deletions
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 6c233d5..6ca204f 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -13,7 +13,8 @@ docfiles = \
doc_bootstrap_howto.doxygen \
doc_changelog.doxygen \
doc_portage_differences.doxygen \
- doc_news.doxygen
+ doc_news.doxygen \
+ doc_security_advisories.doxygen
EXTRA_DIST = doxygen.conf.in header.html footer.html paludis.css $(docfiles)
diff --git a/doc/doc_security_advisories.doxygen b/doc/doc_security_advisories.doxygen
new file mode 100644
index 0000000..1f290f8
--- /dev/null
+++ b/doc/doc_security_advisories.doxygen
@@ -0,0 +1,154 @@
+/* vim: set ft=cpp tw=80 sw=4 et spell spelllang=en : */
+
+/**
+\page SecurityAdvisoriesSpecs Handling of Security Advisories
+
+\version 1
+\author Danny van Dyk <kugelfang@gentoo.org>
+\author Stefan Cornelius <dercorny@gentoo.org>
+
+
+\section SecurityAdvisoriesSpecsAbstract Abstract
+
+This specification describes how security advisory files should be named,
+stored, structured and handled by Paludis.
+
+
+\section SecurityAdvisoriesSpecsNaming Naming
+
+The format of file names shall be
+\code
+ advisory-YYYYMM-XX.conf
+\endcode
+where XX is a unique ID that is increased with every GLSA in the month and
+YYYYMM is the usual date notation.
+
+
+\section SecurityAdvisoriesSpecsStorage Storage
+
+Security advisories shall be stored as text files in
+\code
+ ${repo}/metadata/security
+\endcode
+by default. Files within subdirectories shall not be parsed by Paludis. However,
+the user can change this path in the repository's configuration file. The key's
+name shall be <code>securitydir</code>.
+
+Once tree wide Manifest support (aka as Manifest2 support) will be implemented,
+all advisories should be listed by a Manifest file and checked against this
+manifest before parsing them.
+
+
+\section SecurityAdvisoriesSpecsStructure Structure
+
+The file format shall consist of a RFC 822 style header and a trailing text
+body. This document describes specification <code>0</code>. The following keys shall be
+understood by Paludis:
+
+The following keys are mandatory and must be unique in the advisory.
+Paludis shall throw an exception otherwise.
+
+<table>
+ <tr>
+ <td>Id</td>
+ <td>Unique identifier of this advisory. Must be in <code>YYYYMM-XX</code> format and
+ reflect the suffix of the advisory's file name.</td>
+ </tr>
+ <tr>
+ <td>Title</td>
+ <td>A string that describes the kind of vulnerability indicated by the advisory.</td>
+ </tr>
+ <tr>
+ <td>Access</td>
+ <td>Describes if if the vulnerability can be exploited by <code>local</code> or
+ <code>remote</code> users.</td>
+ </tr>
+ <tr>
+ <td>Last-Modified</td>
+ <td>A date string that describes when the advisory was changed.</td>
+ </tr>
+ <tr>
+ <td>Revision</td>
+ <td>The advisory's revision number. It must start with <code>1.0</code> and should be
+ increased by <code>0.1</code> whenever the advisory's metadata was changed
+ substantially.</td>
+ <tr>
+ <td>Severity</td>
+ <td>The severity of the vulnerabilities described by the advisory.
+ Supported values are <code>high</code>, <code>normal</code> and <code>low</code>.</td>
+ </tr>
+ <tr>
+ <td>Spec-Version</td>
+ <td>The version of the specification that the advisory applies to should be
+ referenced here. As of this specification, the only valid value is <code>1</code>.</td>
+ </tr>
+</table>
+
+\note The following keys can be specified more than once.
+
+<table>
+ <tr>
+ <td>Affected</td>
+ <td>A string of one dependency atom or two ranged dependency atoms. When two
+ dependency atoms are given, their intersection must not describe and empty
+ set of packages.</td>
+ </tr>
+ <tr>
+ <td>Bug-Id</td>
+ <td>Identifier of a bug report that is associated with this advisory.\n
+
+ Must be in format <code>DISTRO#YY</code>, where <code>DISTRO</code> is a unique string
+ describing the distribution that the bug report is filed against and <code>YY</code>
+ is the bug's local identifier.
+
+ There is no restriction on the values of <code>DISTRO</code> besides the uniqueness,
+ but each used distribution identifier should be listed along a short
+ description and/or URL of the associated bug tracker in the text file
+\code
+ ${repo}/metadata/security/bugtracker
+\endcode </td>
+ </tr>
+ <tr>
+ <td>CVE</td>
+ <td>A string of one CVE IDs in the format <code>CVE-XXXX-XXXX</code>. Historically,
+ there have been CVE references prefixed by CAN instead of CVE. These
+ are explicitly permitted by this specification, but are to be treated as
+ deprecated.</td>
+ </tr>
+ <tr>
+ <td>Reference</td>
+ <td>URL to a public web-based advisory, announcement or (possibly) exploit.
+ Optionally, a descriptive string can be prepended.</td>
+ </tr>
+ <tr>
+ <td>Restart</td>
+ <td>The name of a service or init script that should be restarted by
+ Paludis after the vulnerable package has been replaced.</td>
+ </tr>
+ <tr>
+ <td>Unaffected</td>
+ <td>A string of one dependency atom or two ranged dependency atoms. When two
+ dependency atoms are given, their intersection must not describe and empty
+ set of packages.</td>
+ </tr>
+</table>
+
+The text body shall be separated from the aforementioned header by an empty
+line. The contents of the text body is not subject of this advisory.
+
+Paludis shall provide an eselect module to display advisory texts.
+
+
+\section Handling Handling
+
+Paludis will parse security advisories when it builds the built-in
+package set <code>security</code> or when it is called with command line option
+<code>--list-vulnerabilities</code>. The later shall support all modifiers, which are
+respected by other <code>--list</code> actions.
+
+All packages that match the contents of at least one <code>Affected</code> item and that
+do not match any <code>Unaffected</code> item will be treated as vulnerable. Paludis shall
+then search for a package in the same slot that matches at least one
+<code>Unaffected</code> items and does not match any <code>Affected</code> item. If several
+package versions match this criterion, Paludis shall select the highest version.
+*/
diff --git a/paludis/config_file.cc b/paludis/config_file.cc
index 5bd036e..c7e1334 100644
--- a/paludis/config_file.cc
+++ b/paludis/config_file.cc
@@ -394,17 +394,19 @@ AdvisoryFile::accept_line(const std::string & line) const
std::string key(line.substr(0, p)), value(line.substr(p + 1));
normalise_line(key);
normalise_line(value);
- if ((key == "Affected") || (key == "Unaffected") || (key == "Bug-Id") || (key == "Url")
- || (key == "Reviewed-By"))
+ if ((key == "Affected") || (key == "Bug-Id") || (key == "CVE") || (key == "Reference")
+ || (key == "Restart") || (key == "Unaffected"))
{
if (key == "Affected")
_affected.push_back(value);
else if (key == "Unaffected")
_unaffected.push_back(value);
-
- if (!_entries[key].empty())
- value = "\n" + value;
- _entries[key] += value;
+ else
+ {
+ if (! _entries[key].empty())
+ value = "\n" + value;
+ _entries[key] += value;
+ }
}
else
{
@@ -425,11 +427,20 @@ AdvisoryFile::sanitise()
if (_entries["Title"].empty())
throw AdvisoryFileError("Missing mandatory key: 'Title'.");
- if (_entries["Committed-By"].empty())
- throw AdvisoryFileError("Missing mandatory key: 'Committed-By'.");
+ if (_entries["Access"].empty())
+ throw AdvisoryFileError("Missing mandatory key: 'Access'.");
+
+ if (_entries["Last-Modified"].empty())
+ throw AdvisoryFileError("Missing mandatory key: 'Last-Modified'.");
+
+ if (_entries["Revision"].empty())
+ throw AdvisoryFileError("Missing mandatory key: 'Revision'.");
+
+ if (_entries["Severity"].empty())
+ throw AdvisoryFileError("Missing mandatory key: 'Severity'.");
- if (_entries["Reviewed-By"].empty())
- throw AdvisoryFileError("Missing mandatory key: 'Reviewed-by'.");
+ if (_entries["Spec-Version"].empty())
+ throw AdvisoryFileError("Missing mandatory key: 'Spec-Version'.");
}
NewsFile::NewsFile(const FSEntry & filename) :
diff --git a/utils/glsa2txt.py b/utils/glsa2txt.py
new file mode 100755
index 0000000..8a62e38
--- /dev/null
+++ b/utils/glsa2txt.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+
+# vim: set sw=4 sts=4 et foldmethod=syntax :
+
+# Copyright (c) 2005, 2006 Danny van Dyk <kugelfang@gentoo.org>
+#
+# This file is part of the Paludis package manager. Paludis is free software;
+# you can redistribute it and/or modify it under the terms of the GNU General
+# Public License version 2, as published by the Free Software Foundation.
+#
+# Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+# Place, Suite 330, Boston, MA 02111-1307 USA
+# Copiyright (C) 2005-2006 Danny van Dyk <kugelfang@gentoo.org>
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+import sys, os, xml.sax
+
+GLSA_TAGS = ['title', 'synopsis', 'package', 'unaffected', 'vulnerable', 'description', 'impact', 'workaround', 'uri', 'metadata', 'access', 'bug']
+
+class GLSA2TXT:
+ class GLSAHandler(xml.sax.ContentHandler):
+ glsa = {}
+ package = ""
+ arch = []
+ lasttag = ""
+ isrge = 0
+
+ def startElement(self, tag, attr):
+ if tag in GLSA_TAGS:
+ self.lasttag = tag
+ if tag == 'glsa':
+ self.glsa = { "Title": "", "synopsis": "", "description": "", "workaround": "", "Url": [],
+ "Committed-By": "", "Reviewed-By": "", "Access": "", "impact": "", "bug": "",
+ "Bug": []}
+ self.glsa["Id"] = attr.get('id')
+ self.glsa["Affected"] = []
+ self.glsa["Unaffected"] = []
+ if tag == 'package':
+ self.package = attr.get('name')
+ self.glsa[self.package + "-arch"] = attr.get('arch')
+ self.glsa[self.package + "-unaffected"] = ""
+ self.glsa[self.package + "-vulnerable"] = ""
+ if tag == 'unaffected' or tag == 'vulnerable':
+ range = attr.get('range')
+ if range[0] == 'r':
+ range = range[1:]
+ self.glsa[self.package + "-" + tag + "-combo"] = 1
+ else:
+ self.glsa[self.package + "-" + tag + "-combo"] = 0
+ if range == 'eq':
+ range = '='
+ elif range == 'ge':
+ range = '>='
+ elif range == 'le':
+ range = '<='
+ elif range == 'gt':
+ range = '>'
+ elif range == 'lt':
+ range = '<'
+ self.glsa[self.package + "-" + tag + "-range"] = range
+ self.glsa[self.package + "-" + tag + "-version"] = ""
+ self.glsa[self.package + "-" + tag + "-slot"] = attr.get('slot');
+ if tag == 'uri':
+ self.glsa["Url"].append(attr.get('link'))
+ if tag == 'metadata':
+ mtag = attr.get('tag')
+ if mtag == 'submitter':
+ self.lasttag = 'Committed-By'
+ elif mtag == 'bugReady':
+ self.lasttag = 'Reviewed-By'
+ else:
+ self.lasttag = ''
+ if tag == 'impact':
+ self.glsa["Severity"] = attr.get('type')
+
+ def characters(self, ch):
+ if self.lasttag in ['access', 'title']:
+ self.glsa[self.lasttag.capitalize()] += ch
+ if self.lasttag in ['synopsis', 'description', 'workaround', 'impact', 'bug']:
+ self.glsa[self.lasttag] += ch
+ if self.lasttag in ['unaffected', 'vulnerable']:
+ self.glsa[self.package + "-" + self.lasttag + "-version"] += ch
+ if self.lasttag in ['Committed-By', 'Reviewed-By']:
+ self.glsa[self.lasttag] += ch.replace('\n', '')
+
+ def endElement(self, tag):
+ if tag in GLSA_TAGS:
+ self.lasttag = ""
+ if tag == 'title':
+ desc = self.glsa["Title"] + "\n"
+ desc += '=' * len(self.glsa["Title"]) + "\n"
+ self.glsa["Description"] = desc
+ if tag == 'bug':
+ self.glsa["Bug"].append(self.glsa["bug"])
+ self.glsa["bug"] = ""
+ if tag in ['synopsis', 'description', 'workaround', 'impact']:
+ desc = "\n" + tag.title() + "\n"
+ desc += "'" * len(tag) + "\n\n"
+ lines = []
+ for x in self.glsa[tag].split('\n'):
+ y = x.strip()
+ if not y == "":
+ lines.append(y)
+ desc += "\n".join(lines) + "\n"
+ self.glsa["Description"] += desc
+ if tag in ['unaffected', 'vulnerable']:
+ package = self.package
+ archs = self.glsa[package + "-arch"]
+ slot = self.glsa[package + "-" + tag + "-slot"]
+ combo = self.glsa[package + "-" + tag + "-combo"]
+ range = self.glsa[package + "-" + tag + "-range"]
+ version = self.glsa[package + "-" + tag + "-version"]
+ if not slot == None:
+ slot = ":" + slot
+ else:
+ slot = ""
+ entry = range + package + "-" + version + slot
+ if combo == 1:
+ vitems = version.split('-r')
+ if not vitems[0:-1] == []:
+ version = "-r".join(vitems[0:-1])
+ entry += " ~" + package + "-" + version + slot
+ archs = self.glsa[self.package + "-arch"]
+ if tag == 'vulnerable':
+ Tag = 'Affected'
+ else:
+ Tag = 'Unaffected'
+ if not archs == '*':
+ for arch in archs.split(' '):
+ self.glsa[Tag].append(arch + "? ( " + entry + " )")
+ else:
+ self.glsa[Tag].append(entry);
+
+ def write_advisory(self, handler):
+ glsa = handler.glsa
+ print "Id: " + glsa["Id"]
+ print "Title: " + glsa["Title"]
+ print "Access: " + glsa["Access"]
+ print "Last-Modified: $Date$"
+ print "Revision: 1.0"
+ print "Severity: " + glsa["Severity"]
+ print "Spec-Version: 1"
+ for x in glsa["Affected"]:
+ print "Affected: " + x
+ for x in glsa["Bug"]:
+ print "Bug-Id: Gentoo#" + x
+ for x in glsa["Url"]:
+ print "Reference: " + x
+ for x in glsa["Unaffected"]:
+ print "Unaffected: " + x
+
+ print
+ print glsa["Description"]
+
+ def process(self):
+ try:
+ parser = xml.sax.make_parser("xml.sax.drivers2.drv_xmlproc")
+ handler = GLSA2TXT.GLSAHandler()
+ parser.setContentHandler(handler)
+ #parser.setFeature("http://xml.org/sax/features/external-general-entities",False)
+ parser.parse(sys.argv[1])
+
+ self.write_advisory(handler)
+
+ except (xml.sax.SAXException, IOError), e:
+ print "Error parsing " + sys.argv[0] + "!"
+ print e.getMessage()
+
+if __name__ == '__main__':
+ x = GLSA2TXT()
+ x.process()