1212import urllib .request
1313from typing import Any , Dict , List
1414
15+ import yaml
16+
1517from esp_idf_sbom .libsbom import log
1618
1719HINT = '''\
@@ -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
90126def 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
0 commit comments