Hi all,

Do we really need a new "not_affected" state? I guess the ignore state is exactly designed for those purposes.

Regards,
Andrej

On 25.10.2023 07:13, Matsunaga-Shinji wrote:
CVEs that are currently considered "Patched" are classified into the following 
3 statuses:
1. "Patched"      - means that a patch file that fixed the vulnerability has 
been applied
2. "Not affected" - means that the package version (PV) is not affected by the 
vulnerability
3. "Undecidable"  - means that versions cannot be compared to determine if they 
are affected by the vulnerability

Signed-off-by: Shinji Matsunaga <[email protected]>
Signed-off-by: Shunsuke Tokumoto <[email protected]>
---

Changes for v2:
    - Fix the status "Out of range" to "Not affected"

  meta/classes/cve-check.bbclass | 55 +++++++++++++++++++++++-----------
  1 file changed, 38 insertions(+), 17 deletions(-)

diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index b55f4299da..502db324df 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -185,10 +185,10 @@ python do_cve_check () {
                  patched_cves = get_patched_cves(d)
              except FileNotFoundError:
                  bb.fatal("Failure in searching patches")
-            ignored, patched, unpatched, status = check_cves(d, patched_cves)
-            if patched or unpatched or (d.getVar("CVE_CHECK_COVERAGE") == "1" 
and status):
-                cve_data = get_cve_info(d, patched + unpatched + ignored)
-                cve_write_data(d, patched, unpatched, ignored, cve_data, 
status)
+            ignored, patched, unpatched, not_affected, undecidable, status = 
check_cves(d, patched_cves)
+            if patched or unpatched or not_affected or undecidable or 
(d.getVar("CVE_CHECK_COVERAGE") == "1" and status):
+                cve_data = get_cve_info(d, patched + unpatched + ignored + 
not_affected + undecidable)
+                cve_write_data(d, patched, unpatched, ignored, not_affected, 
undecidable, cve_data, status)
          else:
              bb.note("No CVE database found, skipping CVE check")
@@ -308,13 +308,13 @@ def check_cves(d, patched_cves):
      products = d.getVar("CVE_PRODUCT").split()
      # If this has been unset then we're not scanning for CVEs here (for 
example, image recipes)
      if not products:
-        return ([], [], [], [])
+        return ([], [], [], [], [], [])
      pv = d.getVar("CVE_VERSION").split("+git")[0]
# If the recipe has been skipped/ignored we return empty lists
      if pn in d.getVar("CVE_CHECK_SKIP_RECIPE").split():
          bb.note("Recipe has been skipped by cve-check")
-        return ([], [], [], [])
+        return ([], [], [], [], [], [])
# Convert CVE_STATUS into ignored CVEs and check validity
      cve_ignore = []
@@ -328,6 +328,8 @@ def check_cves(d, patched_cves):
      conn = sqlite3.connect(db_file, uri=True)
# For each of the known product names (e.g. curl has CPEs using curl and libcurl)...
+    cves_not_affected = []
+    cves_undecidable = []
      for product in products:
          cves_in_product = False
          if ":" in product:
@@ -355,6 +357,7 @@ def check_cves(d, patched_cves):
vulnerable = False
              ignored = False
+            undecidable = False
product_cursor = conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor))
              for row in product_cursor:
@@ -376,7 +379,7 @@ def check_cves(d, patched_cves):
                          except:
                              bb.warn("%s: Failed to compare %s %s %s for %s" %
                                      (product, pv, operator_start, 
version_start, cve))
-                            vulnerable_start = False
+                            undecidable = True
                      else:
                          vulnerable_start = False
@@ -387,10 +390,15 @@ def check_cves(d, patched_cves):
                          except:
                              bb.warn("%s: Failed to compare %s %s %s for %s" %
                                      (product, pv, operator_end, version_end, 
cve))
-                            vulnerable_end = False
+                            undecidable = True
                      else:
                          vulnerable_end = False
+ if undecidable:
+                        bb.note("%s-%s is undecidable to %s" % (pn, real_pv, 
cve))
+                        cves_undecidable.append(cve)
+                        break
+
                      if operator_start and operator_end:
                          vulnerable = vulnerable_start and vulnerable_end
                      else:
@@ -406,9 +414,9 @@ def check_cves(d, patched_cves):
                      break
              product_cursor.close()
- if not vulnerable:
+            if not undecidable and not vulnerable:
                  bb.note("%s-%s is not vulnerable to %s" % (pn, real_pv, cve))
-                patched_cves.add(cve)
+                cves_not_affected.append(cve)
          cve_cursor.close()
if not cves_in_product:
@@ -420,7 +428,7 @@ def check_cves(d, patched_cves):
      if not cves_in_recipe:
          bb.note("No CVE records for products in recipe %s" % (pn))
- return (list(cves_ignored), list(patched_cves), cves_unpatched, cves_status)
+    return (list(cves_ignored), list(patched_cves), cves_unpatched, 
cves_not_affected, cves_undecidable, cves_status)
def get_cve_info(d, cves):
      """
@@ -447,7 +455,7 @@ def get_cve_info(d, cves):
      conn.close()
      return cve_data
-def cve_write_data_text(d, patched, unpatched, ignored, cve_data):
+def cve_write_data_text(d, patched, unpatched, ignored, not_affected, 
undecidable, cve_data):
      """
      Write CVE information in WORKDIR; and to CVE_CHECK_DIR, and
      CVE manifest if enabled.
@@ -471,7 +479,7 @@ def cve_write_data_text(d, patched, unpatched, ignored, 
cve_data):
          return
# Early exit, the text format does not report packages without CVEs
-    if not patched+unpatched+ignored:
+    if not patched+unpatched+ignored+not_affected+undecidable:
          return
nvd_link = "https://nvd.nist.gov/vuln/detail/";
@@ -482,6 +490,8 @@ def cve_write_data_text(d, patched, unpatched, ignored, 
cve_data):
      for cve in sorted(cve_data):
          is_patched = cve in patched
          is_ignored = cve in ignored
+        is_not_affected = cve in not_affected
+        is_undecidable = cve in undecidable
status = "Unpatched"
          if (is_patched or is_ignored) and not report_all:
@@ -490,6 +500,10 @@ def cve_write_data_text(d, patched, unpatched, ignored, 
cve_data):
              status = "Ignored"
          elif is_patched:
              status = "Patched"
+        elif is_not_affected:
+            status = "Not affected"
+        elif is_undecidable:
+            status = "Undecidable"
          else:
              # default value of status is Unpatched
              unpatched_cves.append(cve)
@@ -561,7 +575,7 @@ def cve_check_write_json_output(d, output, direct_file, 
deploy_file, manifest_fi
          with open(index_path, "a+") as f:
              f.write("%s\n" % fragment_path)
-def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
+def cve_write_data_json(d, patched, unpatched, ignored, not_affected, 
undecidable, cve_data, cve_status):
      """
      Prepare CVE data for the JSON format, then write it.
      """
@@ -606,6 +620,9 @@ def cve_write_data_json(d, patched, unpatched, ignored, 
cve_data, cve_status):
      for cve in sorted(cve_data):
          is_patched = cve in patched
          is_ignored = cve in ignored
+        is_not_affected = cve in not_affected
+        is_undecidable = cve in undecidable
+
          status = "Unpatched"
          if (is_patched or is_ignored) and not report_all:
              continue
@@ -613,6 +630,10 @@ def cve_write_data_json(d, patched, unpatched, ignored, 
cve_data, cve_status):
              status = "Ignored"
          elif is_patched:
              status = "Patched"
+        elif is_not_affected:
+            status = "Not affected"
+        elif is_undecidable:
+            status = "Undecidable"
          else:
              # default value of status is Unpatched
              unpatched_cves.append(cve)
@@ -645,12 +666,12 @@ def cve_write_data_json(d, patched, unpatched, ignored, 
cve_data, cve_status):
cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file) -def cve_write_data(d, patched, unpatched, ignored, cve_data, status):
+def cve_write_data(d, patched, unpatched, ignored, not_affected, undecidable, 
cve_data, status):
      """
      Write CVE data in each enabled format.
      """
if d.getVar("CVE_CHECK_FORMAT_TEXT") == "1":
-        cve_write_data_text(d, patched, unpatched, ignored, cve_data)
+        cve_write_data_text(d, patched, unpatched, ignored, not_affected, 
undecidable, cve_data)
      if d.getVar("CVE_CHECK_FORMAT_JSON") == "1":
-        cve_write_data_json(d, patched, unpatched, ignored, cve_data, status)
+        cve_write_data_json(d, patched, unpatched, ignored, not_affected, 
undecidable, cve_data, status)




-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#189670): 
https://lists.openembedded.org/g/openembedded-core/message/189670
Mute This Topic: https://lists.openembedded.org/mt/102172913/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to