diff mbox series

[RFC,1/2] cve-check: encode affected product/vendor in CVE_STATUS

Message ID 20240809062339.15465-1-marta.rybczynska@syslinbit.com
State Accepted, archived
Commit abca80a716e92fc18d3085aba1a15f4bac72379c
Headers show
Series [RFC,1/2] cve-check: encode affected product/vendor in CVE_STATUS | expand

Commit Message

Marta Rybczynska Aug. 9, 2024, 6:23 a.m. UTC
CVE_STATUS contains assesment of a given CVE, but until now it didn't have
include the affected vendor/product. In the case of a global system include,
that CVE_STATUS was visible in all recipes.

This patch allows encoding of affected product/vendor to each CVE_STATUS
assessment, also for groups. We can then filter them later and use only
CVEs that correspond to the recipe.

This is going to be used in meta/conf/distro/include/cve-extra-exclusions.inc
and similar places.

Signed-off-by: Marta Rybczynska <marta.rybczynska@syslinbit.com>
---
 meta/classes/cve-check.bbclass | 24 ++++++++++++------------
 meta/lib/oe/cve_check.py       | 22 +++++++++++++---------
 meta/lib/oe/spdx30_tasks.py    | 11 ++++++-----
 3 files changed, 31 insertions(+), 26 deletions(-)

Comments

Peter Marko Aug. 9, 2024, 7:07 a.m. UTC | #1
> -----Original Message-----
> From: openembedded-core@lists.openembedded.org <openembedded-
> core@lists.openembedded.org> On Behalf Of Marta Rybczynska via
> lists.openembedded.org
> Sent: Friday, August 9, 2024 8:24
> To: openembedded-core@lists.openembedded.org
> Cc: Marta Rybczynska <marta.rybczynska@syslinbit.com>
> Subject: [OE-core] [RFC][PATCH 1/2] cve-check: encode affected
> product/vendor in CVE_STATUS
> 
> CVE_STATUS contains assesment of a given CVE, but until now it didn't have
> include the affected vendor/product. In the case of a global system include,
> that CVE_STATUS was visible in all recipes.
> 
> This patch allows encoding of affected product/vendor to each CVE_STATUS
> assessment, also for groups. We can then filter them later and use only
> CVEs that correspond to the recipe.
> 
> This is going to be used in meta/conf/distro/include/cve-extra-exclusions.inc
> and similar places.
> 
> Signed-off-by: Marta Rybczynska <marta.rybczynska@syslinbit.com>
> ---
>  meta/classes/cve-check.bbclass | 24 ++++++++++++------------
>  meta/lib/oe/cve_check.py       | 22 +++++++++++++---------
>  meta/lib/oe/spdx30_tasks.py    | 11 ++++++-----
>  3 files changed, 31 insertions(+), 26 deletions(-)
> 
> diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
> index c946de29a4..bc35a1c53c 100644
> --- a/meta/classes/cve-check.bbclass
> +++ b/meta/classes/cve-check.bbclass
> @@ -324,8 +324,8 @@ def check_cves(d, patched_cves):
>      # 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":
> +        decoded_status = decode_cve_status(d, cve)
> +        if 'mapping' in decoded_status and decoded_status['mapping'] ==
> "Ignored":
>              cve_ignore.append(cve)
> 
>      import sqlite3
> @@ -507,11 +507,11 @@ def cve_write_data_text(d, patched, unpatched,
> ignored, cve_data):
>          write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"),
> d.getVar("PV"))
>          write_string += "CVE: %s\n" % cve
>          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
> +        status_details = decode_cve_status(d, cve)
> +        if 'detail' in status_details:
> +            write_string += "CVE DETAIL: %s\n" % status_details['detail']
> +        if 'description' in status_details:
> +            write_string += "CVE DESCRIPTION: %s\n" %
> status_details['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"]
> @@ -637,11 +637,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
> +        status_details = decode_cve_status(d, cve)
> +        if 'detail' in status_details:
> +            cve_item["detail"] = status_details['detail']
> +        if 'description' in status_details:
> +            cve_item["description"] = status_details['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 ed5c714cb8..f71fdc672c 100644
> --- a/meta/lib/oe/cve_check.py
> +++ b/meta/lib/oe/cve_check.py
> @@ -132,8 +132,8 @@ def get_patched_cves(d):
> 
>      # Search for additional patched CVEs
>      for cve in (d.getVarFlags("CVE_STATUS") or {}):
> -        decoded_status, _, _ = decode_cve_status(d, cve)
> -        if decoded_status == "Patched":
> +        decoded_status = decode_cve_status(d, cve)
> +        if 'mapping' in decoded_status and decoded_status['mapping'] ==
> "Patched":
>              bb.debug(2, "CVE %s is additionally patched" % cve)
>              patched_cves.add(cve)
> 
> @@ -227,19 +227,23 @@ def convert_cve_version(version):
> 
>  def decode_cve_status(d, cve):
>      """
> -    Convert CVE_STATUS into status, detail and description.
> +    Convert CVE_STATUS into status, vendor, product, detail and description.
>      """
>      status = d.getVarFlag("CVE_STATUS", cve)
>      if not status:
> -        return ("", "", "")
> +        return {}
> 
> -    status_split = status.split(':', 1)
> -    detail = status_split[0]
> -    description = status_split[1].strip() if (len(status_split) > 1) else ""
> +    status_split = status.split(':', 3)

This effectively forbids usage of ":" in CVE_STATUS.
Since "grep -RI 'CVE_STATUS.*:.*:'" already yiealds 8 results for oe-core + meta-oe, this is not a good idea.
I'd propose to keep the original split and check the first word of the second part; something like:

if status_split[1].strip().startswith('cpe:'):
    extracted_cpe, status_split[1] = status_split[1].strip().split(' ', 1)
    # fourth part is important here to not keep possible trailing ":" in product
    extracted_cpe_split = extracted_cpe.split(':', 4)
    status_out['vendor'] = "*" if len(extracted_cpe_split) == 2 or len(extracted_cpe_split[2] == 0) else extracted_cpe_split[1]
    status_out['product'] = extracted_cpe_split[2] if len(extracted_cpe_split) >= 3 and len(extracted_cpe_split[2] > 0) else extracted_cpe_split[1]
else:
    status_out['vendor'] = "*"
    status_out['product'] = "*"
 
> +    status_out = {}
> +    status_out['detail'] = status_split[0]
> +    status_out['vendor'] = status_split[1].strip() if (len(status_split) > 3) else
> "*"
> +    status_out['product'] = status_split[len(status_split)-2].strip() if
> (len(status_split) > 2) else "*"
> +    status_out['description'] = status_split[len(status_split)-1].strip() if
> (len(status_split) > 1) else ""
> 
> -    status_mapping = d.getVarFlag("CVE_CHECK_STATUSMAP", detail)
> +    status_mapping = d.getVarFlag("CVE_CHECK_STATUSMAP",
> status_out['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"
> +    status_out['mapping'] = status_mapping
> 
> -    return (status_mapping, detail, description)
> +    return status_out
> diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
> index 9d5bbadc0f..fd80713234 100644
> --- a/meta/lib/oe/spdx30_tasks.py
> +++ b/meta/lib/oe/spdx30_tasks.py
> @@ -486,21 +486,22 @@ def create_spdx(d):
>      cve_by_status = {}
>      if include_vex != "none":
>          for cve in d.getVarFlags("CVE_STATUS") or {}:
> -            status, detail, description = oe.cve_check.decode_cve_status(d, cve)
> +            decoded_status = oe.cve_check.decode_cve_status(d, cve)
> 
>              # If this CVE is fixed upstream, skip it unless all CVEs are
>              # specified.
> -            if include_vex != "all" and detail in (
> +            if include_vex != "all" and 'detail' in decoded_status and \
> +                decoded_status['detail'] in (
>                  "fixed-version",
>                  "cpe-stable-backport",
>              ):
>                  bb.debug(1, "Skipping %s since it is already fixed upstream" % cve)
>                  continue
> 
> -            cve_by_status.setdefault(status, {})[cve] = (
> +            cve_by_status.setdefault(decoded_status['mapping'], {})[cve] = (
>                  build_objset.new_cve_vuln(cve),
> -                detail,
> -                description,
> +                decoded_status['detail'],
> +                decoded_status['description'],
>              )
> 
>      cpe_ids = oe.cve_check.get_cpe_ids(d.getVar("CVE_PRODUCT"),
> d.getVar("CVE_VERSION"))
> --
> 2.43.0
Marta Rybczynska Aug. 9, 2024, 10:44 a.m. UTC | #2
On Fri, Aug 9, 2024 at 9:07 AM Marko, Peter <Peter.Marko@siemens.com> wrote:

>
>
> > -----Original Message-----
> > From: openembedded-core@lists.openembedded.org <openembedded-
> > core@lists.openembedded.org> On Behalf Of Marta Rybczynska via
> > lists.openembedded.org
> > Sent: Friday, August 9, 2024 8:24
> > To: openembedded-core@lists.openembedded.org
> > Cc: Marta Rybczynska <marta.rybczynska@syslinbit.com>
> > Subject: [OE-core] [RFC][PATCH 1/2] cve-check: encode affected
> > product/vendor in CVE_STATUS
> >
> > CVE_STATUS contains assesment of a given CVE, but until now it didn't
> have
> > include the affected vendor/product. In the case of a global system
> include,
> > that CVE_STATUS was visible in all recipes.
> >
> > This patch allows encoding of affected product/vendor to each CVE_STATUS
> > assessment, also for groups. We can then filter them later and use only
> > CVEs that correspond to the recipe.
> >
> > This is going to be used in
> meta/conf/distro/include/cve-extra-exclusions.inc
> > and similar places.
> >
> > Signed-off-by: Marta Rybczynska <marta.rybczynska@syslinbit.com>
> > ---
> >  meta/classes/cve-check.bbclass | 24 ++++++++++++------------
> >  meta/lib/oe/cve_check.py       | 22 +++++++++++++---------
> >  meta/lib/oe/spdx30_tasks.py    | 11 ++++++-----
> >  3 files changed, 31 insertions(+), 26 deletions(-)
> >
> > diff --git a/meta/classes/cve-check.bbclass
> b/meta/classes/cve-check.bbclass
> > index c946de29a4..bc35a1c53c 100644
> > --- a/meta/classes/cve-check.bbclass
> > +++ b/meta/classes/cve-check.bbclass
> > @@ -324,8 +324,8 @@ def check_cves(d, patched_cves):
> >      # 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":
> > +        decoded_status = decode_cve_status(d, cve)
> > +        if 'mapping' in decoded_status and decoded_status['mapping'] ==
> > "Ignored":
> >              cve_ignore.append(cve)
> >
> >      import sqlite3
> > @@ -507,11 +507,11 @@ def cve_write_data_text(d, patched, unpatched,
> > ignored, cve_data):
> >          write_string += "PACKAGE VERSION: %s%s\n" %
> (d.getVar("EXTENDPE"),
> > d.getVar("PV"))
> >          write_string += "CVE: %s\n" % cve
> >          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
> > +        status_details = decode_cve_status(d, cve)
> > +        if 'detail' in status_details:
> > +            write_string += "CVE DETAIL: %s\n" %
> status_details['detail']
> > +        if 'description' in status_details:
> > +            write_string += "CVE DESCRIPTION: %s\n" %
> > status_details['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"]
> > @@ -637,11 +637,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
> > +        status_details = decode_cve_status(d, cve)
> > +        if 'detail' in status_details:
> > +            cve_item["detail"] = status_details['detail']
> > +        if 'description' in status_details:
> > +            cve_item["description"] = status_details['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 ed5c714cb8..f71fdc672c 100644
> > --- a/meta/lib/oe/cve_check.py
> > +++ b/meta/lib/oe/cve_check.py
> > @@ -132,8 +132,8 @@ def get_patched_cves(d):
> >
> >      # Search for additional patched CVEs
> >      for cve in (d.getVarFlags("CVE_STATUS") or {}):
> > -        decoded_status, _, _ = decode_cve_status(d, cve)
> > -        if decoded_status == "Patched":
> > +        decoded_status = decode_cve_status(d, cve)
> > +        if 'mapping' in decoded_status and decoded_status['mapping'] ==
> > "Patched":
> >              bb.debug(2, "CVE %s is additionally patched" % cve)
> >              patched_cves.add(cve)
> >
> > @@ -227,19 +227,23 @@ def convert_cve_version(version):
> >
> >  def decode_cve_status(d, cve):
> >      """
> > -    Convert CVE_STATUS into status, detail and description.
> > +    Convert CVE_STATUS into status, vendor, product, detail and
> description.
> >      """
> >      status = d.getVarFlag("CVE_STATUS", cve)
> >      if not status:
> > -        return ("", "", "")
> > +        return {}
> >
> > -    status_split = status.split(':', 1)
> > -    detail = status_split[0]
> > -    description = status_split[1].strip() if (len(status_split) > 1)
> else ""
> > +    status_split = status.split(':', 3)
>
> This effectively forbids usage of ":" in CVE_STATUS.
> Since "grep -RI 'CVE_STATUS.*:.*:'" already yiealds 8 results for oe-core
> + meta-oe, this is not a good idea.
> I'd propose to keep the original split and check the first word of the
> second part; something like:
>
> if status_split[1].strip().startswith('cpe:'):
>     extracted_cpe, status_split[1] = status_split[1].strip().split(' ', 1)
>     # fourth part is important here to not keep possible trailing ":" in
> product
>     extracted_cpe_split = extracted_cpe.split(':', 4)
>     status_out['vendor'] = "*" if len(extracted_cpe_split) == 2 or
> len(extracted_cpe_split[2] == 0) else extracted_cpe_split[1]
>     status_out['product'] = extracted_cpe_split[2] if
> len(extracted_cpe_split) >= 3 and len(extracted_cpe_split[2] > 0) else
> extracted_cpe_split[1]
> else:
>     status_out['vendor'] = "*"
>     status_out['product'] = "*"
>
>
A valid point indeed. The "cpe:" prefix is a good idea. However, I think
that splitting on ' ' isn't a very good idea either.
What about assuming that: if you give the "cpe:" part, then you always give
both vendor and product. What will be valable
ignored:cpe:*:*:some description
ignored:cpe:*:*:some description::
ignored:cpe:vendor:product:some description::

but not
ignored:cpe:product:some description::

Kind regards,
Marta
Peter Marko Aug. 9, 2024, 11:04 a.m. UTC | #3
> From: Marta Rybczynska <rybczynska@gmail.com> 
> Sent: Friday, August 9, 2024 12:45
> To: Marko, Peter (ADV D EU SK BFS1) <Peter.Marko@siemens.com>
> Cc: openembedded-core@lists.openembedded.org; Marta Rybczynska <marta.rybczynska@syslinbit.com>
> Subject: Re: [OE-core] [RFC][PATCH 1/2] cve-check: encode affected product/vendor in CVE_STATUS
>
> > On Fri, Aug 9, 2024 at 9:07 AM Marko, Peter <mailto:Peter.Marko@siemens.com> wrote:
> > > -----Original Message-----
> > > From: mailto:openembedded-core@lists.openembedded.org <openembedded-
> > > mailto:core@lists.openembedded.org> On Behalf Of Marta Rybczynska via
> > > http://lists.openembedded.org
> > > Sent: Friday, August 9, 2024 8:24
> > > To: mailto:openembedded-core@lists.openembedded.org
> > > Cc: Marta Rybczynska <mailto:marta.rybczynska@syslinbit.com>
> > > Subject: [OE-core] [RFC][PATCH 1/2] cve-check: encode affected
> > > product/vendor in CVE_STATUS
> > > <snip>
> > > -    status_split = status.split(':', 1)
> > > -    detail = status_split[0]
> > > -    description = status_split[1].strip() if (len(status_split) > 1) else ""
> > > +    status_split = status.split(':', 3)
> >
> > This effectively forbids usage of ":" in CVE_STATUS.
> > Since "grep -RI 'CVE_STATUS.*:.*:'" already yiealds 8 results for oe-core + meta-oe, this is not a good idea.
> > I'd propose to keep the original split and check the first word of the second part; something like:
> >
> > if status_split[1].strip().startswith('cpe:'):
> >    extracted_cpe, status_split[1] = status_split[1].strip().split(' ', 1)
> >    # fourth part is important here to not keep possible trailing ":" in product
> >    extracted_cpe_split = extracted_cpe.split(':', 4)
> >    status_out['vendor'] = "*" if len(extracted_cpe_split) == 2 or len(extracted_cpe_split[2] == 0) else extracted_cpe_split[1]
> >    status_out['product'] = extracted_cpe_split[2] if len(extracted_cpe_split) >= 3 and len(extracted_cpe_split[2] > 0) else extracted_cpe_split[1]
> > else:
> >    status_out['vendor'] = "*"
> >    status_out['product'] = "*"
>
> A valid point indeed. The "cpe:" prefix is a good idea. However, I think that splitting on ' ' isn't a very good idea either.
> What about assuming that: if you give the "cpe:" part, then you always give both vendor and product. What will be valable
> ignored:cpe:*:*:some description
> ignored:cpe:*:*:some description::
> ignored:cpe:vendor:product:some description::
>
> but not
> ignored:cpe:product:some description::
>
> Kind regards,
> Marta 

Sure, why not, it's your patch.
I just wanted to point out a problem.

Kind Regards,
  Peter
diff mbox series

Patch

diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index c946de29a4..bc35a1c53c 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -324,8 +324,8 @@  def check_cves(d, patched_cves):
     # 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":
+        decoded_status = decode_cve_status(d, cve)
+        if 'mapping' in decoded_status and decoded_status['mapping'] == "Ignored":
             cve_ignore.append(cve)
 
     import sqlite3
@@ -507,11 +507,11 @@  def cve_write_data_text(d, patched, unpatched, ignored, cve_data):
         write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV"))
         write_string += "CVE: %s\n" % cve
         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
+        status_details = decode_cve_status(d, cve)
+        if 'detail' in status_details:
+            write_string += "CVE DETAIL: %s\n" % status_details['detail']
+        if 'description' in status_details:
+            write_string += "CVE DESCRIPTION: %s\n" % status_details['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"]
@@ -637,11 +637,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
+        status_details = decode_cve_status(d, cve)
+        if 'detail' in status_details:
+            cve_item["detail"] = status_details['detail']
+        if 'description' in status_details:
+            cve_item["description"] = status_details['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 ed5c714cb8..f71fdc672c 100644
--- a/meta/lib/oe/cve_check.py
+++ b/meta/lib/oe/cve_check.py
@@ -132,8 +132,8 @@  def get_patched_cves(d):
 
     # Search for additional patched CVEs
     for cve in (d.getVarFlags("CVE_STATUS") or {}):
-        decoded_status, _, _ = decode_cve_status(d, cve)
-        if decoded_status == "Patched":
+        decoded_status = decode_cve_status(d, cve)
+        if 'mapping' in decoded_status and decoded_status['mapping'] == "Patched":
             bb.debug(2, "CVE %s is additionally patched" % cve)
             patched_cves.add(cve)
 
@@ -227,19 +227,23 @@  def convert_cve_version(version):
 
 def decode_cve_status(d, cve):
     """
-    Convert CVE_STATUS into status, detail and description.
+    Convert CVE_STATUS into status, vendor, product, detail and description.
     """
     status = d.getVarFlag("CVE_STATUS", cve)
     if not status:
-        return ("", "", "")
+        return {}
 
-    status_split = status.split(':', 1)
-    detail = status_split[0]
-    description = status_split[1].strip() if (len(status_split) > 1) else ""
+    status_split = status.split(':', 3)
+    status_out = {}
+    status_out['detail'] = status_split[0]
+    status_out['vendor'] = status_split[1].strip() if (len(status_split) > 3) else "*"
+    status_out['product'] = status_split[len(status_split)-2].strip() if (len(status_split) > 2) else "*"
+    status_out['description'] = status_split[len(status_split)-1].strip() if (len(status_split) > 1) else ""
 
-    status_mapping = d.getVarFlag("CVE_CHECK_STATUSMAP", detail)
+    status_mapping = d.getVarFlag("CVE_CHECK_STATUSMAP", status_out['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"
+    status_out['mapping'] = status_mapping
 
-    return (status_mapping, detail, description)
+    return status_out
diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
index 9d5bbadc0f..fd80713234 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -486,21 +486,22 @@  def create_spdx(d):
     cve_by_status = {}
     if include_vex != "none":
         for cve in d.getVarFlags("CVE_STATUS") or {}:
-            status, detail, description = oe.cve_check.decode_cve_status(d, cve)
+            decoded_status = oe.cve_check.decode_cve_status(d, cve)
 
             # If this CVE is fixed upstream, skip it unless all CVEs are
             # specified.
-            if include_vex != "all" and detail in (
+            if include_vex != "all" and 'detail' in decoded_status and \
+                decoded_status['detail'] in (
                 "fixed-version",
                 "cpe-stable-backport",
             ):
                 bb.debug(1, "Skipping %s since it is already fixed upstream" % cve)
                 continue
 
-            cve_by_status.setdefault(status, {})[cve] = (
+            cve_by_status.setdefault(decoded_status['mapping'], {})[cve] = (
                 build_objset.new_cve_vuln(cve),
-                detail,
-                description,
+                decoded_status['detail'],
+                decoded_status['description'],
             )
 
     cpe_ids = oe.cve_check.get_cpe_ids(d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION"))