summary refs log tree commit diff
path: root/guix/cve.scm
diff options
context:
space:
mode:
Diffstat (limited to 'guix/cve.scm')
-rw-r--r--guix/cve.scm177
1 files changed, 177 insertions, 0 deletions
diff --git a/guix/cve.scm b/guix/cve.scm
new file mode 100644
index 0000000000..a7b0bde6dc
--- /dev/null
+++ b/guix/cve.scm
@@ -0,0 +1,177 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2015 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix cve)
+  #:use-module (guix utils)
+  #:use-module (guix http-client)
+  #:use-module (sxml ssax)
+  #:use-module (web uri)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-19)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 vlist)
+  #:export (vulnerability?
+            vulnerability-id
+            vulnerability-packages
+
+            xml->vulnerabilities
+            current-vulnerabilities
+            vulnerabilities->lookup-proc))
+
+;;; Commentary:
+;;;
+;;; This modules provides the tools to fetch, parse, and digest part of the
+;;; Common Vulnerabilities and Exposures (CVE) feeds provided by the US NIST
+;;; at <https://nvd.nist.gov/download.cfm#CVE_FEED>.
+;;;
+;;; Code:
+
+(define-record-type <vulnerability>
+  (vulnerability id packages)
+  vulnerability?
+  (id         vulnerability-id)
+  (packages   vulnerability-packages))
+
+(define %cve-feed-uri
+  (string->uri
+   "https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-Modified.xml.gz"))
+
+(define %ttl
+  ;; According to <https://nvd.nist.gov/download.cfm#CVE_FEED>, feeds are
+  ;; updated "approximately every two hours."
+  (* 3600 3))
+
+(define (call-with-cve-port proc)
+  "Pass PROC an input port from which to read the CVE stream."
+  (let ((port (http-fetch/cached %cve-feed-uri #:ttl %ttl)))
+    (dynamic-wind
+      (const #t)
+      (lambda ()
+        (call-with-decompressed-port 'gzip port
+          proc))
+      (lambda ()
+        (close-port port)))))
+
+(define %cpe-package-rx
+  ;; For applications: "cpe:/a:VENDOR:PACKAGE:VERSION".
+  (make-regexp "^cpe:/a:([^:]+):([^:]+):([^:]+)"))
+
+(define (cpe->package-name cpe)
+  "Converts the Common Platform Enumeration (CPE) string CPE to a package
+name, in a very naive way.  Return #f if CPE does not look like an application
+CPE string."
+  (and=> (regexp-exec %cpe-package-rx (string-trim-both cpe))
+         (lambda (matches)
+           (cons (match:substring matches 2)
+                 (match:substring matches 3)))))
+
+(define %parse-vulnerability-feed
+  ;; Parse the XML vulnerability feed from
+  ;; <https://nvd.nist.gov/download.cfm#CVE_FEED> and return a list of
+  ;; vulnerability objects.
+  (ssax:make-parser NEW-LEVEL-SEED
+                    (lambda (elem-gi attributes namespaces expected-content
+                                     seed)
+                      (match elem-gi
+                        ((name-space . 'entry)
+                         (cons (assoc-ref attributes 'id) seed))
+                        ((name-space . 'vulnerable-software-list)
+                         (cons '() seed))
+                        ((name-space . 'product)
+                         (cons 'product seed))
+                        (x seed)))
+
+                    FINISH-ELEMENT
+                    (lambda (elem-gi attributes namespaces parent-seed
+                                     seed)
+                      (match elem-gi
+                        ((name-space . 'entry)
+                         (match seed
+                           (((? string? id) . rest)
+                            ;; Some entries have no vulnerable-software-list.
+                            rest)
+                           ((products id . rest)
+                            (match (filter-map cpe->package-name products)
+                              (()
+                               ;; No application among PRODUCTS.
+                               rest)
+                              (packages
+                               (cons (vulnerability id (reverse packages))
+                                     rest))))))
+                        (x
+                         seed)))
+
+                    CHAR-DATA-HANDLER
+                    (lambda (str _ seed)
+                      (match seed
+                        (('product software-list . rest)
+                         ;; Add STR to the vulnerable software list this
+                         ;; <product> tag is part of.
+                         (cons (cons str software-list) rest))
+                        (x x)))))
+
+(define (xml->vulnerabilities port)
+  "Read from PORT an XML feed of vulnerabilities and return a list of
+vulnerability objects."
+  (reverse (%parse-vulnerability-feed port '())))
+
+(define (current-vulnerabilities)
+  "Return the current list of Common Vulnerabilities and Exposures (CVE) as
+published by the US NIST."
+  (call-with-cve-port
+   (lambda (port)
+     ;; XXX: The SSAX "error port" is used to send pointless warnings such as
+     ;; "warning: Skipping PI".  Turn that off.
+     (parameterize ((current-ssax-error-port (%make-void-port "w")))
+       (xml->vulnerabilities port)))))
+
+(define (vulnerabilities->lookup-proc vulnerabilities)
+  "Return a lookup procedure built from VULNERABILITIES that takes a package
+name and optionally a version number.  When the version is omitted, the lookup
+procedure returns a list of version/vulnerability pairs; otherwise, it returns
+a list of vulnerabilities affection the given package version."
+  (define table
+    ;; Map package names to lists of version/vulnerability pairs.
+    (fold (lambda (vuln table)
+            (match vuln
+              (($ <vulnerability> id packages)
+               (fold (lambda (package table)
+                       (match package
+                         ((name . version)
+                          (vhash-cons name (cons version vuln)
+                                      table))))
+                     table
+                     packages))))
+          vlist-null
+          vulnerabilities))
+
+  (lambda* (package #:optional version)
+    (vhash-fold* (if version
+                     (lambda (pair result)
+                       (match pair
+                         ((v . vuln)
+                          (if (string=? v version)
+                              (cons vuln result)
+                              result))))
+                     cons)
+                 '()
+                 package table)))
+
+;;; cve.scm ends here