diff mbox series

[v6,1/2] RFC: cve-check: add option to add additional patched CVEs

Message ID 20230620141557.54562-2-andrej.valek@siemens.com
State New
Headers show
Series [v6,1/2] RFC: cve-check: add option to add additional patched CVEs | expand

Commit Message

Andrej Valek June 20, 2023, 2:15 p.m. UTC
- Replace CVE_CHECK_IGNORE with CVE_STATUS to be more flexible.
The CVE_STATUS should contain an information about status wich
is decoded in 3 items:
- generic status: "Ignored", "Patched" or "Unpatched"
- more detailed status enum
- description: free text describing reason for status

Examples of usage:
CVE_STATUS[CVE-1234-0001] = "not-applicable-platform: Issue only applies on Windows"
CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally"

CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
CVE_CHECK_STATUSMAP[fixed-version] = "Patched"

Signed-off-by: Andrej Valek <andrej.valek@siemens.com>
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 meta/classes/cve-check.bbclass | 86 +++++++++++++++++++++++++++++-----
 meta/lib/oe/cve_check.py       | 25 ++++++++++
 2 files changed, 98 insertions(+), 13 deletions(-)

Comments

Dear Richard and Adrian,

I appreciate efforts of Andrej and Peter, you guys have done a great job for improvement in CVE specific security area.

As I mentioned information and importance of VEX status for future use case: 
https://patchwork.yoctoproject.org/project/oe-core/patch/20230519081850.82586-1-andrej.valek@siemens.com/#10797

I can see community members are also in favour of VEX:
https://patchwork.yoctoproject.org/project/oe-core/patch/20230519062420.37015-1-andrej.valek@siemens.com/#11120

We can start looking in that direction, because to adopt initial VEX template we just required minor modifications with development of Andrej.

In current implementation we have main three categories of status: "Patched", "Ignored" and "Unpatched".
On top of which we want to add comment information which can be added in JSON format to process further.

VEX have main 4 category: Fixed, Not Affected, Affected and Under Investigation.
Richard has rightly mentioned that we don't require affected status as those CVEs would fix in near future once fix is available in source of specific package.

We can map our existing status as below with VEX status.

Existing Status 	| VEX adoption
-------------------------------------------
Patched	 	| Fixed	 	
Ignore	 	| Not Affected
Not required 	| Not Affected
Unpatched	| Under Investigation

Fixed and Under Investigation don't require any sub-status as their status is sufficient to explain their case.
To get more information on possible sub-status of not affected status, we can follow one of below reference document.
https://www.cisa.gov/sites/default/files/publications/VEX_Status_Justification_Jun22.pdf : 2.0 Status Justifications Overview
This document covers all the possible cases which are already discuss or may come in future development.

Thank you, Richard, for considering my request.
I would appreciate comment from you and community people for the adoption of VEX.

Thanks,
Sanjay Chitroda

-----Original Message-----
From: openembedded-core@lists.openembedded.org <openembedded-core@lists.openembedded.org> On Behalf Of Andrej Valek via lists.openembedded.org
Sent: Tuesday, June 20, 2023 7:46 PM
To: openembedded-core@lists.openembedded.org
Cc: Andrej Valek <andrej.valek@siemens.com>; Peter Marko <peter.marko@siemens.com>
Subject: [OE-core][PATCH v6 1/2] RFC: cve-check: add option to add additional patched CVEs

- Replace CVE_CHECK_IGNORE with CVE_STATUS to be more flexible.
The CVE_STATUS should contain an information about status wich is decoded in 3 items:
- generic status: "Ignored", "Patched" or "Unpatched"
- more detailed status enum
- description: free text describing reason for status

Examples of usage:
CVE_STATUS[CVE-1234-0001] = "not-applicable-platform: Issue only applies on Windows"
CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally"

CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
CVE_CHECK_STATUSMAP[fixed-version] = "Patched"

Signed-off-by: Andrej Valek <andrej.valek@siemens.com>
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
Siddharth June 21, 2023, 6:48 a.m. UTC | #2
Hi Sanjay,

I feel the that the proposal by Andrej is a simpler one and makes me more inclined towards using it as compared to going to VEX status. I do agree that VEX is something which can be mapped but at the end of the day its always "simpler the better" and easy to maintain. Definately, as mentioned by Richard there would be a bit of copy/paste going way forward but will be easier to maintain and understand rather than leaving confusing trails at some points down the line. 2 status having one similar adoption can also add to confusion going forward.

the proposal by andrej inline with https://lists.openembedded.org/g/openembedded-core/message/182855 and is better suited to avoid confusion.

Cheers,
Siddharth
Luca Ceresoli June 21, 2023, 7:55 a.m. UTC | #3
Hello Andrej,

On Tue, 20 Jun 2023 16:15:56 +0200
"Andrej Valek via lists.openembedded.org"
<andrej.valek=siemens.com@lists.openembedded.org> wrote:
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

As you can see your sender address has been mangled, and as a result
the patch is rejected by the the openembedded git server. This is not
your fault, but we need you to modify your git configuration to prevent
this from happening in the future. Have a look at the wiki for more
info and how to solve that:

https://www.openembedded.org/wiki/How_to_submit_a_patch_to_OpenEmbedded#Fixing_your_From_identity

I'm taking your patch for testing on the autobuilders fixing it
manually so you don't need to resend your patch this time.

Best regards,
Luca
diff mbox series

Patch

diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index bd9e7e7445..6710c1d6bb 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -70,14 +70,35 @@  CVE_CHECK_COVERAGE ??= "1"
 # Skip CVE Check for packages (PN)
 CVE_CHECK_SKIP_RECIPE ?= ""
 
-# Ingore the check for a given list of CVEs. If a CVE is found,
-# then it is considered patched. The value is a string containing
-# space separated CVE values:
+# Replace NVD DB check status for a given CVE. Each of CVE has to be mentioned
+# separately with optional detail and description for this status.
 #
-# CVE_CHECK_IGNORE = 'CVE-2014-2524 CVE-2018-1234'
+# CVE_STATUS[CVE-1234-0001] = "not-applicable-platform: Issue only applies on Windows"
+# CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally"
 #
+# CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
+# CVE_CHECK_STATUSMAP[fixed-version] = "Patched"
+#
+# CVE_CHECK_IGNORE is deprecated and CVE_STATUS has to be used instead.
+# Keep CVE_CHECK_IGNORE until other layers migrate to new variables
 CVE_CHECK_IGNORE ?= ""
 
+# Possible options for CVE statuses
+CVE_CHECK_STATUSMAP[patched] = "Patched"
+CVE_CHECK_STATUSMAP[fixed-version] = "Patched"
+CVE_CHECK_STATUSMAP[backported-patch] = "Patched"
+CVE_CHECK_STATUSMAP[cpe-stable-backport] = "Patched"
+
+CVE_CHECK_STATUSMAP[unpatched] = "Unpatched"
+CVE_CHECK_STATUSMAP[vulnerable-investigating] = "Unpatched"
+
+CVE_CHECK_STATUSMAP[ignored] = "Ignored"
+CVE_CHECK_STATUSMAP[cpe-incorrect] = "Ignored"
+CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
+CVE_CHECK_STATUSMAP[upstream-wontfix] = "Ignored"
+CVE_CHECK_STATUSMAP[not-applicable-config] = "Ignored"
+CVE_CHECK_STATUSMAP[not-affected] = "Ignored"
+
 # Layers to be excluded
 CVE_CHECK_LAYER_EXCLUDELIST ??= ""
 
@@ -88,6 +109,24 @@  CVE_CHECK_LAYER_INCLUDELIST ??= ""
 # set to "alphabetical" for version using single alphabetical character as increment release
 CVE_VERSION_SUFFIX ??= ""
 
+python () {
+    # Fallback all CVEs from CVE_CHECK_IGNORE to CVE_STATUS
+    cve_check_ignore = d.getVar("CVE_CHECK_IGNORE")
+    if cve_check_ignore:
+        bb.warn("CVE_CHECK_IGNORE is deprecated in favor of CVE_STATUS")
+        for cve in (d.getVar("CVE_CHECK_IGNORE") or "").split():
+            d.setVarFlag("CVE_STATUS", cve, "ignored")
+
+    # Process CVE_STATUS_GROUPS to set multiple statuses and optional detail or description at once
+    for cve_status_group in (d.getVar("CVE_STATUS_GROUPS") or "").split():
+        cve_group = d.getVar(cve_status_group)
+        if cve_group is not None:
+            for cve in cve_group.split():
+                d.setVarFlag("CVE_STATUS", cve, d.getVarFlag(cve_status_group, "status"))
+        else:
+            bb.warn("CVE_STATUS_GROUPS contains undefined variable %s" % cve_status_group)
+}
+
 def generate_json_report(d, out_path, link_path):
     if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")):
         import json
@@ -260,7 +299,7 @@  def check_cves(d, patched_cves):
     """
     Connect to the NVD database and find unpatched cves.
     """
-    from oe.cve_check import Version, convert_cve_version
+    from oe.cve_check import Version, convert_cve_version, decode_cve_status
 
     pn = d.getVar("PN")
     real_pv = d.getVar("PV")
@@ -282,7 +321,12 @@  def check_cves(d, patched_cves):
         bb.note("Recipe has been skipped by cve-check")
         return ([], [], [], [])
 
-    cve_ignore = d.getVar("CVE_CHECK_IGNORE").split()
+    # Convert CVE_STATUS into ignored CVEs and check validity
+    cve_ignore = []
+    for cve in (d.getVarFlags("CVE_STATUS") or {}):
+        decoded_status, _, _ = decode_cve_status(d, cve)
+        if decoded_status == "Ignored":
+            cve_ignore.append(cve)
 
     import sqlite3
     db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro")
@@ -413,6 +457,8 @@  def cve_write_data_text(d, patched, unpatched, ignored, cve_data):
     CVE manifest if enabled.
     """
 
+    from oe.cve_check import decode_cve_status
+
     cve_file = d.getVar("CVE_CHECK_LOG")
     fdir_name  = d.getVar("FILE_DIRNAME")
     layer = fdir_name.split("/")[-3]
@@ -441,20 +487,27 @@  def cve_write_data_text(d, patched, unpatched, ignored, cve_data):
         is_patched = cve in patched
         is_ignored = cve in ignored
 
+        status = "Unpatched"
         if (is_patched or is_ignored) and not report_all:
             continue
+        if is_ignored:
+            status = "Ignored"
+        elif is_patched:
+            status = "Patched"
+        else:
+            # default value of status is Unpatched
+            unpatched_cves.append(cve)
 
         write_string += "LAYER: %s\n" % layer
         write_string += "PACKAGE NAME: %s\n" % d.getVar("PN")
         write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV"))
         write_string += "CVE: %s\n" % cve
-        if is_ignored:
-            write_string += "CVE STATUS: Ignored\n"
-        elif is_patched:
-            write_string += "CVE STATUS: Patched\n"
-        else:
-            unpatched_cves.append(cve)
-            write_string += "CVE STATUS: Unpatched\n"
+        write_string += "CVE STATUS: %s\n" % status
+        _, detail, description = decode_cve_status(d, cve)
+        if detail:
+            write_string += "CVE DETAIL: %s\n" % detail
+        if description:
+            write_string += "CVE DESCRIPTION: %s\n" % description
         write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"]
         write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["scorev2"]
         write_string += "CVSS v3 BASE SCORE: %s\n" % cve_data[cve]["scorev3"]
@@ -516,6 +569,8 @@  def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
     Prepare CVE data for the JSON format, then write it.
     """
 
+    from oe.cve_check import decode_cve_status
+
     output = {"version":"1", "package": []}
     nvd_link = "https://nvd.nist.gov/vuln/detail/"
 
@@ -576,6 +631,11 @@  def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
             "status" : status,
             "link": issue_link
         }
+        _, detail, description = decode_cve_status(d, cve)
+        if detail:
+            cve_item["detail"] = detail
+        if description:
+            cve_item["description"] = description
         cve_list.append(cve_item)
 
     package_data["issue"] = cve_list
diff --git a/meta/lib/oe/cve_check.py b/meta/lib/oe/cve_check.py
index dbaa0b373a..5bf3caac47 100644
--- a/meta/lib/oe/cve_check.py
+++ b/meta/lib/oe/cve_check.py
@@ -130,6 +130,13 @@  def get_patched_cves(d):
         if not fname_match and not text_match:
             bb.debug(2, "Patch %s doesn't solve CVEs" % patch_file)
 
+    # Search for additional patched CVEs
+    for cve in (d.getVarFlags("CVE_STATUS") or {}):
+        decoded_status, _, _ = decode_cve_status(d, cve)
+        if decoded_status == "Patched":
+            bb.debug(2, "CVE %s is additionally patched" % cve)
+            patched_cves.add(cve)
+
     return patched_cves
 
 
@@ -218,3 +225,21 @@  def convert_cve_version(version):
 
     return version + update
 
+def decode_cve_status(d, cve):
+    """
+    Convert CVE_STATUS into status, detail and description.
+    """
+    status = d.getVarFlag("CVE_STATUS", cve)
+    if status is None:
+        return ("", "", "")
+
+    status_split = status.split(':', 1)
+    detail = status_split[0]
+    description = status_split[1].strip() if (len(status_split) > 1) else ""
+
+    status_mapping = d.getVarFlag("CVE_CHECK_STATUSMAP", detail)
+    if status_mapping is None:
+        bb.warn('Invalid detail %s for CVE_STATUS[%s] = "%s", fallback to Unpatched' % (detail, cve, status))
+        status_mapping = "Unpatched"
+
+    return (status_mapping, detail, description)