diff mbox series

[bug#47193,2/2] lint: Indicate CVE severity.

Message ID 20210316160653.9891-2-me@tobias.gr
State New
Headers show
Series [bug#47193,1/2] lint: Sort possible vulnerabilities. | expand

Checks

Context Check Description
cbaines/submitting builds success
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch success View Laminar job
cbaines/issue success View issue

Commit Message

Tobias Geerinckx-Rice March 16, 2021, 4:06 p.m. UTC
* guix/cve.scm <cve-item>[cvss3-base-severity]: New field.
(impact-data->cve-cvss3-base-severity): New procedure.
<vulnerability>[severity]: New field.
(vulnerability->sexp, sexp->vulnerability, cve-item->vulnerability)
(write-cache): Bump the format version to 2.
(vulnerabilities->lookup-proc): Adjust accordingly.
* guix/lint.scm (check-vulnerabilities): Indicate CVE severity according
to the output port's terminal capabilities.
---
 guix/cve.scm  | 48 ++++++++++++++++++++++++++++++++----------------
 guix/lint.scm | 32 +++++++++++++++++++++++++++++++-
 2 files changed, 63 insertions(+), 17 deletions(-)

Comments

Ludovic Courtès March 31, 2021, 1:03 p.m. UTC | #1
Hi,

Tobias Geerinckx-Rice <me@tobias.gr> skribis:

> * guix/cve.scm <cve-item>[cvss3-base-severity]: New field.
> (impact-data->cve-cvss3-base-severity): New procedure.
> <vulnerability>[severity]: New field.
> (vulnerability->sexp, sexp->vulnerability, cve-item->vulnerability)
> (write-cache): Bump the format version to 2.
> (vulnerabilities->lookup-proc): Adjust accordingly.
> * guix/lint.scm (check-vulnerabilities): Indicate CVE severity according
> to the output port's terminal capabilities.

I would move the lint.scm bit to a separate patch.

Please also add a short test for ‘vulnerability-severity’ in
tests/cve.scm.

[...]

> +  (cvssv3-base-severity cve-item-cvssv3-base-severity ;string
> +                        "impact" impact-data->cve-cvssv3-base-severity)
> +  (published-date       cve-item-published-date
> +                        "publishedDate" string->date*)
> +  (last-modified-date   cve-item-last-modified-date
> +                        "lastModifiedDate" string->date*))
>  
>  (define-json-mapping <cve> cve cve?
>    json->cve
> @@ -183,6 +188,15 @@ element found in CVEs, return an sexp such as (\"binutils\" (<
>    (let ((nodes (vector->list (assoc-ref alist "nodes"))))
>      (filter-map node->configuration nodes)))
>  
> +(define (impact-data->cve-cvssv3-base-severity alist)
> +  "Given ALIST, a JSON dictionary for the \"impact\" element found in
> +CVEs, return a string indicating its CVSSv3 severity.  This should be
> +one of \"NONE\", \"LOW\", \"MEDIUM\", \"HIGH\", or \"CRITICAL\", but we
> +return whatever we find, or #F if the severity cannot be determined."
> +  (let* ((base-metric-v3 (assoc-ref alist "baseMetricV3"))
> +         (cvss-v3        (assoc-ref base-metric-v3 "cvssV3")))
> +    (assoc-ref cvss-v3 "baseSeverity")))

I would pass the result through (string->symbol (string-downcase …)).

For clarity, perhaps we can do:

  (define-json-mapping <cvss> cvss cvss?
    json->cvss
    (vector-string  cvss-vector-string “vector_String")
    (base-severity  cvss-severity "base_Severity"
                    (compose string->symbol string-downcase)))

… and use that instead of the last ‘assoc-ref’ call above.

The rest LGTM.

Thanks for this pleasant improvement!

Ludo’.
Léo Le Bouter March 31, 2021, 1:06 p.m. UTC | #2
On Wed, 2021-03-31 at 15:03 +0200, Ludovic Courtès wrote:

[...]

> The rest LGTM.
> 
> Thanks for this pleasant improvement!
> 
> Ludo’.
> 

Hello Ludo!

Did you get it to work on your end?

Léo
Ludovic Courtès March 31, 2021, 8:57 p.m. UTC | #3
Léo Le Bouter <lle-bout@zaclys.net> skribis:

> Did you get it to work on your end?

I didn’t try, but I’m confident Tobias will do the right thing!

Ludo’.
Léo Le Bouter April 1, 2021, 11:36 p.m. UTC | #4
On Wed, 2021-03-31 at 22:57 +0200, Ludovic Courtès wrote:
> Léo Le Bouter <lle-bout@zaclys.net> skribis:
> 
> > Did you get it to work on your end?
> 
> I didn’t try, but I’m confident Tobias will do the right thing!
> 
> Ludo’.

I see, thanks, I was looking to get it to work for me since Tobias
seems busy maybe you had some elements I could use, I don't doubt they
will do the right thing!
diff mbox series

Patch

diff --git a/guix/cve.scm b/guix/cve.scm
index b3a8b13a06..3809e4493f 100644
--- a/guix/cve.scm
+++ b/guix/cve.scm
@@ -1,5 +1,6 @@ 
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -38,6 +39,7 @@ 
             cve-item?
             cve-item-cve
             cve-item-configurations
+            cve-item-cvssv3-base-severity
             cve-item-published-date
             cve-item-last-modified-date
 
@@ -53,6 +55,7 @@ 
 
             vulnerability?
             vulnerability-id
+            vulnerability-severity
             vulnerability-packages
 
             json->vulnerabilities
@@ -72,13 +75,15 @@ 
 
 (define-json-mapping <cve-item> cve-item cve-item?
   json->cve-item
-  (cve            cve-item-cve "cve" json->cve)   ;<cve>
-  (configurations cve-item-configurations         ;list of sexps
-                  "configurations" configuration-data->cve-configurations)
-  (published-date cve-item-published-date
-                  "publishedDate" string->date*)
-  (last-modified-date cve-item-last-modified-date
-                      "lastModifiedDate" string->date*))
+  (cve                  cve-item-cve "cve" json->cve) ;<cve>
+  (configurations       cve-item-configurations       ;list of sexps
+                        "configurations" configuration-data->cve-configurations)
+  (cvssv3-base-severity cve-item-cvssv3-base-severity ;string
+                        "impact" impact-data->cve-cvssv3-base-severity)
+  (published-date       cve-item-published-date
+                        "publishedDate" string->date*)
+  (last-modified-date   cve-item-last-modified-date
+                        "lastModifiedDate" string->date*))
 
 (define-json-mapping <cve> cve cve?
   json->cve
@@ -183,6 +188,15 @@  element found in CVEs, return an sexp such as (\"binutils\" (<
   (let ((nodes (vector->list (assoc-ref alist "nodes"))))
     (filter-map node->configuration nodes)))
 
+(define (impact-data->cve-cvssv3-base-severity alist)
+  "Given ALIST, a JSON dictionary for the \"impact\" element found in
+CVEs, return a string indicating its CVSSv3 severity.  This should be
+one of \"NONE\", \"LOW\", \"MEDIUM\", \"HIGH\", or \"CRITICAL\", but we
+return whatever we find, or #F if the severity cannot be determined."
+  (let* ((base-metric-v3 (assoc-ref alist "baseMetricV3"))
+         (cvss-v3        (assoc-ref base-metric-v3 "cvssV3")))
+    (assoc-ref cvss-v3 "baseSeverity")))
+
 (define (json->cve-items json)
   "Parse JSON, an input port or a string, and return a list of <cve-item>
 records."
@@ -251,20 +265,21 @@  records."
   (* 3600 24 (date-month %now)))
 
 (define-record-type <vulnerability>
-  (vulnerability id packages)
+  (vulnerability id severity packages)
   vulnerability?
   (id         vulnerability-id)             ;string
+  (severity   vulnerability-severity)       ;string
   (packages   vulnerability-packages))      ;((p1 sexp1) (p2 sexp2) ...)
 
 (define vulnerability->sexp
   (match-lambda
-    (($ <vulnerability> id packages)
-     `(v ,id ,packages))))
+    (($ <vulnerability> id severity packages)
+     `(v ,id ,severity ,packages))))
 
 (define sexp->vulnerability
   (match-lambda
-    (('v id (packages ...))
-     (vulnerability id packages))))
+    (('v id severity (packages ...))
+     (vulnerability id severity packages))))
 
 (define (cve-configuration->package-list config)
   "Parse CONFIG, a config sexp, and return a list of the form (P SEXP)
@@ -309,12 +324,13 @@  versions."
   "Return a <vulnerability> corresponding to ITEM, a <cve-item> record;
 return #f if ITEM does not list any configuration or if it does not list
 any \"a\" (application) configuration."
-  (let ((id (cve-id (cve-item-cve item))))
+  (let ((id (cve-id (cve-item-cve item)))
+        (severity (cve-item-base-severity item)))
     (match (cve-item-configurations item)
       (()                                         ;no configurations
        #f)
       ((configs ...)
-       (vulnerability id
+       (vulnerability id severity
                       (merge-package-lists
                        (map cve-configuration->package-list configs)))))))
 
@@ -332,7 +348,7 @@  sexp to CACHE."
         (json->vulnerabilities input))
 
       (write `(vulnerabilities
-               1                                  ;format version
+               2                                  ;format version
                ,(map vulnerability->sexp vulns))
              cache))))
 
@@ -396,7 +412,7 @@  vulnerabilities affecting the given package version."
     ;; Map package names to lists of version/vulnerability pairs.
     (fold (lambda (vuln table)
             (match vuln
-              (($ <vulnerability> id packages)
+              (($ <vulnerability> id severity packages)
                (fold (lambda (package table)
                        (match package
                          ((name . versions)
diff --git a/guix/lint.scm b/guix/lint.scm
index ed57e19fe2..f3c4e13052 100644
--- a/guix/lint.scm
+++ b/guix/lint.scm
@@ -48,6 +48,7 @@ 
   #:use-module (guix monads)
   #:use-module (guix scripts)
   #:use-module ((guix ui) #:select (texi->plain-text fill-paragraph))
+  #:use-module (guix colors)
   #:use-module (guix gnu-maintenance)
   #:use-module (guix cve)
   #:use-module ((guix swh) #:hide (origin?))
@@ -1165,6 +1166,35 @@  the NIST server non-fatal."
   "Check for known vulnerabilities for PACKAGE.  Obtain the list of
 vulnerability records for PACKAGE by calling PACKAGE-VULNERABILITIES."
 
+  (define severity->color
+    ;; A standard CVE colour gradient is red > orange > yellow > green > none.
+    ;; However, ANSI non-bold YELLOW is actually orange whilst BOLD YELLOW
+    ;; is actual yellow, so BOLD would confusingly be less serious.  Skip it.
+    (match-lambda
+      ("CRITICAL"     (color BOLD RED))
+      ("HIGH"         (color RED))
+      ("MEDIUM"       (color YELLOW))
+      ("LOW"          (color GREEN))
+      (_              (color))))
+
+  (define (colorize-vulnerability vulnerability)
+    ;; If the terminal supports ANSI colours, use them to indicate severity.
+    (colorize-string (vulnerability-id vulnerability)
+                     (severity->color (vulnerability-severity
+                                       vulnerability))))
+
+  (define (simple-format-vulnerability vulnerability)
+    ;; Otherwise, omit colour coding and explicitly append the severity string.
+    (simple-format #f "~a (~a)"
+                   (vulnerability-id vulnerability)
+                   (string-downcase (vulnerability-severity vulnerability))))
+
+  (define format-vulnerability
+    ;; Check once which of the above to use for all PACKAGE vulnerabilities.
+    (if (color-output? (current-output-port))
+        colorize-vulnerability
+        simple-format-vulnerability))
+
   (define (vulnerability< v1 v2)
     (define (string-list< list1 list2)
       (match list1
@@ -1201,7 +1231,7 @@  vulnerability records for PACKAGE by calling PACKAGE-VULNERABILITIES."
               (make-warning
                package
                (G_ "probably vulnerable to ~a")
-               (list (string-join (map vulnerability-id
+               (list (string-join (map format-vulnerability
                                        (sort unpatched vulnerability<))
                                   ", "))))))))))