All of lore.kernel.org
 help / color / mirror / Atom feed
* [Buildroot] [PATCH v3 0/8] Improving CVE reporting
@ 2020-07-24 15:43 Gregory CLEMENT
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 1/8] support/scripts: Turn CVE check into a module Gregory CLEMENT
                   ` (8 more replies)
  0 siblings, 9 replies; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

Hello,

The purpose of this series is to improve the CVE reporting in order to
be usable for a project.

Until know the CVE affecting the packages were reported for the
buildroot project using pkg-stat. With this series it is now possible
to report the packages affected by CVEs for a given configuration.

While I was on CVE I switched to the support of the JSON 1.1 for the
NVDE database.

In this series I also added a new state for the CVE status of the
packages. This new state will be used to emphasize that the automatic
check has failed and it was needed to be verified manually. The idea
behind this was to be as much accurate as possible to avoid any false
positive. It will also help to improve the meta-data of the package.

The next step will be to reuse the works done by Matthew Weber [1] to
use the cpeid and only use the package name and the package version as
fall back.

In this series there is at least one open point about the packages
excluded from the cve check. For now I excluded the kernel and gcc as
there are also excluded by the pkg-stats script but this list could
(should ?) be extended or modified.

In this third version the following changes have been done:
v2 -> v3:
- removed the first patch that had been applied

- rebased the series on a recent master branch

- Fixed recursive call in parse_node as suggested by Titouan. I didn't
  use the more recent syntax (Python >=3.4) because I think buildroot
  doesn't impose having a recent python.

Titouan also mentioned that CPE nodes can be ORed or ANDed and I
confirm it. So I had a closer look on it. First found there are
children node only with the AND operator. Then most of the time the
AND associate a version of product than could be affected with a
version of another product which usually provide service to the first
one such as an operating system. Or we could have the association of a
software and an hardware. Having an application in the second part of
the AND can happen but is very rare.

Supporting these features will make the code more complex. By just
parsing the node recursively without applying the AND condition, we
could have some false positive CVE. But at least we won't miss CVE and
the case were it would be useful for buildroot should be very scarce.

If later we realize that we have a lot of false positive because we
ignore this feature then we can decide to modify the code to support
it.

v1 -> v2

 - Port the version fix to pkg-stat from cve.py and move this patch as
   the first one

 - Remove debug message

 - Remove unused argument -p and -n in cve-checker

 - Remove the information about the commit used in the output for the
   cve-checker

 - Remove all the unnecessary import

 - Add a default path to the download directory for nvd for the
   cve-checker

 - Do not use boolean anymore for the affected status

 - Use ignore_cves instead of ignored_cves in pkg-utils

 - Fix the html output for cve-checker and pkg-stat

 - Check if ijson is present on the host

Gregory

Gregory CLEMENT (8):
  support/scripts: Turn CVE check into a module
  support/scripts/cve.py: Switch to JSON 1.1
  package/pkg-utils: show-info: report the list of the CVEs ignored
  support/script: Make CVE class independent of the Pacakage class
  support/scripts: Add a per configuration CVE checker
  support/script/pkg-stats: Manage the CVEs that need to be check
  support/script/cve-checker: Manage the CVEs that need to be check
  package/pkg-utils/cve.py: Manage case when package version doesn't
    exist

 package/pkg-utils.mk        |   5 +-
 support/scripts/cve-checker | 275 ++++++++++++++++++++++++++++++++++++
 support/scripts/cve.py      | 233 ++++++++++++++++++++++++++++++
 support/scripts/pkg-stats   | 171 +++++-----------------
 4 files changed, 548 insertions(+), 136 deletions(-)
 create mode 100755 support/scripts/cve-checker
 create mode 100755 support/scripts/cve.py

-- 
2.27.0

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 1/8] support/scripts: Turn CVE check into a module
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-08-28  7:18   ` Thomas Petazzoni
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 2/8] support/scripts/cve.py: Switch to JSON 1.1 Gregory CLEMENT
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

In order to be able to do CVE checking outside of pkg-stat, move the
CVE class in a module that can be used by other scripts.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 support/scripts/cve.py    | 156 ++++++++++++++++++++++++++++++++++++++
 support/scripts/pkg-stats | 132 +-------------------------------
 2 files changed, 160 insertions(+), 128 deletions(-)
 create mode 100755 support/scripts/cve.py

diff --git a/support/scripts/cve.py b/support/scripts/cve.py
new file mode 100755
index 0000000000..8a4087ef8a
--- /dev/null
+++ b/support/scripts/cve.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2009 by Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+# Copyright (C) 2020 by Gregory CLEMENT <gregory.clement@bootlin.com>
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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
+
+import datetime
+import os
+import requests  # URL checking
+import distutils.version
+import time
+import gzip
+import sys
+
+try:
+    import ijson
+except ImportError:
+    sys.stderr.write("You need ijson to parse NVD for CVE check\n")
+    exit(1)
+
+sys.path.append('utils/')
+
+NVD_START_YEAR = 2002
+NVD_JSON_VERSION = "1.0"
+NVD_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/" + NVD_JSON_VERSION
+
+class CVE:
+    """An accessor class for CVE Items in NVD files"""
+    CVE_AFFECTS = 1
+    CVE_DOESNT_AFFECT = 2
+    CVE_UNKNOWN = 3
+
+    def __init__(self, nvd_cve):
+        """Initialize a CVE from its NVD JSON representation"""
+        self.nvd_cve = nvd_cve
+
+    @staticmethod
+    def download_nvd_year(nvd_path, year):
+        metaf = "nvdcve-%s-%s.meta" % (NVD_JSON_VERSION, year)
+        path_metaf = os.path.join(nvd_path, metaf)
+        jsonf_gz = "nvdcve-%s-%s.json.gz" % (NVD_JSON_VERSION, year)
+        path_jsonf_gz = os.path.join(nvd_path, jsonf_gz)
+
+        # If the database file is less than a day old, we assume the NVD data
+        # locally available is recent enough.
+        if os.path.exists(path_jsonf_gz) and os.stat(path_jsonf_gz).st_mtime >= time.time() - 86400:
+            return path_jsonf_gz
+
+        # If not, we download the meta file
+        url = "%s/%s" % (NVD_BASE_URL, metaf)
+        print("Getting %s" % url)
+        page_meta = requests.get(url)
+        page_meta.raise_for_status()
+
+        # If the meta file already existed, we compare the existing
+        # one with the data newly downloaded. If they are different,
+        # we need to re-download the database.
+        # If the database does not exist locally, we need to redownload it in
+        # any case.
+        if os.path.exists(path_metaf) and os.path.exists(path_jsonf_gz):
+            meta_known = open(path_metaf, "r").read()
+            if page_meta.text == meta_known:
+                return path_jsonf_gz
+
+        # Grab the compressed JSON NVD, and write files to disk
+        url = "%s/%s" % (NVD_BASE_URL, jsonf_gz)
+        print("Getting %s" % url)
+        page_json = requests.get(url)
+        page_json.raise_for_status()
+        open(path_jsonf_gz, "wb").write(page_json.content)
+        open(path_metaf, "w").write(page_meta.text)
+        return path_jsonf_gz
+
+    @classmethod
+    def read_nvd_dir(cls, nvd_dir):
+        """
+        Iterate over all the CVEs contained in NIST Vulnerability Database
+        feeds since NVD_START_YEAR. If the files are missing or outdated in
+        nvd_dir, a fresh copy will be downloaded, and kept in .json.gz
+        """
+        for year in range(NVD_START_YEAR, datetime.datetime.now().year + 1):
+            filename = CVE.download_nvd_year(nvd_dir, year)
+            try:
+                content = ijson.items(gzip.GzipFile(filename), 'CVE_Items.item')
+            except:  # noqa: E722
+                print("ERROR: cannot read %s. Please remove the file then rerun this script" % filename)
+                raise
+            for cve in content:
+                yield cls(cve['cve'])
+
+    def each_product(self):
+        """Iterate over each product section of this cve"""
+        for vendor in self.nvd_cve['affects']['vendor']['vendor_data']:
+            for product in vendor['product']['product_data']:
+                yield product
+
+    @property
+    def identifier(self):
+        """The CVE unique identifier"""
+        return self.nvd_cve['CVE_data_meta']['ID']
+
+    @property
+    def pkg_names(self):
+        """The set of package names referred by this CVE definition"""
+        return set(p['product_name'] for p in self.each_product())
+
+    def affects(self, br_pkg):
+        """
+        True if the Buildroot Package object passed as argument is affected
+        by this CVE.
+        """
+        if br_pkg.is_cve_ignored(self.identifier):
+            return self.CVE_DOESNT_AFFECT
+
+        for product in self.each_product():
+            if product['product_name'] != br_pkg.name:
+                continue
+
+            for v in product['version']['version_data']:
+                if v["version_affected"] == "=":
+                    if br_pkg.current_version == v["version_value"]:
+                        return self.CVE_AFFECTS
+                elif v["version_affected"] == "<=":
+                    pkg_version = distutils.version.LooseVersion(br_pkg.current_version)
+                    if not hasattr(pkg_version, "version"):
+                        print("Cannot parse package '%s' version '%s'" % (br_pkg.name, br_pkg.current_version))
+                        continue
+                    cve_affected_version = distutils.version.LooseVersion(v["version_value"])
+                    if not hasattr(cve_affected_version, "version"):
+                        print("Cannot parse CVE affected version '%s'" % v["version_value"])
+                        continue
+                    try:
+                        affected = pkg_version <= cve_affected_version
+                        break
+                    except TypeError:
+                        return self.CVE_UNKNOWN
+                    if affected:
+                        return self.CVE_AFFECTS
+                    else:
+                        return self.CVE_DOESNT_AFFECT
+                else:
+                    print("version_affected: %s" % v['version_affected'])
+        return self.CVE_DOESNT_AFFECT
diff --git a/support/scripts/pkg-stats b/support/scripts/pkg-stats
index ec4d538758..58847f9ca6 100755
--- a/support/scripts/pkg-stats
+++ b/support/scripts/pkg-stats
@@ -25,11 +25,8 @@ import re
 import subprocess
 import requests  # URL checking
 import json
-import ijson
 import certifi
-import distutils.version
 import time
-import gzip
 import sys
 from urllib3 import HTTPSConnectionPool
 from urllib3.exceptions import HTTPError
@@ -38,9 +35,8 @@ from multiprocessing import Pool
 sys.path.append('utils/')
 from getdeveloperlib import parse_developers  # noqa: E402
 
-NVD_START_YEAR = 2002
-NVD_JSON_VERSION = "1.0"
-NVD_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/" + NVD_JSON_VERSION
+import cve as cvecheck
+
 
 INFRA_RE = re.compile(r"\$\(eval \$\(([a-z-]*)-package\)\)")
 URL_RE = re.compile(r"\s*https?://\S*\s*$")
@@ -50,10 +46,6 @@ RM_API_STATUS_FOUND_BY_DISTRO = 2
 RM_API_STATUS_FOUND_BY_PATTERN = 3
 RM_API_STATUS_NOT_FOUND = 4
 
-CVE_AFFECTS = 1
-CVE_DOESNT_AFFECT = 2
-CVE_UNKNOWN = 3
-
 # Used to make multiple requests to the same host. It is global
 # because it's used by sub-processes.
 http_pool = None
@@ -285,122 +277,6 @@ class Package:
             (self.name, self.path, self.is_status_ok('license'),
              self.is_status_ok('license-files'), self.status['hash'], self.patch_count)
 
-
-class CVE:
-    """An accessor class for CVE Items in NVD files"""
-    def __init__(self, nvd_cve):
-        """Initialize a CVE from its NVD JSON representation"""
-        self.nvd_cve = nvd_cve
-
-    @staticmethod
-    def download_nvd_year(nvd_path, year):
-        metaf = "nvdcve-%s-%s.meta" % (NVD_JSON_VERSION, year)
-        path_metaf = os.path.join(nvd_path, metaf)
-        jsonf_gz = "nvdcve-%s-%s.json.gz" % (NVD_JSON_VERSION, year)
-        path_jsonf_gz = os.path.join(nvd_path, jsonf_gz)
-
-        # If the database file is less than a day old, we assume the NVD data
-        # locally available is recent enough.
-        if os.path.exists(path_jsonf_gz) and os.stat(path_jsonf_gz).st_mtime >= time.time() - 86400:
-            return path_jsonf_gz
-
-        # If not, we download the meta file
-        url = "%s/%s" % (NVD_BASE_URL, metaf)
-        print("Getting %s" % url)
-        page_meta = requests.get(url)
-        page_meta.raise_for_status()
-
-        # If the meta file already existed, we compare the existing
-        # one with the data newly downloaded. If they are different,
-        # we need to re-download the database.
-        # If the database does not exist locally, we need to redownload it in
-        # any case.
-        if os.path.exists(path_metaf) and os.path.exists(path_jsonf_gz):
-            meta_known = open(path_metaf, "r").read()
-            if page_meta.text == meta_known:
-                return path_jsonf_gz
-
-        # Grab the compressed JSON NVD, and write files to disk
-        url = "%s/%s" % (NVD_BASE_URL, jsonf_gz)
-        print("Getting %s" % url)
-        page_json = requests.get(url)
-        page_json.raise_for_status()
-        open(path_jsonf_gz, "wb").write(page_json.content)
-        open(path_metaf, "w").write(page_meta.text)
-        return path_jsonf_gz
-
-    @classmethod
-    def read_nvd_dir(cls, nvd_dir):
-        """
-        Iterate over all the CVEs contained in NIST Vulnerability Database
-        feeds since NVD_START_YEAR. If the files are missing or outdated in
-        nvd_dir, a fresh copy will be downloaded, and kept in .json.gz
-        """
-        for year in range(NVD_START_YEAR, datetime.datetime.now().year + 1):
-            filename = CVE.download_nvd_year(nvd_dir, year)
-            try:
-                content = ijson.items(gzip.GzipFile(filename), 'CVE_Items.item')
-            except:  # noqa: E722
-                print("ERROR: cannot read %s. Please remove the file then rerun this script" % filename)
-                raise
-            for cve in content:
-                yield cls(cve['cve'])
-
-    def each_product(self):
-        """Iterate over each product section of this cve"""
-        for vendor in self.nvd_cve['affects']['vendor']['vendor_data']:
-            for product in vendor['product']['product_data']:
-                yield product
-
-    @property
-    def identifier(self):
-        """The CVE unique identifier"""
-        return self.nvd_cve['CVE_data_meta']['ID']
-
-    @property
-    def pkg_names(self):
-        """The set of package names referred by this CVE definition"""
-        return set(p['product_name'] for p in self.each_product())
-
-    def affects(self, br_pkg):
-        """
-        True if the Buildroot Package object passed as argument is affected
-        by this CVE.
-        """
-        if br_pkg.is_cve_ignored(self.identifier):
-            return CVE_DOESNT_AFFECT
-
-        for product in self.each_product():
-            if product['product_name'] != br_pkg.name:
-                continue
-
-            for v in product['version']['version_data']:
-                if v["version_affected"] == "=":
-                    if br_pkg.current_version == v["version_value"]:
-                        return CVE_AFFECTS
-                elif v["version_affected"] == "<=":
-                    pkg_version = distutils.version.LooseVersion(br_pkg.current_version)
-                    if not hasattr(pkg_version, "version"):
-                        print("Cannot parse package '%s' version '%s'" % (br_pkg.name, br_pkg.current_version))
-                        continue
-                    cve_affected_version = distutils.version.LooseVersion(v["version_value"])
-                    if not hasattr(cve_affected_version, "version"):
-                        print("Cannot parse CVE affected version '%s'" % v["version_value"])
-                        continue
-                    try:
-                        affected = pkg_version <= cve_affected_version
-                        break
-                    except TypeError:
-                        return CVE_UNKNOWN
-                    if affected:
-                        return CVE_AFFECTS
-                    else:
-                        return CVE_DOESNT_AFFECT
-                else:
-                    print("version_affected: %s" % v['version_affected'])
-        return CVE_DOESNT_AFFECT
-
-
 def get_pkglist(npackages, package_list):
     """
     Builds the list of Buildroot packages, returning a list of Package
@@ -620,9 +496,9 @@ def check_package_cves(nvd_path, packages):
     if not os.path.isdir(nvd_path):
         os.makedirs(nvd_path)
 
-    for cve in CVE.read_nvd_dir(nvd_path):
+    for cve in cvecheck.CVE.read_nvd_dir(nvd_path):
         for pkg_name in cve.pkg_names:
-            if pkg_name in packages and cve.affects(packages[pkg_name]) == CVE_AFFECTS:
+            if pkg_name in packages and cve.affects(packages[pkg_name]) == cve.CVE_AFFECTS:
                 packages[pkg_name].cves.append(cve.identifier)
 
 
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 2/8] support/scripts/cve.py: Switch to JSON 1.1
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 1/8] support/scripts: Turn CVE check into a module Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-08-28  7:34   ` Thomas Petazzoni
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 3/8] package/pkg-utils: show-info: report the list of the CVEs ignored Gregory CLEMENT
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

In 2019, the JSON vulnerability feeds switched from version 1.0 to
1.1.

The main difference is the removal of the affects element that was
used to check if a package was affected by a CVE.

This information is duplicated in the configuration element which
contains in the end the cpeid as well as properties about the versions
affected. Instead of having a list of the versions affected, with
these properties, it is possible to have a range of versions.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 support/scripts/cve.py | 125 ++++++++++++++++++++++++++++++++---------
 1 file changed, 98 insertions(+), 27 deletions(-)

diff --git a/support/scripts/cve.py b/support/scripts/cve.py
index 8a4087ef8a..a8861d966c 100755
--- a/support/scripts/cve.py
+++ b/support/scripts/cve.py
@@ -34,9 +34,19 @@ except ImportError:
 sys.path.append('utils/')
 
 NVD_START_YEAR = 2002
-NVD_JSON_VERSION = "1.0"
+NVD_JSON_VERSION = "1.1"
 NVD_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/" + NVD_JSON_VERSION
 
+import operator
+
+ops = {
+    '>=' : operator.ge,
+    '>' : operator.gt,
+    '<=' : operator.le,
+    '<' : operator.lt,
+    '=' : operator.eq
+}
+
 class CVE:
     """An accessor class for CVE Items in NVD files"""
     CVE_AFFECTS = 1
@@ -99,23 +109,81 @@ class CVE:
                 print("ERROR: cannot read %s. Please remove the file then rerun this script" % filename)
                 raise
             for cve in content:
-                yield cls(cve['cve'])
+                yield cls(cve)
 
     def each_product(self):
         """Iterate over each product section of this cve"""
-        for vendor in self.nvd_cve['affects']['vendor']['vendor_data']:
+        for vendor in self.nvd_cve['cve']['affects']['vendor']['vendor_data']:
             for product in vendor['product']['product_data']:
                 yield product
 
+    def parse_node(self, node):
+        """
+        Parse the node inside the configurations section to extract the
+        cpe information usefull to know if a product is affected by
+        the CVE. Actually only the product name and the version
+        descriptor are needed, but we also provide the vendor name.
+        """
+
+        # The node containing the cpe entries matching the CVE can also
+        # contain sub-nodes, so we need to manage it.
+        for child in node.get('children', ()):
+             for parsed_node in self.parse_node(child):
+                 yield parsed_node
+
+        for cpe in node.get('cpe_match', ()):
+            if not cpe['vulnerable']:
+                return
+            vendor, product, version = cpe['cpe23Uri'].split(':')[3:6]
+            op_start = ''
+            op_end = ''
+            v_start = ''
+            v_end = ''
+
+            if version != '*' and version != '-':
+                # Version is defined, this is a '=' match
+                op_start = '='
+                v_start = version
+            elif version == '-':
+                # no version information is available
+                op_start = '='
+                v_start = version
+            else:
+                # Parse start version, end version and operators
+                if 'versionStartIncluding' in cpe:
+                    op_start = '>='
+                    v_start = cpe['versionStartIncluding']
+
+                if 'versionStartExcluding' in cpe:
+                    op_start = '>'
+                    v_start = cpe['versionStartExcluding']
+
+                if 'versionEndIncluding' in cpe:
+                    op_end = '<='
+                    v_end = cpe['versionEndIncluding']
+
+                if 'versionEndExcluding' in cpe:
+                    op_end = '<'
+                    v_end = cpe['versionEndExcluding']
+
+            key =['vendor', 'product', 'v_start', 'op_start', 'v_end', 'op_end']
+            val = [vendor, product, v_start, op_start, v_end, op_end]
+            yield dict(zip(key, val))
+
+    def each_cpe(self):
+        for node in self.nvd_cve['configurations']['nodes']:
+            for cpe in self.parse_node(node):
+                yield cpe
+
     @property
     def identifier(self):
         """The CVE unique identifier"""
-        return self.nvd_cve['CVE_data_meta']['ID']
+        return self.nvd_cve['cve']['CVE_data_meta']['ID']
 
     @property
     def pkg_names(self):
         """The set of package names referred by this CVE definition"""
-        return set(p['product_name'] for p in self.each_product())
+        return set(p['product'] for p in self.each_cpe())
 
     def affects(self, br_pkg):
         """
@@ -125,32 +193,35 @@ class CVE:
         if br_pkg.is_cve_ignored(self.identifier):
             return self.CVE_DOESNT_AFFECT
 
-        for product in self.each_product():
-            if product['product_name'] != br_pkg.name:
+        for cpe in self.each_cpe():
+            affected = True
+            if cpe['product'] != br_pkg.name:
                 continue
+            if cpe['v_start'] == '-':
+                return self.CVE_AFFECTS
+            if not (cpe['v_start'] or cpe['v_end']):
+                print("No CVE affected version")
+                continue
+            pkg_version = distutils.version.LooseVersion(br_pkg.current_version)
+            if not hasattr(pkg_version, "version"):
+                print("Cannot parse package '%s' version '%s'" % (br_pkg.name, br_pkg.current_version))
+                continue
+
+            if cpe['v_start']:
+                    try:
+                        cve_affected_version = distutils.version.LooseVersion(cpe['v_start'])
+                        affected = ops.get(cpe['op_start'])(pkg_version, cve_affected_version)
+                        break
+                    except:
+                        return self.CVE_UNKNOWN
 
-            for v in product['version']['version_data']:
-                if v["version_affected"] == "=":
-                    if br_pkg.current_version == v["version_value"]:
-                        return self.CVE_AFFECTS
-                elif v["version_affected"] == "<=":
-                    pkg_version = distutils.version.LooseVersion(br_pkg.current_version)
-                    if not hasattr(pkg_version, "version"):
-                        print("Cannot parse package '%s' version '%s'" % (br_pkg.name, br_pkg.current_version))
-                        continue
-                    cve_affected_version = distutils.version.LooseVersion(v["version_value"])
-                    if not hasattr(cve_affected_version, "version"):
-                        print("Cannot parse CVE affected version '%s'" % v["version_value"])
-                        continue
+            if (affected and cpe['v_end']):
                     try:
-                        affected = pkg_version <= cve_affected_version
+                        cve_affected_version = distutils.version.LooseVersion(cpe['v_end'])
+                        affected = ops.get(cpe['op_end'])(pkg_version, cve_affected_version)
                         break
                     except TypeError:
                         return self.CVE_UNKNOWN
-                    if affected:
-                        return self.CVE_AFFECTS
-                    else:
-                        return self.CVE_DOESNT_AFFECT
-                else:
-                    print("version_affected: %s" % v['version_affected'])
+            if (affected):
+                return self.CVE_AFFECTS
         return self.CVE_DOESNT_AFFECT
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 3/8] package/pkg-utils: show-info: report the list of the CVEs ignored
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 1/8] support/scripts: Turn CVE check into a module Gregory CLEMENT
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 2/8] support/scripts/cve.py: Switch to JSON 1.1 Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-08-28  8:51   ` Thomas Petazzoni
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 4/8] support/script: Make CVE class independent of the Pacakage class Gregory CLEMENT
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

Add the list of the CVEs to ignore for each package because they
already have a fix for it.

This information will be useful for a cve-checker.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 package/pkg-utils.mk | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/package/pkg-utils.mk b/package/pkg-utils.mk
index d88a14ab0f..21b415cbf3 100644
--- a/package/pkg-utils.mk
+++ b/package/pkg-utils.mk
@@ -117,7 +117,10 @@ define _json-info-pkg
 		$(call make-comma-list,$(sort $($(1)_FINAL_ALL_DEPENDENCIES)))
 	],
 	"reverse_dependencies": [
-		$(call make-comma-list,$(sort $($(1)_RDEPENDENCIES)))
+		$(call make-comma-list,$(sort $($(1)_RDEPENDENCIES))),
+	],
+	"ignore_cves": [
+		$(call make-comma-list,$(sort $($(1)_IGNORE_CVES)))
 	]
 endef
 
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 4/8] support/script: Make CVE class independent of the Pacakage class
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
                   ` (2 preceding siblings ...)
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 3/8] package/pkg-utils: show-info: report the list of the CVEs ignored Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-08-28  9:03   ` Thomas Petazzoni
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker Gregory CLEMENT
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

The affects method of the CVE use the Package class defined in
pkg-stats. The purpose of migrating the CVE class outside of pkg-stats
was to be able to reuse it from other scripts. So let's remove the
Package dependency and only use the needed information.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 support/scripts/cve.py    | 10 +++++-----
 support/scripts/pkg-stats | 14 ++++++++------
 2 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/support/scripts/cve.py b/support/scripts/cve.py
index a8861d966c..4e83ac8961 100755
--- a/support/scripts/cve.py
+++ b/support/scripts/cve.py
@@ -185,26 +185,26 @@ class CVE:
         """The set of package names referred by this CVE definition"""
         return set(p['product'] for p in self.each_cpe())
 
-    def affects(self, br_pkg):
+    def affects(self, name, version, cve_ignore_list):
         """
         True if the Buildroot Package object passed as argument is affected
         by this CVE.
         """
-        if br_pkg.is_cve_ignored(self.identifier):
+        if (self.identifier in cve_ignore_list):
             return self.CVE_DOESNT_AFFECT
 
         for cpe in self.each_cpe():
             affected = True
-            if cpe['product'] != br_pkg.name:
+            if cpe['product'] != name:
                 continue
             if cpe['v_start'] == '-':
                 return self.CVE_AFFECTS
             if not (cpe['v_start'] or cpe['v_end']):
                 print("No CVE affected version")
                 continue
-            pkg_version = distutils.version.LooseVersion(br_pkg.current_version)
+            pkg_version = distutils.version.LooseVersion(version)
             if not hasattr(pkg_version, "version"):
-                print("Cannot parse package '%s' version '%s'" % (br_pkg.name, br_pkg.current_version))
+                print("Cannot parse package '%s' version '%s'" % (name, version))
                 continue
 
             if cpe['v_start']:
diff --git a/support/scripts/pkg-stats b/support/scripts/pkg-stats
index 58847f9ca6..f073e866cb 100755
--- a/support/scripts/pkg-stats
+++ b/support/scripts/pkg-stats
@@ -242,11 +242,12 @@ class Package:
                     self.status['pkg-check'] = ("error", "{} warnings".format(self.warnings))
                 return
 
-    def is_cve_ignored(self, cve):
+    def cve_ignored_list(self):
         """
-        Tells if the CVE is ignored by the package
+        Give the list of CVEs ignored by the package
         """
-        return cve in self.all_ignored_cves.get(self.pkgvar(), [])
+        print(self.all_ignored_cves.get(self.pkgvar(), []))
+        return list(self.all_ignored_cves.get(self.pkgvar(), []))
 
     def set_developers(self, developers):
         """
@@ -498,9 +499,10 @@ def check_package_cves(nvd_path, packages):
 
     for cve in cvecheck.CVE.read_nvd_dir(nvd_path):
         for pkg_name in cve.pkg_names:
-            if pkg_name in packages and cve.affects(packages[pkg_name]) == cve.CVE_AFFECTS:
-                packages[pkg_name].cves.append(cve.identifier)
-
+            if pkg_name in packages:
+                pkg = packages[pkg_name]
+                if cve.affects(pkg.name, pkg.current_version, pkg.cve_ignored_list()) == cve.CVE_AFFECTS :
+                    pkg.cves.append(cve.identifier)
 
 def calculate_stats(packages):
     stats = defaultdict(int)
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
                   ` (3 preceding siblings ...)
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 4/8] support/script: Make CVE class independent of the Pacakage class Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-07-29 18:03   ` Matthew Weber
  2020-08-28  9:45   ` Thomas Petazzoni
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 6/8] support/script/pkg-stats: Manage the CVEs that need to be check Gregory CLEMENT
                   ` (3 subsequent siblings)
  8 siblings, 2 replies; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

This scripts takes as entry on stdin a JSON description of the package
used for a given configuration. This description is the one generated
by "make show-info".

The script generates the list of all the package used and if they are
affected by a CVE. The output is either a JSON or an HTML file similar
to the one generated by pkg-stats.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 support/scripts/cve-checker | 258 ++++++++++++++++++++++++++++++++++++
 1 file changed, 258 insertions(+)
 create mode 100755 support/scripts/cve-checker

diff --git a/support/scripts/cve-checker b/support/scripts/cve-checker
new file mode 100755
index 0000000000..19fd104b56
--- /dev/null
+++ b/support/scripts/cve-checker
@@ -0,0 +1,258 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2009 by Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+# Copyright (C) 2020 by Gregory CLEMENT <gregory.clement@bootlin.com>
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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
+
+import argparse
+import datetime
+import os
+import json
+import sys
+
+sys.path.append('utils/')
+
+import cve as cvecheck
+
+class Package:
+    def __init__(self, name, version, ignored_cves):
+        self.name = name
+        self.version = version
+        self.cves = list()
+        self.ignored_cves = ignored_cves
+
+def check_package_cves(nvd_path, packages):
+    if not os.path.isdir(nvd_path):
+        os.makedirs(nvd_path)
+
+    for cve in cvecheck.CVE.read_nvd_dir(nvd_path):
+        for pkg_name in cve.pkg_names:
+            pkg = packages.get(pkg_name, '')
+            if pkg and cve.affects(pkg.name, pkg.version, pkg.ignored_cves):
+                pkg.cves.append(cve.identifier)
+
+html_header = """
+<head>
+<script src=\"https://www.kryogenix.org/code/browser/sorttable/sorttable.js\"></script>
+<style type=\"text/css\">
+table {
+  width: 100%;
+}
+td {
+  border: 1px solid black;
+}
+td.centered {
+  text-align: center;
+}
+td.wrong {
+  background: #ff9a69;
+}
+td.correct {
+  background: #d2ffc4;
+}
+td.nopatches {
+  background: #d2ffc4;
+}
+td.somepatches {
+  background: #ffd870;
+}
+td.lotsofpatches {
+  background: #ff9a69;
+}
+
+td.good_url {
+  background: #d2ffc4;
+}
+td.missing_url {
+  background: #ffd870;
+}
+td.invalid_url {
+  background: #ff9a69;
+}
+
+td.version-good {
+  background: #d2ffc4;
+}
+td.version-needs-update {
+  background: #ff9a69;
+}
+td.version-unknown {
+ background: #ffd870;
+}
+td.version-error {
+ background: #ccc;
+}
+
+</style>
+<title>CVE status for Buildroot packages</title>
+</head>
+
+<a href=\"#results\">CVE Status</a><br/>
+
+<p id=\"sortable_hint\"></p>
+"""
+
+
+html_footer = """
+</body>
+<script>
+if (typeof sorttable === \"object\") {
+  document.getElementById(\"sortable_hint\").innerHTML =
+  \"hint: the table can be sorted by clicking the column headers\"
+}
+</script>
+</html>
+"""
+
+
+def infra_str(infra_list):
+    if not infra_list:
+        return "Unknown"
+    elif len(infra_list) == 1:
+        return "<b>%s</b><br/>%s" % (infra_list[0][1], infra_list[0][0])
+    elif infra_list[0][1] == infra_list[1][1]:
+        return "<b>%s</b><br/>%s + %s" % \
+            (infra_list[0][1], infra_list[0][0], infra_list[1][0])
+    else:
+        return "<b>%s</b> (%s)<br/><b>%s</b> (%s)" % \
+            (infra_list[0][1], infra_list[0][0],
+             infra_list[1][1], infra_list[1][0])
+
+
+def boolean_str(b):
+    if b:
+        return "Yes"
+    else:
+        return "No"
+
+
+def dump_html_pkg(f, pkg):
+    f.write(" <tr>\n")
+    f.write("  <td>%s</td>\n" % pkg.name)
+
+    # Current version
+    if len(pkg.version) > 20:
+        version = pkg.version[:20] + "..."
+    else:
+        version = pkg.version
+    f.write("  <td class=\"centered\">%s</td>\n" % version)
+
+    # CVEs
+    td_class = ["centered"]
+    if len(pkg.cves) == 0:
+        td_class.append("correct")
+    else:
+        td_class.append("wrong")
+    f.write("  <td class=\"%s\">\n" % " ".join(td_class))
+    for cve in pkg.cves:
+        f.write("   <a href=\"https://security-tracker.debian.org/tracker/%s\">%s<br/>\n" % (cve, cve))
+    f.write("  </td>\n")
+
+    f.write(" </tr>\n")
+
+
+def dump_html_all_pkgs(f, packages):
+    f.write("""
+<table class=\"sortable\">
+<tr>
+<td>Package</td>
+<td class=\"centered\">Version</td>
+<td class=\"centered\">CVEs</td>
+</tr>
+""")
+    for pkg in packages:
+        dump_html_pkg(f, pkg)
+    f.write("</table>")
+
+
+def dump_html_gen_info(f, date):
+    # Updated on Mon Feb 19 08:12:08 CET 2018
+    f.write("<p><i>Updated on %s</i></p>\n" % (str(date)))
+
+
+def dump_html(packages, date, output):
+    with open(output, 'w') as f:
+        f.write(html_header)
+        dump_html_all_pkgs(f, packages)
+        dump_html_gen_info(f, date)
+        f.write(html_footer)
+
+
+def dump_json(packages, date, output):
+    # Format packages as a dictionnary instead of a list
+    # Exclude local field that does not contains real date
+    excluded_fields = ['url_worker', 'name']
+    pkgs = {
+        pkg.name: {
+            k: v
+            for k, v in pkg.__dict__.items()
+            if k not in excluded_fields
+        } for pkg in packages
+    }
+     # The actual structure to dump, add date to it
+    final = {'packages': pkgs,
+             'date': str(date)}
+    with open(output, 'w') as f:
+        json.dump(final, f, indent=2, separators=(',', ': '))
+        f.write('\n')
+
+
+def resolvepath(path):
+        return os.path.abspath(os.path.expanduser(path))
+
+
+def parse_args():
+    parser = argparse.ArgumentParser()
+    output = parser.add_argument_group('output', 'Output file(s)')
+    output.add_argument('--html', dest='html', type=resolvepath,
+                        help='HTML output file')
+    output.add_argument('--json', dest='json', type=resolvepath,
+                        help='JSON output file')
+    packages = parser.add_mutually_exclusive_group()
+    parser.add_argument('--nvd-path', dest='nvd_path',
+                        help='Path to the local NVD database',type=resolvepath,
+                        default='./nvd_dl')
+    args = parser.parse_args()
+    if not args.html and not args.json:
+        parser.error('at least one of --html or --json (or both) is required')
+    return args
+
+
+def __main__():
+    packages = list()
+    exclude_pacakges = ["linux", "gcc"]
+    content = json.load(sys.stdin)
+    for item in content:
+        if item in exclude_pacakges:
+            continue
+        pkg = content[item]
+        p = Package(item, pkg.get('version', ''), pkg.get('ignore_cves', ''))
+        packages.append(p)
+
+    args = parse_args()
+    date = datetime.datetime.utcnow()
+
+    if args.nvd_path:
+        print("Checking packages CVEs")
+        check_package_cves(args.nvd_path, {p.name: p for p in packages})
+    if args.html:
+        print("Write HTML")
+        dump_html(packages, date, args.html)
+    if args.json:
+        print("Write JSON")
+        dump_json(packages, date, args.json)
+
+__main__()
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 6/8] support/script/pkg-stats: Manage the CVEs that need to be check
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
                   ` (4 preceding siblings ...)
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 7/8] support/script/cve-checker: " Gregory CLEMENT
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

When looking for if a package is affected, the version comparison can
fail. This means that we don't know if the version of the package used
is affected or not and we need to check manually the version.

This patch exposes this new information in json and html format.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 support/scripts/pkg-stats | 29 ++++++++++++++++++++++++++---
 1 file changed, 26 insertions(+), 3 deletions(-)

diff --git a/support/scripts/pkg-stats b/support/scripts/pkg-stats
index f073e866cb..62f019cf7c 100755
--- a/support/scripts/pkg-stats
+++ b/support/scripts/pkg-stats
@@ -106,6 +106,7 @@ class Package:
         self.url = None
         self.url_worker = None
         self.cves = list()
+        self.cves_to_check = list()
         self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
         self.status = {}
 
@@ -501,7 +502,10 @@ def check_package_cves(nvd_path, packages):
         for pkg_name in cve.pkg_names:
             if pkg_name in packages:
                 pkg = packages[pkg_name]
-                if cve.affects(pkg.name, pkg.current_version, pkg.cve_ignored_list()) == cve.CVE_AFFECTS :
+                affected = cve.affects(pkg.name, pkg.current_version, pkg.cve_ignored_list())
+                if (affected == cve.CVE_UNKNOWN):
+                    pkg.cves_to_check.append(cve.identifier)
+                elif affected == cve.CVE_AFFECTS:
                     pkg.cves.append(cve.identifier)
 
 def calculate_stats(packages):
@@ -541,8 +545,11 @@ def calculate_stats(packages):
             stats["version-not-uptodate"] += 1
         stats["patches"] += pkg.patch_count
         stats["total-cves"] += len(pkg.cves)
+        stats["total-cves-to-check"] += len(pkg.cves_to_check)
         if len(pkg.cves) != 0:
             stats["pkg-cves"] += 1
+        if len(pkg.cves_to_check) != 0:
+            stats["pkg-cves_to_check"] += 1
     return stats
 
 
@@ -765,6 +772,17 @@ def dump_html_pkg(f, pkg):
         f.write("   <a href=\"https://security-tracker.debian.org/tracker/%s\">%s<br/>\n" % (cve, cve))
     f.write("  </td>\n")
 
+    # CVEs to check
+    td_class = ["centered"]
+    if len(pkg.cves_to_check) == 0:
+        td_class.append("correct")
+    else:
+        td_class.append("wrong")
+    f.write("  <td class=\"%s\">\n" % " ".join(td_class))
+    for cve in pkg.cves_to_check:
+        f.write("   <a href=\"https://security-tracker.debian.org/tracker/%s\">%s<br/>\n" % (cve, cve))
+    f.write("  </td>\n")
+
     f.write(" </tr>\n")
 
 
@@ -783,6 +801,7 @@ def dump_html_all_pkgs(f, packages):
 <td class=\"centered\">Warnings</td>
 <td class=\"centered\">Upstream URL</td>
 <td class=\"centered\">CVEs</td>
+<td class=\"centered\">CVEs to check</td>
 </tr>
 """)
     for pkg in sorted(packages):
@@ -821,10 +840,14 @@ def dump_html_stats(f, stats):
             stats["version-not-uptodate"])
     f.write("<tr><td>Packages with no known upstream version</td><td>%s</td></tr>\n" %
             stats["version-unknown"])
-    f.write("<tr><td>Packages affected by CVEs</td><td>%s</td></tr>\n" %
+    f.write("<tr><td>"Packages that might be affected by CVEs, where version needs to be checked</td><td>%s</td></tr>\n" %
             stats["pkg-cves"])
-    f.write("<tr><td>Total number of CVEs affecting all packages</td><td>%s</td></tr>\n" %
+    f.write("<tr><td>Total number of CVEs that might affect all packages, where version needs to be checked</td><td>%s</td></tr>\n" %
             stats["total-cves"])
+    f.write("<tr><td>Packages affected by CVEs</td><td>%s</td></tr>\n" %
+            stats["pkg-cves_to_check"])
+    f.write("<tr><td>Total number of CVEs affecting all packages</td><td>%s</td></tr>\n" %
+            stats["total-cves_to_check"])
     f.write("</table>\n")
 
 
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 7/8] support/script/cve-checker: Manage the CVEs that need to be check
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
                   ` (5 preceding siblings ...)
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 6/8] support/script/pkg-stats: Manage the CVEs that need to be check Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 8/8] package/pkg-utils/cve.py: Manage case when package version doesn't exist Gregory CLEMENT
  2020-07-28  7:52 ` [Buildroot] [PATCH v3 0/8] Improving CVE reporting Thomas Petazzoni
  8 siblings, 0 replies; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

When looking for if a package is affected, the version comparison can
fail. This means that we don't know if the version of the package used
is affected or not and we need to check manually the version.

This patch exposes this new information in json and html format.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 support/scripts/cve-checker | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/support/scripts/cve-checker b/support/scripts/cve-checker
index 19fd104b56..712ec1ded0 100755
--- a/support/scripts/cve-checker
+++ b/support/scripts/cve-checker
@@ -32,6 +32,7 @@ class Package:
         self.name = name
         self.version = version
         self.cves = list()
+        self.cves_to_check = list()
         self.ignored_cves = ignored_cves
 
 def check_package_cves(nvd_path, packages):
@@ -41,8 +42,12 @@ def check_package_cves(nvd_path, packages):
     for cve in cvecheck.CVE.read_nvd_dir(nvd_path):
         for pkg_name in cve.pkg_names:
             pkg = packages.get(pkg_name, '')
-            if pkg and cve.affects(pkg.name, pkg.version, pkg.ignored_cves):
-                pkg.cves.append(cve.identifier)
+            if pkg:
+                affected = cve.affects(pkg.name, pkg.version, pkg.ignored_cves)
+                if (affected == cve.CVE_UNKNOWN):
+                    pkg.cves_to_check.append(cve.identifier)
+                elif affected == cve.CVE_AFFECTS:
+                    pkg.cves.append(cve.identifier)
 
 html_header = """
 <head>
@@ -161,6 +166,17 @@ def dump_html_pkg(f, pkg):
         f.write("   <a href=\"https://security-tracker.debian.org/tracker/%s\">%s<br/>\n" % (cve, cve))
     f.write("  </td>\n")
 
+    # CVEs to check
+    td_class = ["centered"]
+    if len(pkg.cves_to_check) == 0:
+        td_class.append("correct")
+    else:
+        td_class.append("wrong")
+    f.write("  <td class=\"%s\">\n" % " ".join(td_class))
+    for cve in pkg.cves_to_check:
+        f.write("   <a href=\"https://security-tracker.debian.org/tracker/%s\">%s<br/>\n" % (cve, cve))
+    f.write("  </td>\n")
+
     f.write(" </tr>\n")
 
 
@@ -171,6 +187,7 @@ def dump_html_all_pkgs(f, packages):
 <td>Package</td>
 <td class=\"centered\">Version</td>
 <td class=\"centered\">CVEs</td>
+<td class=\"centered\">CVEs to check</td>
 </tr>
 """)
     for pkg in packages:
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 8/8] package/pkg-utils/cve.py: Manage case when package version doesn't exist
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
                   ` (6 preceding siblings ...)
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 7/8] support/script/cve-checker: " Gregory CLEMENT
@ 2020-07-24 15:43 ` Gregory CLEMENT
  2020-07-28  7:52 ` [Buildroot] [PATCH v3 0/8] Improving CVE reporting Thomas Petazzoni
  8 siblings, 0 replies; 17+ messages in thread
From: Gregory CLEMENT @ 2020-07-24 15:43 UTC (permalink / raw)
  To: buildroot

Until now, when a package didn't report a version, then the CVE
comparison was just skipped. It leads most of the time to declare the
package not affected by the CVE.

Instead of it, report the CVE_UNKNOWN status in order to be aware that
the CVE related to this package has to be checked.

Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
---
 support/scripts/cve.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/support/scripts/cve.py b/support/scripts/cve.py
index 4e83ac8961..83daf6b089 100755
--- a/support/scripts/cve.py
+++ b/support/scripts/cve.py
@@ -193,6 +193,7 @@ class CVE:
         if (self.identifier in cve_ignore_list):
             return self.CVE_DOESNT_AFFECT
 
+        unknown_pkg_version = False
         for cpe in self.each_cpe():
             affected = True
             if cpe['product'] != name:
@@ -205,6 +206,7 @@ class CVE:
             pkg_version = distutils.version.LooseVersion(version)
             if not hasattr(pkg_version, "version"):
                 print("Cannot parse package '%s' version '%s'" % (name, version))
+                unknown_pkg_version = True
                 continue
 
             if cpe['v_start']:
@@ -224,4 +226,8 @@ class CVE:
                         return self.CVE_UNKNOWN
             if (affected):
                 return self.CVE_AFFECTS
-        return self.CVE_DOESNT_AFFECT
+
+        if unknown_pkg_version:
+            return self.CVE_UNKNOWN
+        else:
+            return self.CVE_DOESNT_AFFECT
-- 
2.27.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 0/8] Improving CVE reporting
  2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
                   ` (7 preceding siblings ...)
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 8/8] package/pkg-utils/cve.py: Manage case when package version doesn't exist Gregory CLEMENT
@ 2020-07-28  7:52 ` Thomas Petazzoni
  2020-07-28 22:07   ` Titouan Christophe
  8 siblings, 1 reply; 17+ messages in thread
From: Thomas Petazzoni @ 2020-07-28  7:52 UTC (permalink / raw)
  To: buildroot

Hello Gr?gory,

On Fri, 24 Jul 2020 17:43:48 +0200
Gregory CLEMENT <gregory.clement@bootlin.com> wrote:

> Titouan also mentioned that CPE nodes can be ORed or ANDed and I
> confirm it. So I had a closer look on it. First found there are
> children node only with the AND operator. Then most of the time the
> AND associate a version of product than could be affected with a
> version of another product which usually provide service to the first
> one such as an operating system. Or we could have the association of a
> software and an hardware. Having an application in the second part of
> the AND can happen but is very rare.
> 
> Supporting these features will make the code more complex. By just
> parsing the node recursively without applying the AND condition, we
> could have some false positive CVE. But at least we won't miss CVE and
> the case were it would be useful for buildroot should be very scarce.

Could you give some specific example of where those AND operators with
child nodes are used ? This would help understand what are the
situations that make use of this.

Thanks!

Thomas
-- 
Thomas Petazzoni, CTO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 0/8] Improving CVE reporting
  2020-07-28  7:52 ` [Buildroot] [PATCH v3 0/8] Improving CVE reporting Thomas Petazzoni
@ 2020-07-28 22:07   ` Titouan Christophe
  0 siblings, 0 replies; 17+ messages in thread
From: Titouan Christophe @ 2020-07-28 22:07 UTC (permalink / raw)
  To: buildroot

Hello all,

On 28/07/20 09:52, Thomas Petazzoni wrote:
> 
> Could you give some specific example of where those AND operators with
> child nodes are used ? This would help understand what are the
> situations that make use of this.
> 
> Thanks!
> 
> Thomas
> 

See for example CVE-2019-3699 
(https://nvd.nist.gov/vuln/detail/CVE-2019-3699). This is about a 
vulnerability of privoxy when it runs on OpenSuse. This CVE is currently 
detected for the privoxy package on http://autobuild.buildroot.net/stats/

I have extracted the NVD entry from the NVD 2019 json file for 
convenience: http://paste.awesom.eu/ibNy . The matching CPEs are 
logically declared as follows:

AND(
     privoxy:privoxy <3.0.28-lp151.1.1,
     opensuse:leap:15.1
)


They seem to use this to indicate if a particular distribution/OS is 
vulnerable to the CVE.

Titouan

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker Gregory CLEMENT
@ 2020-07-29 18:03   ` Matthew Weber
  2020-08-28  9:45   ` Thomas Petazzoni
  1 sibling, 0 replies; 17+ messages in thread
From: Matthew Weber @ 2020-07-29 18:03 UTC (permalink / raw)
  To: buildroot

Gregory,

On Fri, Jul 24, 2020 at 10:44 AM Gregory CLEMENT
<gregory.clement@bootlin.com> wrote:
>
> This scripts takes as entry on stdin a JSON description of the package
> used for a given configuration. This description is the one generated
> by "make show-info".
>
> The script generates the list of all the package used and if they are
> affected by a CVE. The output is either a JSON or an HTML file similar
> to the one generated by pkg-stats.
>
> Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>

Tested-by: Matthew Weber <matthew.weber@rockwellcollins.com>=

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 1/8] support/scripts: Turn CVE check into a module
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 1/8] support/scripts: Turn CVE check into a module Gregory CLEMENT
@ 2020-08-28  7:18   ` Thomas Petazzoni
  0 siblings, 0 replies; 17+ messages in thread
From: Thomas Petazzoni @ 2020-08-28  7:18 UTC (permalink / raw)
  To: buildroot

On Fri, 24 Jul 2020 17:43:49 +0200
Gregory CLEMENT <gregory.clement@bootlin.com> wrote:

> In order to be able to do CVE checking outside of pkg-stat, move the
> CVE class in a module that can be used by other scripts.
> 
> Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>
> ---
>  support/scripts/cve.py    | 156 ++++++++++++++++++++++++++++++++++++++
>  support/scripts/pkg-stats | 132 +-------------------------------
>  2 files changed, 160 insertions(+), 128 deletions(-)
>  create mode 100755 support/scripts/cve.py

I've improved the commit log a bit, and applied to next. Thanks!

Thomas
-- 
Thomas Petazzoni, CTO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 2/8] support/scripts/cve.py: Switch to JSON 1.1
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 2/8] support/scripts/cve.py: Switch to JSON 1.1 Gregory CLEMENT
@ 2020-08-28  7:34   ` Thomas Petazzoni
  0 siblings, 0 replies; 17+ messages in thread
From: Thomas Petazzoni @ 2020-08-28  7:34 UTC (permalink / raw)
  To: buildroot

Hello,

On Fri, 24 Jul 2020 17:43:50 +0200
Gregory CLEMENT <gregory.clement@bootlin.com> wrote:

> In 2019, the JSON vulnerability feeds switched from version 1.0 to
> 1.1.
> 
> The main difference is the removal of the affects element that was
> used to check if a package was affected by a CVE.
> 
> This information is duplicated in the configuration element which
> contains in the end the cpeid as well as properties about the versions
> affected. Instead of having a list of the versions affected, with
> these properties, it is possible to have a range of versions.
> 
> Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>

So, I've applied to next, but after doing a number of changes. First,
your patch caused quite a few flake8 warnings.

Then, I compared the list of CVEs before/after this changed, and
noticed a number of CVEs were "lost", investigated why, and fixed this.
With your patch applied and my fixes, we now only have additional CVEs,
and we're not losing any CVEs compared to before.

I will comment below on various things I have changed.

> diff --git a/support/scripts/cve.py b/support/scripts/cve.py
> index 8a4087ef8a..a8861d966c 100755
> --- a/support/scripts/cve.py
> +++ b/support/scripts/cve.py
> @@ -34,9 +34,19 @@ except ImportError:
>  sys.path.append('utils/')
>  
>  NVD_START_YEAR = 2002
> -NVD_JSON_VERSION = "1.0"
> +NVD_JSON_VERSION = "1.1"
>  NVD_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/" + NVD_JSON_VERSION
>  
> +import operator

Imports should be at the beginning of the file, this was a flake8
warning.

> +
> +ops = {
> +    '>=' : operator.ge,
> +    '>' : operator.gt,
> +    '<=' : operator.le,
> +    '<' : operator.lt,
> +    '=' : operator.eq

No space before the ':', this was a flake8 warning as well.

> +            key =['vendor', 'product', 'v_start', 'op_start', 'v_end', 'op_end']

There was a missing space after the =

> +            val = [vendor, product, v_start, op_start, v_end, op_end]
> +            yield dict(zip(key, val))

but I have anyway simplified this to just:

            yield {
                'vendor': vendor,
                'product': product,
                'v_start': v_start,
                'op_start': op_start,
                'v_end': v_end,
                'op_end': op_end
            }

>      def affects(self, br_pkg):
>          """
> @@ -125,32 +193,35 @@ class CVE:
>          if br_pkg.is_cve_ignored(self.identifier):
>              return self.CVE_DOESNT_AFFECT
>  
> -        for product in self.each_product():
> -            if product['product_name'] != br_pkg.name:
> +        for cpe in self.each_cpe():
> +            affected = True

I didn't really like this "affected = True" and the whole logic around
"affected". So I've changed the logic a bit to rather skip the
iteration of the loop if the current version falls outside of the range
described by the v_start/v_end fields. I'll post the code later below.

> +            if cpe['product'] != br_pkg.name:
>                  continue
> +            if cpe['v_start'] == '-':
> +                return self.CVE_AFFECTS

This change caused a lot of CVEs to be added, as it treats "-" as a
wildcard, and we were not doing this before. So, I've pushed a
preliminary commit at
https://git.buildroot.org/buildroot/commit/?h=next&id=008ca2c583cb9dc70cd30c5318b3b1cbef57b06a,
which changed the previous JSON 1.0 code to also treat "-" as a
wildcard. This allowed me to verify step by step how the list of CVEs
affecting packages was evolving throughout the changes we're making to
this code.

So, between the original JSON 1.0 code, and the JSON 1.0 code that got
changed to treat "-" as a version wildcard, lots (~500) of CVEs got
added. Turns out that in the JSON 1.0 schema, there are a lot of CVEs
that describe the version precisely *and* with a wildcard, so we had
lost some "precision" here. So the amount of CVEs affecting our
packages jumped from 141 to 658.

However, for many of those CVEs, the JSON 1.1 data is more precise, and
has less of those wildcard versions. So with the JSON 1.1 usage, the
total number of CVEs affecting us dropped to 420.

For those interested in looking at the differences:

 - JSON dump of pkg-stats output, before all those changes:
   https://bootlin.com/~thomas/cve-1.0-dump.json

 - JSON dump of pkg-stats output, after we treat "-" as a version
   wildcard, but still using the NVD database in 1.0 schema:
   https://bootlin.com/~thomas/cve-1.0-dump-with-version-wildcard.json

 - JSON dump of pkg-stats output, after all the changes, which means
   we're using the NVD database in 1.1 schema:
   https://bootlin.com/~thomas/cve-1.1-dump.json

Note: these JSON dumps don't have the "latest upstream version" and
"upstream URL checks" information, as I disabled them to make pkg-stats
a bit faster and focus on the CVE stuff.

> +            if not (cpe['v_start'] or cpe['v_end']):
> +                print("No CVE affected version")
> +                continue
> +            pkg_version = distutils.version.LooseVersion(br_pkg.current_version)
> +            if not hasattr(pkg_version, "version"):
> +                print("Cannot parse package '%s' version '%s'" % (br_pkg.name, br_pkg.current_version))
> +                continue

There was no reason to do this logic inside the loop each time, so I
moved it outside of the loop. The pkg_version variable remains checked
inside the loop.

> +            if cpe['v_start']:
> +                    try:
> +                        cve_affected_version = distutils.version.LooseVersion(cpe['v_start'])
> +                        affected = ops.get(cpe['op_start'])(pkg_version, cve_affected_version)
> +                        break

This break was bogus, and caused many CVEs to be ignored.

> +                    except:
> +                        return self.CVE_UNKNOWN
>  
> -            for v in product['version']['version_data']:
> -                if v["version_affected"] == "=":
> -                    if br_pkg.current_version == v["version_value"]:
> -                        return self.CVE_AFFECTS
> -                elif v["version_affected"] == "<=":
> -                    pkg_version = distutils.version.LooseVersion(br_pkg.current_version)
> -                    if not hasattr(pkg_version, "version"):
> -                        print("Cannot parse package '%s' version '%s'" % (br_pkg.name, br_pkg.current_version))
> -                        continue
> -                    cve_affected_version = distutils.version.LooseVersion(v["version_value"])
> -                    if not hasattr(cve_affected_version, "version"):
> -                        print("Cannot parse CVE affected version '%s'" % v["version_value"])
> -                        continue
> +            if (affected and cpe['v_end']):
>                      try:
> -                        affected = pkg_version <= cve_affected_version
> +                        cve_affected_version = distutils.version.LooseVersion(cpe['v_end'])
> +                        affected = ops.get(cpe['op_end'])(pkg_version, cve_affected_version)
>                          break

Ditto for this break.

So overall, the code looks like this:

        for cpe in self.each_cpe():
            if cpe['product'] != br_pkg.name:
                continue
            if cpe['v_start'] == '-':
                return self.CVE_AFFECTS
            if not cpe['v_start'] and not cpe['v_end']:
                print("No CVE affected version")
                continue
            if not pkg_version:
                continue

            if cpe['v_start']:
                try:
                    cve_affected_version = distutils.version.LooseVersion(cpe['v_start'])
                    inrange = ops.get(cpe['op_start'])(pkg_version, cve_affected_version)
                except TypeError:
                    return self.CVE_UNKNOWN

                # current package version is before v_start, so we're
                # not affected by the CVE
                if not inrange:
                    continue

            if cpe['v_end']:
                try:
                    cve_affected_version = distutils.version.LooseVersion(cpe['v_end'])
                    inrange = ops.get(cpe['op_end'])(pkg_version, cve_affected_version)
                except TypeError:
                    return self.CVE_UNKNOWN

                # current package version is after v_end, so we're
                # not affected by the CVE
                if not inrange:
                    continue

            # We're in the version range affected by this CVE
            return self.CVE_AFFECTS

        return self.CVE_DOESNT_AFFECT

Feel free to comment on that, if you see any issue with this. We can
always re-adjust what I have pushed.

Thanks!

Thomas
-- 
Thomas Petazzoni, CTO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 3/8] package/pkg-utils: show-info: report the list of the CVEs ignored
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 3/8] package/pkg-utils: show-info: report the list of the CVEs ignored Gregory CLEMENT
@ 2020-08-28  8:51   ` Thomas Petazzoni
  0 siblings, 0 replies; 17+ messages in thread
From: Thomas Petazzoni @ 2020-08-28  8:51 UTC (permalink / raw)
  To: buildroot

On Fri, 24 Jul 2020 17:43:51 +0200
Gregory CLEMENT <gregory.clement@bootlin.com> wrote:

> Add the list of the CVEs to ignore for each package because they
> already have a fix for it.
> 
> This information will be useful for a cve-checker.
> 
> Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>

Hello, I have applied to next, after tweaking a bit the code.

> ---
>  package/pkg-utils.mk | 5 ++++-
>  1 file changed, 4 insertions(+), 1 deletion(-)
> 
> diff --git a/package/pkg-utils.mk b/package/pkg-utils.mk
> index d88a14ab0f..21b415cbf3 100644
> --- a/package/pkg-utils.mk
> +++ b/package/pkg-utils.mk
> @@ -117,7 +117,10 @@ define _json-info-pkg
>  		$(call make-comma-list,$(sort $($(1)_FINAL_ALL_DEPENDENCIES)))
>  	],
>  	"reverse_dependencies": [
> -		$(call make-comma-list,$(sort $($(1)_RDEPENDENCIES)))
> +		$(call make-comma-list,$(sort $($(1)_RDEPENDENCIES))),

Adding the final comma on this line was not needed.

> +	],
> +	"ignore_cves": [
> +		$(call make-comma-list,$(sort $($(1)_IGNORE_CVES)))
>  	]

I changed to only emit the ignore_cves property if there are ignored
CVEs, like this:

+	$(if $($(1)_IGNORE_CVES),
+		$(comma) "ignore_cves": [
+			$(call make-comma-list,$(sort $($(1)_IGNORE_CVES)))
+		]
+	)

Do not hesitate to check if it still works for you. It does for me :-)

Thanks,

Thomas
-- 
Thomas Petazzoni, CTO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 4/8] support/script: Make CVE class independent of the Pacakage class
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 4/8] support/script: Make CVE class independent of the Pacakage class Gregory CLEMENT
@ 2020-08-28  9:03   ` Thomas Petazzoni
  0 siblings, 0 replies; 17+ messages in thread
From: Thomas Petazzoni @ 2020-08-28  9:03 UTC (permalink / raw)
  To: buildroot

Hello,

Typo in the commit title: Pacakage -> Package

On Fri, 24 Jul 2020 17:43:52 +0200
Gregory CLEMENT <gregory.clement@bootlin.com> wrote:

> The affects method of the CVE use the Package class defined in
> pkg-stats. The purpose of migrating the CVE class outside of pkg-stats
> was to be able to reuse it from other scripts. So let's remove the
> Package dependency and only use the needed information.
> 
> Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>

I've applied to next, with a few changes. See below.

> +        if (self.identifier in cve_ignore_list):

No parenthesis needed.

> diff --git a/support/scripts/pkg-stats b/support/scripts/pkg-stats
> index 58847f9ca6..f073e866cb 100755
> --- a/support/scripts/pkg-stats
> +++ b/support/scripts/pkg-stats
> @@ -242,11 +242,12 @@ class Package:
>                      self.status['pkg-check'] = ("error", "{} warnings".format(self.warnings))
>                  return
>  
> -    def is_cve_ignored(self, cve):
> +    def cve_ignored_list(self):

Renamed to just ignored_cves(self), and more importantly, added the
@property statement, since really that's what it is: a property of the
class. This allows to reference it like this: pkg.ignored_cves as if it
was a normal property of the class, not a function.

> +        print(self.all_ignored_cves.get(self.pkgvar(), []))

Spurious debug message.

Thanks!

Thomas
-- 
Thomas Petazzoni, CTO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker
  2020-07-24 15:43 ` [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker Gregory CLEMENT
  2020-07-29 18:03   ` Matthew Weber
@ 2020-08-28  9:45   ` Thomas Petazzoni
  1 sibling, 0 replies; 17+ messages in thread
From: Thomas Petazzoni @ 2020-08-28  9:45 UTC (permalink / raw)
  To: buildroot

Hello,

On Fri, 24 Jul 2020 17:43:53 +0200
Gregory CLEMENT <gregory.clement@bootlin.com> wrote:

> This scripts takes as entry on stdin a JSON description of the package
> used for a given configuration. This description is the one generated
> by "make show-info".
> 
> The script generates the list of all the package used and if they are
> affected by a CVE. The output is either a JSON or an HTML file similar
> to the one generated by pkg-stats.
> 
> Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com>

Thanks, I have applied to next, but after doing a number of changes,
see below.

> +import argparse
> +import datetime
> +import os
> +import json
> +import sys
> +
> +sys.path.append('utils/')

This was not needed.

> +
> +import cve as cvecheck
> +
> +class Package:
> +    def __init__(self, name, version, ignored_cves):
> +        self.name = name
> +        self.version = version
> +        self.cves = list()
> +        self.ignored_cves = ignored_cves
> +
> +def check_package_cves(nvd_path, packages):
> +    if not os.path.isdir(nvd_path):
> +        os.makedirs(nvd_path)
> +
> +    for cve in cvecheck.CVE.read_nvd_dir(nvd_path):
> +        for pkg_name in cve.pkg_names:
> +            pkg = packages.get(pkg_name, '')
> +            if pkg and cve.affects(pkg.name, pkg.version, pkg.ignored_cves):

This was not correct as cve.affects() no longer returns a boolean. Due
to this, all existing CVEs were reported in the generated HTML/JSON as
affecting the package.

> +                pkg.cves.append(cve.identifier)
> +
> +html_header = """
> +<head>
> +<script src=\"https://www.kryogenix.org/code/browser/sorttable/sorttable.js\"></script>
> +<style type=\"text/css\">
> +table {
> +  width: 100%;
> +}
> +td {
> +  border: 1px solid black;
> +}
> +td.centered {
> +  text-align: center;
> +}
> +td.wrong {
> +  background: #ff9a69;
> +}
> +td.correct {
> +  background: #d2ffc4;
> +}
> +td.nopatches {
> +  background: #d2ffc4;
> +}
> +td.somepatches {
> +  background: #ffd870;
> +}
> +td.lotsofpatches {
> +  background: #ff9a69;
> +}
> +
> +td.good_url {
> +  background: #d2ffc4;
> +}
> +td.missing_url {
> +  background: #ffd870;
> +}
> +td.invalid_url {
> +  background: #ff9a69;
> +}
> +
> +td.version-good {
> +  background: #d2ffc4;
> +}
> +td.version-needs-update {
> +  background: #ff9a69;
> +}
> +td.version-unknown {
> + background: #ffd870;
> +}
> +td.version-error {
> + background: #ccc;
> +}

Lots of these CSS classes were not useful, so I dropped them.

> +
> +</style>
> +<title>CVE status for Buildroot packages</title>

Changed "Buildroot packages" for "Buildroot configuration". Indeed,
compared to pkg-stats which operates on all packages (it's a tool for
Buildroot maintenance), cve-checker is really about a given Buildroot
configuration.

> +def infra_str(infra_list):

This function was not used anywhere, so I dropped it.

> +def boolean_str(b):

This function was not used anywhere, so I dropped it.


> +def dump_json(packages, date, output):
> +    # Format packages as a dictionnary instead of a list
> +    # Exclude local field that does not contains real date
> +    excluded_fields = ['url_worker', 'name']
> +    pkgs = {
> +        pkg.name: {
> +            k: v
> +            for k, v in pkg.__dict__.items()
> +            if k not in excluded_fields
> +        } for pkg in packages

I simplified that a bit, as we don't want all fields in the JSON I
believe, just the version and list of CVEs. For example, the list of
ignored CVEs is not really relevant.

> +
> +def parse_args():
> +    parser = argparse.ArgumentParser()
> +    output = parser.add_argument_group('output', 'Output file(s)')
> +    output.add_argument('--html', dest='html', type=resolvepath,
> +                        help='HTML output file')
> +    output.add_argument('--json', dest='json', type=resolvepath,
> +                        help='JSON output file')
> +    packages = parser.add_mutually_exclusive_group()

This line was not used.

> +    parser.add_argument('--nvd-path', dest='nvd_path',
> +                        help='Path to the local NVD database',type=resolvepath,
> +                        default='./nvd_dl')

The default value doesn't exist for pkg-stats, I'm not sure it makes
sense to have a default value. I've however added a required=True
because this script doesn't do anything useful if we don't have access
to the NVD data.

> +def __main__():
> +    packages = list()
> +    exclude_pacakges = ["linux", "gcc"]

I'm not sure why those two packages were excluded, so I've dropped
that, at least for now. We can of course improve things later on.

> +    content = json.load(sys.stdin)
> +    for item in content:
> +        if item in exclude_pacakges:
> +            continue
> +        pkg = content[item]
> +        p = Package(item, pkg.get('version', ''), pkg.get('ignore_cves', ''))
> +        packages.append(p)
> +
> +    args = parse_args()
> +    date = datetime.datetime.utcnow()
> +
> +    if args.nvd_path:

I've dropped this "if", since args.nvd_path is a required option.

As said above: applied to next with all those changes. Thanks!

Thomas
-- 
Thomas Petazzoni, CTO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2020-08-28  9:45 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-24 15:43 [Buildroot] [PATCH v3 0/8] Improving CVE reporting Gregory CLEMENT
2020-07-24 15:43 ` [Buildroot] [PATCH v3 1/8] support/scripts: Turn CVE check into a module Gregory CLEMENT
2020-08-28  7:18   ` Thomas Petazzoni
2020-07-24 15:43 ` [Buildroot] [PATCH v3 2/8] support/scripts/cve.py: Switch to JSON 1.1 Gregory CLEMENT
2020-08-28  7:34   ` Thomas Petazzoni
2020-07-24 15:43 ` [Buildroot] [PATCH v3 3/8] package/pkg-utils: show-info: report the list of the CVEs ignored Gregory CLEMENT
2020-08-28  8:51   ` Thomas Petazzoni
2020-07-24 15:43 ` [Buildroot] [PATCH v3 4/8] support/script: Make CVE class independent of the Pacakage class Gregory CLEMENT
2020-08-28  9:03   ` Thomas Petazzoni
2020-07-24 15:43 ` [Buildroot] [PATCH v3 5/8] support/scripts: Add a per configuration CVE checker Gregory CLEMENT
2020-07-29 18:03   ` Matthew Weber
2020-08-28  9:45   ` Thomas Petazzoni
2020-07-24 15:43 ` [Buildroot] [PATCH v3 6/8] support/script/pkg-stats: Manage the CVEs that need to be check Gregory CLEMENT
2020-07-24 15:43 ` [Buildroot] [PATCH v3 7/8] support/script/cve-checker: " Gregory CLEMENT
2020-07-24 15:43 ` [Buildroot] [PATCH v3 8/8] package/pkg-utils/cve.py: Manage case when package version doesn't exist Gregory CLEMENT
2020-07-28  7:52 ` [Buildroot] [PATCH v3 0/8] Improving CVE reporting Thomas Petazzoni
2020-07-28 22:07   ` Titouan Christophe

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.