mirror of
https://codeberg.org/guix/guix.git
synced 2025-10-01 18:05:17 +00:00
cve: Upgrade to JSON 2.0 feeds.
Fixes guix/guix#2213. The 1.1-formatted-data is no longer available from NIST. * guix/cve.scm (string->date*, <cve-item>, reference-data->cve-configuration, cpe-match->cve-configuration, configuration-data->cve-configurations, json->cve-items, yearly-feed-uri, cve-item->vulnerability): Upgrade to JSON 2.0 feeds schema. (<cve>): Remove uneeded record. * tests/cve-sample.json: Update them. Remove CVE-2019-0005 (no value added, lots of lines). * tests/cve.scm (%expected-vulnerabilities): Upgrade accordingly. (json->cve-items, vulnerabilities->lookup-proc tests): Update accordingly. Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
parent
ad5e0fc720
commit
d431f4620a
3 changed files with 1773 additions and 1362 deletions
103
guix/cve.scm
103
guix/cve.scm
|
@ -1,5 +1,6 @@
|
|||
;;; GNU Guix --- Functional package management for GNU
|
||||
;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
|
||||
;;; Copyright © 2015-2021 Ludovic Courtès <ludo@gnu.org>
|
||||
;;; Copyright © 2025 Nicolas Graves <ngraves@ngraves.fr>
|
||||
;;;
|
||||
;;; This file is part of GNU Guix.
|
||||
;;;
|
||||
|
@ -36,17 +37,11 @@
|
|||
#:export (json->cve-items
|
||||
|
||||
cve-item?
|
||||
cve-item-cve
|
||||
cve-item-id
|
||||
cve-item-configurations
|
||||
cve-item-published-date
|
||||
cve-item-last-modified-date
|
||||
|
||||
cve?
|
||||
cve-id
|
||||
cve-data-type
|
||||
cve-data-format
|
||||
cve-references
|
||||
|
||||
cve-reference?
|
||||
cve-reference-url
|
||||
cve-reference-tags
|
||||
|
@ -68,28 +63,17 @@
|
|||
;;; Code:
|
||||
|
||||
(define (string->date* str)
|
||||
(string->date str "~Y-~m-~dT~H:~M~z"))
|
||||
(string->date str "~Y-~m-~dT~H:~M:~S"))
|
||||
|
||||
(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
|
||||
(id cve-item-id "id") ;string
|
||||
(configurations cve-item-configurations ;list of sexps
|
||||
"configurations" configuration-data->cve-configurations)
|
||||
(published-date cve-item-published-date
|
||||
"publishedDate" string->date*)
|
||||
"published" string->date*)
|
||||
(last-modified-date cve-item-last-modified-date
|
||||
"lastModifiedDate" string->date*))
|
||||
|
||||
(define-json-mapping <cve> cve cve?
|
||||
json->cve
|
||||
(id cve-id "CVE_data_meta" ;string
|
||||
(cut assoc-ref <> "ID"))
|
||||
(data-type cve-data-type ;'CVE
|
||||
"data_type" string->symbol)
|
||||
(data-format cve-data-format ;'MITRE
|
||||
"data_format" string->symbol)
|
||||
(references cve-references ;list of <cve-reference>
|
||||
"references" reference-data->cve-references))
|
||||
"lastModified" string->date*))
|
||||
|
||||
(define-json-mapping <cve-reference> cve-reference cve-reference?
|
||||
json->cve-reference
|
||||
|
@ -97,12 +81,6 @@
|
|||
(tags cve-reference-tags ;list of strings
|
||||
"tags" vector->list))
|
||||
|
||||
(define (reference-data->cve-references alist)
|
||||
(map json->cve-reference
|
||||
;; Normally "reference_data" is always present but rejected CVEs such
|
||||
;; as CVE-2020-10020 can lack it.
|
||||
(vector->list (or (assoc-ref alist "reference_data") '#()))))
|
||||
|
||||
(define %cpe-package-rx
|
||||
;; For applications: "cpe:2.3:a:VENDOR:PACKAGE:VERSION", or sometimes
|
||||
;; "cpe:2.3:a:VENDOR:PACKAGE:VERSION:PATCH-LEVEL".
|
||||
|
@ -132,15 +110,15 @@ Return three #f values if CPE does not look like an application CPE string."
|
|||
(values #f #f #f))))
|
||||
|
||||
(define (cpe-match->cve-configuration alist)
|
||||
"Convert ALIST, a \"cpe_match\" alist, into an sexp representing the package
|
||||
"Convert ALIST, a \"cpeMatch\" alist, into an sexp representing the package
|
||||
and versions matched. Return #f if ALIST doesn't correspond to an application
|
||||
package."
|
||||
(let ((cpe (assoc-ref alist "cpe23Uri"))
|
||||
(let ((cpe (assoc-ref alist "criteria"))
|
||||
(starti (assoc-ref alist "versionStartIncluding"))
|
||||
(starte (assoc-ref alist "versionStartExcluding"))
|
||||
(endi (assoc-ref alist "versionEndIncluding"))
|
||||
(ende (assoc-ref alist "versionEndExcluding")))
|
||||
;; Normally "cpe23Uri" is here in each "cpe_match" item, but CVE-2020-0534
|
||||
;; Normally "criteria" is here in each "cpeMatch" item, but CVE-2020-0534
|
||||
;; has a configuration that lacks it.
|
||||
(and cpe
|
||||
(let ((vendor package version (cpe->package-identifier cpe)))
|
||||
|
@ -156,7 +134,7 @@ package."
|
|||
(ende `(< ,ende))
|
||||
(else version))))))))
|
||||
|
||||
(define (configuration-data->cve-configurations alist)
|
||||
(define (configuration-data->cve-configurations vector)
|
||||
"Given ALIST, a JSON dictionary for the baroque \"configurations\"
|
||||
element found in CVEs, return an sexp such as (\"binutils\" (<
|
||||
\"2.31\")) that represents matching configurations."
|
||||
|
@ -165,10 +143,13 @@ element found in CVEs, return an sexp such as (\"binutils\" (<
|
|||
("OR" 'or)
|
||||
("AND" 'and)))
|
||||
|
||||
(define (maybe-vector->alist vector)
|
||||
(vector->list (or (and (unspecified? vector) #()) vector #())))
|
||||
|
||||
(define (node->configuration node)
|
||||
(let ((operator (string->operator (assoc-ref node "operator"))))
|
||||
(cond
|
||||
((assoc-ref node "cpe_match")
|
||||
((assoc-ref node "cpeMatch")
|
||||
=>
|
||||
(lambda (matches)
|
||||
(let ((matches (vector->list matches)))
|
||||
|
@ -187,28 +168,31 @@ element found in CVEs, return an sexp such as (\"binutils\" (<
|
|||
(else
|
||||
#f))))
|
||||
|
||||
(let ((nodes (vector->list (assoc-ref alist "nodes"))))
|
||||
(let* ((alist (maybe-vector->alist vector))
|
||||
(nodes (if (null? alist)
|
||||
'()
|
||||
(maybe-vector->alist (assoc-ref (car alist) "nodes")))))
|
||||
(filter-map node->configuration nodes)))
|
||||
|
||||
(define (json->cve-items json)
|
||||
"Parse JSON, an input port or a string, and return a list of <cve-item>
|
||||
records."
|
||||
(let* ((alist (json->scm json))
|
||||
(type (assoc-ref alist "CVE_data_type"))
|
||||
(format (assoc-ref alist "CVE_data_format"))
|
||||
(version (assoc-ref alist "CVE_data_version")))
|
||||
(unless (equal? type "CVE")
|
||||
(raise (condition (&message
|
||||
(message "invalid CVE feed")))))
|
||||
(unless (equal? format "MITRE")
|
||||
(raise (formatted-message (G_ "unsupported CVE format: '~a'")
|
||||
format)))
|
||||
(unless (equal? version "4.0")
|
||||
(raise (formatted-message (G_ "unsupported CVE data version: '~a'")
|
||||
version)))
|
||||
(let ((alist (json->scm json)))
|
||||
(match (assoc-ref alist "format")
|
||||
("NVD_CVE"
|
||||
#t)
|
||||
(format
|
||||
(raise (formatted-message (G_ "unsupported CVE format: '~a'")
|
||||
format))))
|
||||
(match (assoc-ref alist "version")
|
||||
("2.0"
|
||||
#t)
|
||||
(version
|
||||
(raise (formatted-message (G_ "unsupported CVE data version: '~a'")
|
||||
version))))
|
||||
|
||||
(map json->cve-item
|
||||
(vector->list (assoc-ref alist "CVE_Items")))))
|
||||
(map (compose json->cve-item (cut assoc-ref <> "cve"))
|
||||
(vector->list (assoc-ref alist "vulnerabilities")))))
|
||||
|
||||
(define (version-matches? version sexp)
|
||||
"Return true if VERSION, a string, matches SEXP."
|
||||
|
@ -269,7 +253,7 @@ HIDDEN-VENDORS."
|
|||
(define (yearly-feed-uri year)
|
||||
"Return the URI for the CVE feed for YEAR."
|
||||
(string->uri
|
||||
(string-append "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-"
|
||||
(string-append "https://nvd.nist.gov/feeds/json/cve/2.0/nvdcve-2.0-"
|
||||
(number->string year) ".json.gz")))
|
||||
|
||||
(define %current-year-ttl
|
||||
|
@ -352,14 +336,13 @@ matching 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))))
|
||||
(match (cve-item-configurations item)
|
||||
(() ;no configurations
|
||||
#f)
|
||||
((configs ...)
|
||||
(vulnerability id
|
||||
(merge-package-lists
|
||||
(map cve-configuration->package-list configs)))))))
|
||||
(match (cve-item-configurations item)
|
||||
(() ;no configurations
|
||||
#f)
|
||||
((configs ...)
|
||||
(vulnerability (cve-item-id item)
|
||||
(merge-package-lists
|
||||
(map cve-configuration->package-list configs))))))
|
||||
|
||||
(define (json->vulnerabilities json)
|
||||
"Parse JSON, an input port or a string, and return the list of
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -22,6 +22,8 @@
|
|||
#:use-module (srfi srfi-19)
|
||||
#:use-module (srfi srfi-64))
|
||||
|
||||
;; Generated from the 2019 database :
|
||||
;; jq -M '.vulnerabilities |= map(select(.cve.id | IN("CVE-2019-14811", "CVE-2019-17365", "CVE-2019-1010180", "CVE-2019-1010204", "CVE-2019-18192", "CVE-2019-0001"))) | .totalResults = (.vulnerabilities | length) | .resultsPerPage = (.vulnerabilities | length)'
|
||||
(define %sample
|
||||
(search-path %load-path "tests/cve-sample.json"))
|
||||
|
||||
|
@ -31,23 +33,19 @@
|
|||
(define %expected-vulnerabilities
|
||||
;; What we should get when reading %SAMPLE.
|
||||
(list
|
||||
(vulnerability "CVE-2019-0001"
|
||||
;; Only the "a" CPE configurations are kept; the "o"
|
||||
;; configurations are discarded.
|
||||
'(("juniper" "junos" (or "18.2" (or "18.21-s3" "18.21-s4")))))
|
||||
(vulnerability "CVE-2019-0005"
|
||||
'(("juniper" "junos" (or "18.1" "18.11"))))
|
||||
;; CVE-2019-0005 has no "a" configurations.
|
||||
(vulnerability "CVE-2019-14811"
|
||||
'(("artifex" "ghostscript" (< "9.28"))))
|
||||
(vulnerability "CVE-2019-17365"
|
||||
'(("nixos" "nix" (<= "2.3"))))
|
||||
(vulnerability "CVE-2019-1010180"
|
||||
'(("gnu" "gdb" _))) ;any version
|
||||
(vulnerability "CVE-2019-1010204"
|
||||
'(("gnu" "binutils" (and (>= "2.21") (<= "2.31.1")))
|
||||
("gnu" "binutils_gold" (and (>= "1.11") (<= "1.16")))))
|
||||
;; CVE-2019-18192 has no associated configurations.
|
||||
(vulnerability "CVE-2019-1010180"
|
||||
'(("gnu" "gdb" (< "9.1"))))
|
||||
(vulnerability "CVE-2019-14811"
|
||||
'(("artifex" "ghostscript" (< "9.50"))))
|
||||
(vulnerability "CVE-2019-17365"
|
||||
'(("nixos" "nix" (<= "2.3"))))
|
||||
(vulnerability "CVE-2019-18192"
|
||||
'(("gnu" "guix" "1.0.1")))
|
||||
;; Only the "a" CPE configurations are kept; the "o" configurations are discarded.
|
||||
;; This is why CVE-2019-0001 doesn't appear here.
|
||||
))
|
||||
|
||||
|
||||
|
@ -55,13 +53,12 @@
|
|||
|
||||
(test-equal "json->cve-items"
|
||||
'("CVE-2019-0001"
|
||||
"CVE-2019-0005"
|
||||
"CVE-2019-1010204"
|
||||
"CVE-2019-1010180"
|
||||
"CVE-2019-14811"
|
||||
"CVE-2019-17365"
|
||||
"CVE-2019-1010180"
|
||||
"CVE-2019-1010204"
|
||||
"CVE-2019-18192")
|
||||
(map (compose cve-id cve-item-cve)
|
||||
(map cve-item-id
|
||||
(call-with-input-file %sample json->cve-items)))
|
||||
|
||||
(test-equal "cve-item-published-date"
|
||||
|
@ -75,32 +72,32 @@
|
|||
(call-with-input-file %sample json->vulnerabilities))
|
||||
|
||||
(test-equal "vulnerabilities->lookup-proc"
|
||||
(list (list (third %expected-vulnerabilities)) ;ghostscript
|
||||
(list (list (first %expected-vulnerabilities)) ;binutils
|
||||
'()
|
||||
(list (first %expected-vulnerabilities))
|
||||
'()
|
||||
|
||||
(list (second %expected-vulnerabilities)) ;gdb
|
||||
(list (second %expected-vulnerabilities))
|
||||
|
||||
(list (third %expected-vulnerabilities)) ;ghostscript
|
||||
(list (third %expected-vulnerabilities))
|
||||
'()
|
||||
|
||||
(list (fifth %expected-vulnerabilities)) ;gdb
|
||||
(list (fifth %expected-vulnerabilities))
|
||||
|
||||
(list (fourth %expected-vulnerabilities)) ;nix
|
||||
'()
|
||||
|
||||
(list (sixth %expected-vulnerabilities)) ;binutils
|
||||
'()
|
||||
(list (sixth %expected-vulnerabilities))
|
||||
'())
|
||||
(let* ((vulns (call-with-input-file %sample json->vulnerabilities))
|
||||
(lookup (vulnerabilities->lookup-proc vulns)))
|
||||
(list (lookup "ghostscript")
|
||||
(lookup "ghostscript" "9.27")
|
||||
(lookup "ghostscript" "9.28")
|
||||
(lookup "gdb")
|
||||
(lookup "gdb" "42.0")
|
||||
(lookup "nix")
|
||||
(lookup "nix" "2.4")
|
||||
(lookup "binutils" "2.31.1")
|
||||
(list (lookup "binutils" "2.31.1")
|
||||
(lookup "binutils" "2.10")
|
||||
(lookup "binutils_gold" "1.11")
|
||||
(lookup "binutils" "2.32"))))
|
||||
(lookup "binutils" "2.32")
|
||||
(lookup "gdb")
|
||||
(lookup "gdb" "9.0")
|
||||
(lookup "ghostscript")
|
||||
(lookup "ghostscript" "9.27")
|
||||
(lookup "ghostscript" "9.51")
|
||||
(lookup "nix")
|
||||
(lookup "nix" "2.4"))))
|
||||
|
||||
(test-end "cve")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue