Skip to content

Commit 7615665

Browse files
committed
Merge branch 'feat/false_positives' into 'master'
feat: add a global list of excluded CVEs Closes IDF-10301 See merge request espressif/esp-idf-sbom!42
2 parents 83474a5 + b763986 commit 7615665

File tree

4 files changed

+79
-3
lines changed

4 files changed

+79
-3
lines changed

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,13 @@ repos:
4444
hooks:
4545
- id: check-copyright
4646
args: ['--config', 'check_copyright_config.yaml']
47+
48+
- repo: local
49+
hooks:
50+
- id: validate-excluded-cves
51+
name: Validate Excluded CVEs
52+
entry: test/validate_excluded_cves.py
53+
language: python
54+
files: 'excluded_cves.yaml'
55+
additional_dependencies:
56+
- PyYAML

esp_idf_sbom/libsbom/nvd.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import urllib.request
1313
from typing import Any, Dict, List
1414

15+
import yaml
16+
1517
from esp_idf_sbom.libsbom import log
1618

1719
HINT = '''\
@@ -44,8 +46,11 @@ def nvd_request(params: str) -> List[Dict[str, Any]]:
4446
log.debug('NVD request headers:')
4547
log.debug('\n'.join([f'{h}: {v}' for h, v in req.header_items()]))
4648

49+
# NVD recommends waiting for six seconds between requests.
50+
# https://nvd.nist.gov/developers/start-here
51+
time.sleep(6)
4752
try:
48-
with urllib.request.urlopen(req, timeout=30) as res:
53+
with urllib.request.urlopen(req, timeout=60) as res:
4954
data = json.loads(res.read().decode())
5055

5156
except urllib.error.HTTPError as e:
@@ -68,9 +73,10 @@ def nvd_request(params: str) -> List[Dict[str, Any]]:
6873
except OSError as e:
6974
# We may encounter a read error from the underlying socket. If that happens,
7075
# allow up to 3 retries along with 503 HTTP error.
71-
unavailable_cnt += 1
72-
log.warn(f'Unable to read response from NVD server: {e}. Retrying({unavailable_cnt}).')
7376
if unavailable_cnt < 3:
77+
unavailable_cnt += 1
78+
log.warn(f'Unable to read response from NVD server: {e}. Retrying({unavailable_cnt}) in 10 second...')
79+
time.sleep(10)
7480
continue
7581
raise
7682

@@ -86,6 +92,36 @@ def nvd_request(params: str) -> List[Dict[str, Any]]:
8692
return vulns
8793

8894

95+
def get_excluded_cves(cache: Dict[str, Dict[str, Any]]={}) -> Dict[str, Any]:
96+
"""Retrieve the YAML file from the esp-idf-sbom repository, which includes a list of excluded CVEs."""
97+
98+
if 'cves' in cache:
99+
# Download the excluded CVEs once per script run, not for each check.
100+
return cache['cves']
101+
102+
cves: Dict[str, Any] = {}
103+
url = 'https://raw.githubusercontent.com/espressif/esp-idf-sbom/master/excluded_cves.yaml'
104+
req = urllib.request.Request(url)
105+
106+
for retry in range(1, 4):
107+
try:
108+
with urllib.request.urlopen(req, timeout=30) as res:
109+
cves = yaml.safe_load(res.read().decode())
110+
break
111+
112+
except urllib.error.HTTPError as e:
113+
log.warn(f'Cannot download list of excluded CVEs: {e}. Retrying({retry}) ...')
114+
115+
except yaml.YAMLError as e:
116+
log.warn(f'Cannot load list of excluded CVEs: {e}')
117+
break
118+
else:
119+
log.warn(f'Failed to download list of excluded CVEs')
120+
121+
cache['cves'] = cves
122+
return cves
123+
124+
89125
# https://nvd.nist.gov/developers/vulnerabilities
90126
def check(cpe: str, search_name: bool=False) -> List[Dict[str, Any]]:
91127
"""Checks given CPE against NVD and returns its reponse."""
@@ -96,13 +132,19 @@ def check(cpe: str, search_name: bool=False) -> List[Dict[str, Any]]:
96132
if not search_name:
97133
return cpe_vulns
98134

135+
# Obtain the list of excluded CVEs to filter them out from the unanalyzed CVEs provided by NVD.
136+
excluded_cves = get_excluded_cves()
137+
99138
# Check for vulnerabilities using the package name from CPE and do keywordSearch.
100139
pkg_name = cpe.split(':')[4]
101140
keyword_vulns = nvd_request(f'keywordSearch={pkg_name}')
102141

103142
for vuln in keyword_vulns:
104143
if vuln['cve']['vulnStatus'] in ['Received', 'Awaiting Analysis', 'Undergoing Analysis']:
105144
# CVE not analyzed in NVD, include it in the results.
145+
if vuln['cve']['id'] in excluded_cves:
146+
# This CVE was previously analyzed and determined to be a false positive, unrelated to ESP-IDF.
147+
continue
106148
cpe_vulns.append(vuln)
107149

108150
return cpe_vulns

excluded_cves.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# This file lists excluded CVEs to be used with the --name option during
2+
# esp-idf-sbom check or manifest check, helping to exclude false positives for
3+
# unanalyzed CVEs in the NVD database.
4+
5+
CVE-2021-47262: related to the Linux kernel
6+
CVE-2021-47264: related to the Linux kernel

test/validate_excluded_cves.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
3+
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
4+
# SPDX-License-Identifier: Apache-2.0
5+
import re
6+
import sys
7+
8+
import yaml
9+
10+
CVE_RE = re.compile(r'CVE-\d{4}-\d{4,7}')
11+
12+
for fn in sys.argv[1:]:
13+
with open(fn, 'r') as f:
14+
cves = yaml.safe_load(f)
15+
for cve in cves:
16+
match = CVE_RE.match(cve)
17+
if not match:
18+
sys.exit(f'{cve} does not match CVE format')

0 commit comments

Comments
 (0)