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 |
> -----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
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
> 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 --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"))
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(-)