2021-09-21 23:04:37 +02:00
|
|
|
#!/usr/bin/env python3
|
2020-07-24 17:43:49 +02:00
|
|
|
|
|
|
|
# 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 distutils.version
|
support/scripts: use FKIE git tree
Currently, we grab the per-year CVE feeds, in two passes: first, we grab
the meta files, and check whether something has changed since last we
downloaded it; second, we download the feed proper, unless the meta file
has not changed, in which case we use the locally cached feed.
However, it has appeared that the FKIE releases no longer provide the
meta files, which means that (once again), our daily reports are broken.
The obvious fix would be to drop the use of the meta file, and always
and unconditionally download the feeds. That's relatively trivial to do,
but the feeds are relatively big (even as xz-xompressed).
However, the CVE database from FKIE is available as a git tree. Git is
pretty good at only sending delta when updating a local copy. In
addition, the git tree, contains each CVE as an individual file, so it
is relatively easier to scan and parse.
Switch to using a local git clone.
Slightly surprisingly (but not so much either), parsing the CVE files is
much faster when using the git working copy, than it is when parsing the
per-year feeds: indeed, the per-year feeds are xz-compressed, and even
if python is slow-ish to scan a directory and opening files therein, it
is still much faster than to decompress xz files. The timing delta [0]
is ~100s before and ~10s now, about a ten time improvement, over the
whole package set.
The drawback, however, is that the git tree is much bigger on-disk, from
~55MiB for the per-year compressed feeds, to 2.1GiB for the git tree
(~366MiB) and a working copy (~1.8GiB)... Given very few people are
going to use that, that's considered acceptable...
Eventually, with a bit of hacking [1], the two pkg-stats, before and
after this change, yield the same data (except for the date and commit
hash).
[0] hacking support/scripts/pkg-stats to display the time before/after
the CVE scan, and hacking support/scripts/cve.py to do no download so
that only the CVE scan happens (and also because the meta files are no
longer available).
[1] sorting the CVE lists in json, sorting the json keys, and using the
commit from the FKIE git tree that was used for the current per-year
feeds.
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Cc: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Signed-off-by: Arnout Vandecappelle <arnout@mind.be>
(cherry picked from commit fee7efafd05872282e24a6923e40c1505e041196)
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2024-03-18 23:04:20 +01:00
|
|
|
import json
|
|
|
|
import subprocess
|
2020-07-24 17:43:49 +02:00
|
|
|
import sys
|
2020-07-24 17:43:50 +02:00
|
|
|
import operator
|
2020-07-24 17:43:49 +02:00
|
|
|
|
|
|
|
sys.path.append('utils/')
|
|
|
|
|
2024-02-07 16:35:19 +01:00
|
|
|
NVD_START_YEAR = 1999
|
support/scripts: use FKIE git tree
Currently, we grab the per-year CVE feeds, in two passes: first, we grab
the meta files, and check whether something has changed since last we
downloaded it; second, we download the feed proper, unless the meta file
has not changed, in which case we use the locally cached feed.
However, it has appeared that the FKIE releases no longer provide the
meta files, which means that (once again), our daily reports are broken.
The obvious fix would be to drop the use of the meta file, and always
and unconditionally download the feeds. That's relatively trivial to do,
but the feeds are relatively big (even as xz-xompressed).
However, the CVE database from FKIE is available as a git tree. Git is
pretty good at only sending delta when updating a local copy. In
addition, the git tree, contains each CVE as an individual file, so it
is relatively easier to scan and parse.
Switch to using a local git clone.
Slightly surprisingly (but not so much either), parsing the CVE files is
much faster when using the git working copy, than it is when parsing the
per-year feeds: indeed, the per-year feeds are xz-compressed, and even
if python is slow-ish to scan a directory and opening files therein, it
is still much faster than to decompress xz files. The timing delta [0]
is ~100s before and ~10s now, about a ten time improvement, over the
whole package set.
The drawback, however, is that the git tree is much bigger on-disk, from
~55MiB for the per-year compressed feeds, to 2.1GiB for the git tree
(~366MiB) and a working copy (~1.8GiB)... Given very few people are
going to use that, that's considered acceptable...
Eventually, with a bit of hacking [1], the two pkg-stats, before and
after this change, yield the same data (except for the date and commit
hash).
[0] hacking support/scripts/pkg-stats to display the time before/after
the CVE scan, and hacking support/scripts/cve.py to do no download so
that only the CVE scan happens (and also because the meta files are no
longer available).
[1] sorting the CVE lists in json, sorting the json keys, and using the
commit from the FKIE git tree that was used for the current per-year
feeds.
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Cc: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Signed-off-by: Arnout Vandecappelle <arnout@mind.be>
(cherry picked from commit fee7efafd05872282e24a6923e40c1505e041196)
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2024-03-18 23:04:20 +01:00
|
|
|
NVD_BASE_URL = "https://github.com/fkie-cad/nvd-json-data-feeds/"
|
2020-07-24 17:43:49 +02:00
|
|
|
|
2020-07-24 17:43:50 +02:00
|
|
|
ops = {
|
|
|
|
'>=': operator.ge,
|
|
|
|
'>': operator.gt,
|
|
|
|
'<=': operator.le,
|
|
|
|
'<': operator.lt,
|
|
|
|
'=': operator.eq
|
|
|
|
}
|
|
|
|
|
|
|
|
|
support/scripts/{pkg-stats, cve.py}: support CPE ID based matching
This commit modifies cve.py, as well as its users cve-checker and
pkg-stats to support CPE ID based matching, for packages that have CPE
ID information.
One of the non-trivial thing is that we can't simply iterate over all
CVEs, and then iterate over all our packages to see which packages
have CPE ID information that match the CPEs affected by the
CVE. Indeed, this is an O(n^2) operation.
So instead, we do a pre-filtering of packages potentially affected. In
check_package_cves(), we build a cpe_product_pkgs dict that associates
a CPE product name to the packages that have this CPE product
name. The CPE product name is either derived from the CPE information
provided by the package if available, and otherwise we use the package
name, which is what was used prior to this patch.
And then, when we look at CVEs, we only consider the packages that
have a CPE product name matching the CPE products affected by the
CVEs. This is done in check_package_cve_affects().
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-12-04 16:45:58 +01:00
|
|
|
# Check if two CPE IDs match each other
|
|
|
|
def cpe_matches(cpe1, cpe2):
|
|
|
|
cpe1_elems = cpe1.split(":")
|
|
|
|
cpe2_elems = cpe2.split(":")
|
|
|
|
|
|
|
|
remains = filter(lambda x: x[0] not in ["*", "-"] and x[1] not in ["*", "-"] and x[0] != x[1],
|
|
|
|
zip(cpe1_elems, cpe2_elems))
|
|
|
|
return len(list(remains)) == 0
|
|
|
|
|
|
|
|
|
|
|
|
def cpe_product(cpe):
|
|
|
|
return cpe.split(':')[4]
|
|
|
|
|
|
|
|
|
|
|
|
def cpe_version(cpe):
|
|
|
|
return cpe.split(':')[5]
|
|
|
|
|
|
|
|
|
2020-07-24 17:43:49 +02:00
|
|
|
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
|
support/scripts: use FKIE git tree
Currently, we grab the per-year CVE feeds, in two passes: first, we grab
the meta files, and check whether something has changed since last we
downloaded it; second, we download the feed proper, unless the meta file
has not changed, in which case we use the locally cached feed.
However, it has appeared that the FKIE releases no longer provide the
meta files, which means that (once again), our daily reports are broken.
The obvious fix would be to drop the use of the meta file, and always
and unconditionally download the feeds. That's relatively trivial to do,
but the feeds are relatively big (even as xz-xompressed).
However, the CVE database from FKIE is available as a git tree. Git is
pretty good at only sending delta when updating a local copy. In
addition, the git tree, contains each CVE as an individual file, so it
is relatively easier to scan and parse.
Switch to using a local git clone.
Slightly surprisingly (but not so much either), parsing the CVE files is
much faster when using the git working copy, than it is when parsing the
per-year feeds: indeed, the per-year feeds are xz-compressed, and even
if python is slow-ish to scan a directory and opening files therein, it
is still much faster than to decompress xz files. The timing delta [0]
is ~100s before and ~10s now, about a ten time improvement, over the
whole package set.
The drawback, however, is that the git tree is much bigger on-disk, from
~55MiB for the per-year compressed feeds, to 2.1GiB for the git tree
(~366MiB) and a working copy (~1.8GiB)... Given very few people are
going to use that, that's considered acceptable...
Eventually, with a bit of hacking [1], the two pkg-stats, before and
after this change, yield the same data (except for the date and commit
hash).
[0] hacking support/scripts/pkg-stats to display the time before/after
the CVE scan, and hacking support/scripts/cve.py to do no download so
that only the CVE scan happens (and also because the meta files are no
longer available).
[1] sorting the CVE lists in json, sorting the json keys, and using the
commit from the FKIE git tree that was used for the current per-year
feeds.
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Cc: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Signed-off-by: Arnout Vandecappelle <arnout@mind.be>
(cherry picked from commit fee7efafd05872282e24a6923e40c1505e041196)
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2024-03-18 23:04:20 +01:00
|
|
|
def download_nvd(nvd_git_dir):
|
|
|
|
print(f"Updating from {NVD_BASE_URL}")
|
|
|
|
if os.path.exists(nvd_git_dir):
|
|
|
|
subprocess.check_call(
|
|
|
|
["git", "pull"],
|
|
|
|
cwd=nvd_git_dir,
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
stderr=subprocess.DEVNULL,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
# Create the directory and its parents; git
|
|
|
|
# happily clones into an empty directory.
|
|
|
|
os.makedirs(nvd_git_dir)
|
|
|
|
subprocess.check_call(
|
|
|
|
["git", "clone", NVD_BASE_URL, nvd_git_dir],
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
stderr=subprocess.DEVNULL,
|
|
|
|
)
|
2020-07-24 17:43:49 +02:00
|
|
|
|
2024-02-25 23:05:37 +01:00
|
|
|
@staticmethod
|
|
|
|
def sort_id(cve_ids):
|
|
|
|
def cve_key(cve_id):
|
|
|
|
year, id_ = cve_id.split('-')[1:]
|
|
|
|
return (int(year), int(id_))
|
|
|
|
return sorted(cve_ids, key=cve_key)
|
|
|
|
|
2020-07-24 17:43:49 +02:00
|
|
|
@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
|
|
|
|
"""
|
support/scripts: use FKIE git tree
Currently, we grab the per-year CVE feeds, in two passes: first, we grab
the meta files, and check whether something has changed since last we
downloaded it; second, we download the feed proper, unless the meta file
has not changed, in which case we use the locally cached feed.
However, it has appeared that the FKIE releases no longer provide the
meta files, which means that (once again), our daily reports are broken.
The obvious fix would be to drop the use of the meta file, and always
and unconditionally download the feeds. That's relatively trivial to do,
but the feeds are relatively big (even as xz-xompressed).
However, the CVE database from FKIE is available as a git tree. Git is
pretty good at only sending delta when updating a local copy. In
addition, the git tree, contains each CVE as an individual file, so it
is relatively easier to scan and parse.
Switch to using a local git clone.
Slightly surprisingly (but not so much either), parsing the CVE files is
much faster when using the git working copy, than it is when parsing the
per-year feeds: indeed, the per-year feeds are xz-compressed, and even
if python is slow-ish to scan a directory and opening files therein, it
is still much faster than to decompress xz files. The timing delta [0]
is ~100s before and ~10s now, about a ten time improvement, over the
whole package set.
The drawback, however, is that the git tree is much bigger on-disk, from
~55MiB for the per-year compressed feeds, to 2.1GiB for the git tree
(~366MiB) and a working copy (~1.8GiB)... Given very few people are
going to use that, that's considered acceptable...
Eventually, with a bit of hacking [1], the two pkg-stats, before and
after this change, yield the same data (except for the date and commit
hash).
[0] hacking support/scripts/pkg-stats to display the time before/after
the CVE scan, and hacking support/scripts/cve.py to do no download so
that only the CVE scan happens (and also because the meta files are no
longer available).
[1] sorting the CVE lists in json, sorting the json keys, and using the
commit from the FKIE git tree that was used for the current per-year
feeds.
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Cc: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Signed-off-by: Arnout Vandecappelle <arnout@mind.be>
(cherry picked from commit fee7efafd05872282e24a6923e40c1505e041196)
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2024-03-18 23:04:20 +01:00
|
|
|
nvd_git_dir = os.path.join(nvd_dir, "git")
|
|
|
|
CVE.download_nvd(nvd_git_dir)
|
2020-07-24 17:43:49 +02:00
|
|
|
for year in range(NVD_START_YEAR, datetime.datetime.now().year + 1):
|
support/scripts: use FKIE git tree
Currently, we grab the per-year CVE feeds, in two passes: first, we grab
the meta files, and check whether something has changed since last we
downloaded it; second, we download the feed proper, unless the meta file
has not changed, in which case we use the locally cached feed.
However, it has appeared that the FKIE releases no longer provide the
meta files, which means that (once again), our daily reports are broken.
The obvious fix would be to drop the use of the meta file, and always
and unconditionally download the feeds. That's relatively trivial to do,
but the feeds are relatively big (even as xz-xompressed).
However, the CVE database from FKIE is available as a git tree. Git is
pretty good at only sending delta when updating a local copy. In
addition, the git tree, contains each CVE as an individual file, so it
is relatively easier to scan and parse.
Switch to using a local git clone.
Slightly surprisingly (but not so much either), parsing the CVE files is
much faster when using the git working copy, than it is when parsing the
per-year feeds: indeed, the per-year feeds are xz-compressed, and even
if python is slow-ish to scan a directory and opening files therein, it
is still much faster than to decompress xz files. The timing delta [0]
is ~100s before and ~10s now, about a ten time improvement, over the
whole package set.
The drawback, however, is that the git tree is much bigger on-disk, from
~55MiB for the per-year compressed feeds, to 2.1GiB for the git tree
(~366MiB) and a working copy (~1.8GiB)... Given very few people are
going to use that, that's considered acceptable...
Eventually, with a bit of hacking [1], the two pkg-stats, before and
after this change, yield the same data (except for the date and commit
hash).
[0] hacking support/scripts/pkg-stats to display the time before/after
the CVE scan, and hacking support/scripts/cve.py to do no download so
that only the CVE scan happens (and also because the meta files are no
longer available).
[1] sorting the CVE lists in json, sorting the json keys, and using the
commit from the FKIE git tree that was used for the current per-year
feeds.
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Cc: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Signed-off-by: Arnout Vandecappelle <arnout@mind.be>
(cherry picked from commit fee7efafd05872282e24a6923e40c1505e041196)
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2024-03-18 23:04:20 +01:00
|
|
|
for dirpath, _, filenames in os.walk(os.path.join(nvd_git_dir, f"CVE-{year}")):
|
|
|
|
for filename in filenames:
|
|
|
|
if filename[-5:] != ".json":
|
|
|
|
continue
|
|
|
|
with open(os.path.join(dirpath, filename), "rb") as f:
|
|
|
|
yield cls(json.load(f))
|
2020-07-24 17:43:49 +02:00
|
|
|
|
|
|
|
def each_product(self):
|
|
|
|
"""Iterate over each product section of this cve"""
|
2020-07-24 17:43:50 +02:00
|
|
|
for vendor in self.nvd_cve['cve']['affects']['vendor']['vendor_data']:
|
2020-07-24 17:43:49 +02:00
|
|
|
for product in vendor['product']['product_data']:
|
|
|
|
yield product
|
|
|
|
|
2020-07-24 17:43:50 +02:00
|
|
|
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
|
|
|
|
|
2024-02-25 23:05:36 +01:00
|
|
|
for cpe in node.get('cpeMatch', ()):
|
2020-07-24 17:43:50 +02:00
|
|
|
if not cpe['vulnerable']:
|
|
|
|
return
|
2024-02-25 23:05:36 +01:00
|
|
|
product = cpe_product(cpe['criteria'])
|
|
|
|
version = cpe_version(cpe['criteria'])
|
support/scripts/{pkg-stats, cve.py}: support CPE ID based matching
This commit modifies cve.py, as well as its users cve-checker and
pkg-stats to support CPE ID based matching, for packages that have CPE
ID information.
One of the non-trivial thing is that we can't simply iterate over all
CVEs, and then iterate over all our packages to see which packages
have CPE ID information that match the CPEs affected by the
CVE. Indeed, this is an O(n^2) operation.
So instead, we do a pre-filtering of packages potentially affected. In
check_package_cves(), we build a cpe_product_pkgs dict that associates
a CPE product name to the packages that have this CPE product
name. The CPE product name is either derived from the CPE information
provided by the package if available, and otherwise we use the package
name, which is what was used prior to this patch.
And then, when we look at CVEs, we only consider the packages that
have a CPE product name matching the CPE products affected by the
CVEs. This is done in check_package_cve_affects().
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-12-04 16:45:58 +01:00
|
|
|
# ignore when product is '-', which means N/A
|
|
|
|
if product == '-':
|
|
|
|
return
|
2020-07-24 17:43:50 +02:00
|
|
|
op_start = ''
|
|
|
|
op_end = ''
|
|
|
|
v_start = ''
|
|
|
|
v_end = ''
|
|
|
|
|
|
|
|
if version != '*' and version != '-':
|
|
|
|
# Version is defined, this is a '=' match
|
|
|
|
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']
|
|
|
|
|
|
|
|
yield {
|
2024-02-25 23:05:36 +01:00
|
|
|
'id': cpe['criteria'],
|
2020-07-24 17:43:50 +02:00
|
|
|
'v_start': v_start,
|
|
|
|
'op_start': op_start,
|
|
|
|
'v_end': v_end,
|
|
|
|
'op_end': op_end
|
|
|
|
}
|
|
|
|
|
|
|
|
def each_cpe(self):
|
2024-02-25 23:05:36 +01:00
|
|
|
for nodes in self.nvd_cve.get('configurations', []):
|
|
|
|
for node in nodes['nodes']:
|
|
|
|
for cpe in self.parse_node(node):
|
|
|
|
yield cpe
|
2020-07-24 17:43:50 +02:00
|
|
|
|
2020-07-24 17:43:49 +02:00
|
|
|
@property
|
|
|
|
def identifier(self):
|
|
|
|
"""The CVE unique identifier"""
|
2024-02-25 23:05:36 +01:00
|
|
|
return self.nvd_cve['id']
|
2020-07-24 17:43:49 +02:00
|
|
|
|
|
|
|
@property
|
support/scripts/{pkg-stats, cve.py}: support CPE ID based matching
This commit modifies cve.py, as well as its users cve-checker and
pkg-stats to support CPE ID based matching, for packages that have CPE
ID information.
One of the non-trivial thing is that we can't simply iterate over all
CVEs, and then iterate over all our packages to see which packages
have CPE ID information that match the CPEs affected by the
CVE. Indeed, this is an O(n^2) operation.
So instead, we do a pre-filtering of packages potentially affected. In
check_package_cves(), we build a cpe_product_pkgs dict that associates
a CPE product name to the packages that have this CPE product
name. The CPE product name is either derived from the CPE information
provided by the package if available, and otherwise we use the package
name, which is what was used prior to this patch.
And then, when we look at CVEs, we only consider the packages that
have a CPE product name matching the CPE products affected by the
CVEs. This is done in check_package_cve_affects().
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-12-04 16:45:58 +01:00
|
|
|
def affected_products(self):
|
|
|
|
"""The set of CPE products referred by this CVE definition"""
|
|
|
|
return set(cpe_product(p['id']) for p in self.each_cpe())
|
2020-07-24 17:43:49 +02:00
|
|
|
|
support/scripts/{pkg-stats, cve.py}: support CPE ID based matching
This commit modifies cve.py, as well as its users cve-checker and
pkg-stats to support CPE ID based matching, for packages that have CPE
ID information.
One of the non-trivial thing is that we can't simply iterate over all
CVEs, and then iterate over all our packages to see which packages
have CPE ID information that match the CPEs affected by the
CVE. Indeed, this is an O(n^2) operation.
So instead, we do a pre-filtering of packages potentially affected. In
check_package_cves(), we build a cpe_product_pkgs dict that associates
a CPE product name to the packages that have this CPE product
name. The CPE product name is either derived from the CPE information
provided by the package if available, and otherwise we use the package
name, which is what was used prior to this patch.
And then, when we look at CVEs, we only consider the packages that
have a CPE product name matching the CPE products affected by the
CVEs. This is done in check_package_cve_affects().
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-12-04 16:45:58 +01:00
|
|
|
def affects(self, name, version, cve_ignore_list, cpeid=None):
|
2020-07-24 17:43:49 +02:00
|
|
|
"""
|
|
|
|
True if the Buildroot Package object passed as argument is affected
|
|
|
|
by this CVE.
|
|
|
|
"""
|
2020-07-24 17:43:52 +02:00
|
|
|
if self.identifier in cve_ignore_list:
|
2020-07-24 17:43:49 +02:00
|
|
|
return self.CVE_DOESNT_AFFECT
|
|
|
|
|
2020-07-24 17:43:52 +02:00
|
|
|
pkg_version = distutils.version.LooseVersion(version)
|
2020-07-24 17:43:50 +02:00
|
|
|
if not hasattr(pkg_version, "version"):
|
2020-07-24 17:43:52 +02:00
|
|
|
print("Cannot parse package '%s' version '%s'" % (name, version))
|
2020-07-24 17:43:50 +02:00
|
|
|
pkg_version = None
|
|
|
|
|
support/scripts/{pkg-stats, cve.py}: support CPE ID based matching
This commit modifies cve.py, as well as its users cve-checker and
pkg-stats to support CPE ID based matching, for packages that have CPE
ID information.
One of the non-trivial thing is that we can't simply iterate over all
CVEs, and then iterate over all our packages to see which packages
have CPE ID information that match the CPEs affected by the
CVE. Indeed, this is an O(n^2) operation.
So instead, we do a pre-filtering of packages potentially affected. In
check_package_cves(), we build a cpe_product_pkgs dict that associates
a CPE product name to the packages that have this CPE product
name. The CPE product name is either derived from the CPE information
provided by the package if available, and otherwise we use the package
name, which is what was used prior to this patch.
And then, when we look at CVEs, we only consider the packages that
have a CPE product name matching the CPE products affected by the
CVEs. This is done in check_package_cve_affects().
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-12-04 16:45:58 +01:00
|
|
|
# if we don't have a cpeid, build one based on name and version
|
|
|
|
if not cpeid:
|
|
|
|
cpeid = "cpe:2.3:*:*:%s:%s:*:*:*:*:*:*:*" % (name, version)
|
2021-04-12 21:41:25 +02:00
|
|
|
# if we have a cpeid, use its version instead of the package
|
|
|
|
# version, as they might be different due to
|
|
|
|
# <pkg>_CPE_ID_VERSION
|
|
|
|
else:
|
|
|
|
pkg_version = distutils.version.LooseVersion(cpe_version(cpeid))
|
support/scripts/{pkg-stats, cve.py}: support CPE ID based matching
This commit modifies cve.py, as well as its users cve-checker and
pkg-stats to support CPE ID based matching, for packages that have CPE
ID information.
One of the non-trivial thing is that we can't simply iterate over all
CVEs, and then iterate over all our packages to see which packages
have CPE ID information that match the CPEs affected by the
CVE. Indeed, this is an O(n^2) operation.
So instead, we do a pre-filtering of packages potentially affected. In
check_package_cves(), we build a cpe_product_pkgs dict that associates
a CPE product name to the packages that have this CPE product
name. The CPE product name is either derived from the CPE information
provided by the package if available, and otherwise we use the package
name, which is what was used prior to this patch.
And then, when we look at CVEs, we only consider the packages that
have a CPE product name matching the CPE products affected by the
CVEs. This is done in check_package_cve_affects().
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-12-04 16:45:58 +01:00
|
|
|
|
2020-07-24 17:43:50 +02:00
|
|
|
for cpe in self.each_cpe():
|
support/scripts/{pkg-stats, cve.py}: support CPE ID based matching
This commit modifies cve.py, as well as its users cve-checker and
pkg-stats to support CPE ID based matching, for packages that have CPE
ID information.
One of the non-trivial thing is that we can't simply iterate over all
CVEs, and then iterate over all our packages to see which packages
have CPE ID information that match the CPEs affected by the
CVE. Indeed, this is an O(n^2) operation.
So instead, we do a pre-filtering of packages potentially affected. In
check_package_cves(), we build a cpe_product_pkgs dict that associates
a CPE product name to the packages that have this CPE product
name. The CPE product name is either derived from the CPE information
provided by the package if available, and otherwise we use the package
name, which is what was used prior to this patch.
And then, when we look at CVEs, we only consider the packages that
have a CPE product name matching the CPE products affected by the
CVEs. This is done in check_package_cve_affects().
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-12-04 16:45:58 +01:00
|
|
|
if not cpe_matches(cpe['id'], cpeid):
|
2020-07-24 17:43:50 +02:00
|
|
|
continue
|
|
|
|
if not cpe['v_start'] and not cpe['v_end']:
|
support/scripts/cve.py: properly match CPEs with version '*'
Currently, when the version encoded in a CPE is '-', we assume all
versions are affected, but when it's '*' with no further range
information, we assume no version is affected.
This doesn't make sense, so instead, we handle '*' and '-' in the same
way. If there's no version information available in the CVE CPE ID, we
assume all versions are affected.
This increases quite a bit the number of CVEs and package affected:
- "total-cves": 302,
- "pkg-cves": 100,
+ "total-cves": 597,
+ "pkg-cves": 135,
For example, CVE-2007-4476 has a CPE ID of:
cpe:2.3:a:gnu:tar:*:*:*:*:*:*:*:*
So it should be taken into account. In this specific case, it is
combined with an AND with CPE ID
cpe:2.3:o:suse:suse_linux:10:*:enterprise_server:*:*:*:*:* but since
we don't support this kind of matching, we'd better be on the safe
side, and report this CVE as affecting tar, do an analysis of the CVE
impact, and document it in TAR_IGNORE_CVES.
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Reviewed-by: Matt Weber <matthew.weber@rockwellcollins.com>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
2020-11-04 15:51:35 +01:00
|
|
|
return self.CVE_AFFECTS
|
2020-07-24 17:43:50 +02:00
|
|
|
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
|
2020-07-24 17:43:49 +02:00
|
|
|
|
|
|
|
return self.CVE_DOESNT_AFFECT
|