Message ID | 20231025051344.24088-1-shin.matsunaga@fujitsu.com |
---|---|
State | New |
Headers | show |
Series | [v2] cve-check: Classify patched CVEs into 3 statuses | expand |
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 <shin.matsunaga@fujitsu.com> > Signed-off-by: Shunsuke Tokumoto <s-tokumoto@fujitsu.com> > --- > > 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 (#189667): https://lists.openembedded.org/g/openembedded-core/message/189667 > Mute This Topic: https://lists.openembedded.org/mt/102172913/3619876 > Group Owner: openembedded-core+owner@lists.openembedded.org > Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [andrej.v@skyrain.eu] > -=-=-=-=-=-=-=-=-=-=-=- >
Hi Andrej, This is more complex. "Not affected" is also an issue that isn't present in the code - like when we have a version that has never had the vulnerability. Those are also currently 'Patched' in cve-check. This work is in sync with what VEX is doing, is it the use-case Matsanaga-Shinji? Regards, Marta On Wed, Oct 25, 2023 at 8:44 AM Andrej Valek <andrej.v@skyrain.eu> wrote: > > 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 <shin.matsunaga@fujitsu.com> > > Signed-off-by: Shunsuke Tokumoto <s-tokumoto@fujitsu.com> > > --- > > > > 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 (#189667): https://lists.openembedded.org/g/openembedded-core/message/189667 > > Mute This Topic: https://lists.openembedded.org/mt/102172913/3619876 > > Group Owner: openembedded-core+owner@lists.openembedded.org > > Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [andrej.v@skyrain.eu] > > -=-=-=-=-=-=-=-=-=-=-=- > > >
Hi Marta, That's fine, as I said we designed the "ignore" with status "cpe-incorrect" or "ignored" exactly for those purposes. Extending the option with "not affected" doesn't make any sense. You have to set the status to "why is not affected" = "ignored". Which completely covers the requested case. Regards, Andrej On 25.10.2023 11:33, Marta Rybczynska wrote: > Hi Andrej, > This is more complex. "Not affected" is also an issue that isn't present in the > code - like when we have a version that has never had the vulnerability. > Those are also currently 'Patched' in cve-check. > > This work is in sync with what VEX is doing, is it the use-case > Matsanaga-Shinji? > > Regards, > Marta > > On Wed, Oct 25, 2023 at 8:44 AM Andrej Valek <andrej.v@skyrain.eu> wrote: >> 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 <shin.matsunaga@fujitsu.com> >>> Signed-off-by: Shunsuke Tokumoto <s-tokumoto@fujitsu.com> >>> --- >>> >>> 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 (#189667): https://lists.openembedded.org/g/openembedded-core/message/189667 >>> Mute This Topic: https://lists.openembedded.org/mt/102172913/3619876 >>> Group Owner: openembedded-core+owner@lists.openembedded.org >>> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [andrej.v@skyrain.eu] >>> -=-=-=-=-=-=-=-=-=-=-=- >>>
Hello Andrej, This patch is splitting the Patched state, not the ignore one. This is not incorrect CPE or anything else. Currently Patched means one of two situations: either this issue has never affected the code base (example: we have version 1.0, issue was introduced in 2.0 and fixed in 2.1), or the issue has been fixed. Yes, another reason to say ignore, not affected is a manual analysis showing a thing like: the issue affects only windows. Regards, Marta On Wed, 25 Oct 2023, 12:18 Andrej Valek, <andrej.v@skyrain.eu> wrote: > Hi Marta, > > That's fine, as I said we designed the "ignore" with status > "cpe-incorrect" or "ignored" exactly for those purposes. Extending the > option with "not affected" doesn't make any sense. > > You have to set the status to "why is not affected" = "ignored". Which > completely covers the requested case. > > Regards, > Andrej > > On 25.10.2023 11:33, Marta Rybczynska wrote: > > Hi Andrej, > > This is more complex. "Not affected" is also an issue that isn't present > in the > > code - like when we have a version that has never had the vulnerability. > > Those are also currently 'Patched' in cve-check. > > > > This work is in sync with what VEX is doing, is it the use-case > > Matsanaga-Shinji? > > > > Regards, > > Marta > > > > On Wed, Oct 25, 2023 at 8:44 AM Andrej Valek <andrej.v@skyrain.eu> > wrote: > >> 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 <shin.matsunaga@fujitsu.com> > >>> Signed-off-by: Shunsuke Tokumoto <s-tokumoto@fujitsu.com> > >>> --- > >>> > >>> 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 (#189667): > https://lists.openembedded.org/g/openembedded-core/message/189667 > >>> Mute This Topic: https://lists.openembedded.org/mt/102172913/3619876 > >>> Group Owner: openembedded-core+owner@lists.openembedded.org > >>> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub > [andrej.v@skyrain.eu] > >>> -=-=-=-=-=-=-=-=-=-=-=- > >>> > >
Hello Marta, Major reason why we introduced CVE_STATUS was exactly to avoid patch like this. There were ideas to introduce 5 or 10 or 15 different statuses and we decided to keep 3 and introduce “sub-statuses”. These sub-statuses are listed in cve reports, too. Currently we have three main statuses: Patched – common status for all sub-statuses which indicate that component is not vulnerable Unpatched - common status for all sub-statuses which indicate that component is vulnerable Ignored - common status for all sub-statuses which indicate that component is vulnerable but not in yocto configuration context If we don’t like “Patched” we can rename it (e.g. to “Unaffected”) and have additional sub-statuses under this new name. Otherwise we start exploding the statuses as someone will “need” additional one soon. If we really want to introduce these new statues (I hope not), please modify this patch to handle its CVE_STATUS flags, too. Additionally, I’d drop “Undecidable” and map it to “Unpatched” (so someone needs to analyze as any other open vulnerability report) Best Regards, Peter From: Marta Rybczynska <rybczynska@gmail.com> Sent: Wednesday, October 25, 2023 14:44 To: Andrej Valek <andrej.v@skyrain.eu> Cc: Matsunaga-Shinji <shin.matsunaga@fujitsu.com>; Richard Purdie <richard.purdie@linuxfoundation.org>; OE-core <openembedded-core@lists.openembedded.org>; Shunsuke Tokumoto <s-tokumoto@fujitsu.com>; Marko, Peter (ADV D EU SK BFS1) <Peter.Marko@siemens.com> Subject: Re: [OE-core] [PATCH v2] cve-check: Classify patched CVEs into 3 statuses Hello Andrej, This patch is splitting the Patched state, not the ignore one. This is not incorrect CPE or anything else. Currently Patched means one of two situations: either this issue has never affected the code base (example: we have version 1.0, issue was introduced in 2.0 and fixed in 2.1), or the issue has been fixed. Yes, another reason to say ignore, not affected is a manual analysis showing a thing like: the issue affects only windows. Regards, Marta On Wed, 25 Oct 2023, 12:18 Andrej Valek, <andrej.v@skyrain.eu<mailto:andrej.v@skyrain.eu>> wrote: Hi Marta, That's fine, as I said we designed the "ignore" with status "cpe-incorrect" or "ignored" exactly for those purposes. Extending the option with "not affected" doesn't make any sense. You have to set the status to "why is not affected" = "ignored". Which completely covers the requested case. Regards, Andrej On 25.10.2023 11:33, Marta Rybczynska wrote: > Hi Andrej, > This is more complex. "Not affected" is also an issue that isn't present in the > code - like when we have a version that has never had the vulnerability. > Those are also currently 'Patched' in cve-check. > > This work is in sync with what VEX is doing, is it the use-case > Matsanaga-Shinji? > > Regards, > Marta > > On Wed, Oct 25, 2023 at 8:44 AM Andrej Valek <andrej.v@skyrain.eu<mailto:andrej.v@skyrain.eu>> wrote: >> 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 <shin.matsunaga@fujitsu.com<mailto:shin.matsunaga@fujitsu.com>> >>> Signed-off-by: Shunsuke Tokumoto <s-tokumoto@fujitsu.com<mailto:s-tokumoto@fujitsu.com>> >>> --- >>> >>> 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 (#189667): https://lists.openembedded.org/g/openembedded-core/message/189667 >>> Mute This Topic: https://lists.openembedded.org/mt/102172913/3619876 >>> Group Owner: openembedded-core+owner@lists.openembedded.org<mailto:openembedded-core%2Bowner@lists.openembedded.org> >>> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [andrej.v@skyrain.eu<mailto:andrej.v@skyrain.eu>] >>> -=-=-=-=-=-=-=-=-=-=-=- >>>
Hello Marko, I think that we will need to go back to the drawing board and have a look what we want to report from the CVE check. I'm not totally happy with the solution proposed here, because it is adding high-level states. However, it is a step forward to be able to map our status to VEX. In the RFQ work on SPDX3 (which includes VEX in the "security" profile) we had a look at that and realized that mapping of the information is not possible, we're missing information. The current state of "Ignore" is not great either. You have added additional context, but still some statuses do not make sense in Ignore. It is effectively used as an "override" of everything. I also have a multi-fetcher in the works, which downloads CVE data from other sources that NVD. Results are sometimes in conflict and we'll have to handle that too. And there are other sources lurking behind too. So, when the RFQ season is finished (Oct 31st), we'll get back to the drawing board to design what statuses we really want and need. I understand you'd like to be a part of this discussion. Kind regards, Marta On Wed, Oct 25, 2023 at 3:09 PM Marko, Peter <Peter.Marko@siemens.com> wrote: > > Hello Marta, > > > > Major reason why we introduced CVE_STATUS was exactly to avoid patch like this. > > There were ideas to introduce 5 or 10 or 15 different statuses and we decided to keep 3 and introduce “sub-statuses”. > > These sub-statuses are listed in cve reports, too. > > > > Currently we have three main statuses: > > Patched – common status for all sub-statuses which indicate that component is not vulnerable > > Unpatched - common status for all sub-statuses which indicate that component is vulnerable > > Ignored - common status for all sub-statuses which indicate that component is vulnerable but not in yocto configuration context > > > > If we don’t like “Patched” we can rename it (e.g. to “Unaffected”) and have additional sub-statuses under this new name. > > Otherwise we start exploding the statuses as someone will “need” additional one soon. > > > > If we really want to introduce these new statues (I hope not), please modify this patch to handle its CVE_STATUS flags, too. > > Additionally, I’d drop “Undecidable” and map it to “Unpatched” (so someone needs to analyze as any other open vulnerability report) > > > > Best Regards, > > Peter > > > > From: Marta Rybczynska <rybczynska@gmail.com> > Sent: Wednesday, October 25, 2023 14:44 > To: Andrej Valek <andrej.v@skyrain.eu> > Cc: Matsunaga-Shinji <shin.matsunaga@fujitsu.com>; Richard Purdie <richard.purdie@linuxfoundation.org>; OE-core <openembedded-core@lists.openembedded.org>; Shunsuke Tokumoto <s-tokumoto@fujitsu.com>; Marko, Peter (ADV D EU SK BFS1) <Peter.Marko@siemens.com> > Subject: Re: [OE-core] [PATCH v2] cve-check: Classify patched CVEs into 3 statuses > > > > Hello Andrej, > > This patch is splitting the Patched state, not the ignore one. This is not incorrect CPE or anything else. > > > > Currently Patched means one of two situations: either this issue has never affected the code base (example: we have version 1.0, issue was introduced in 2.0 and fixed in 2.1), or the issue has been fixed. > > > > Yes, another reason to say ignore, not affected is a manual analysis showing a thing like: the issue affects only windows. > > > > Regards, > > Marta > > > > On Wed, 25 Oct 2023, 12:18 Andrej Valek, <andrej.v@skyrain.eu> wrote: > > Hi Marta, > > That's fine, as I said we designed the "ignore" with status > "cpe-incorrect" or "ignored" exactly for those purposes. Extending the > option with "not affected" doesn't make any sense. > > You have to set the status to "why is not affected" = "ignored". Which > completely covers the requested case. > > Regards, > Andrej > > On 25.10.2023 11:33, Marta Rybczynska wrote: > > Hi Andrej, > > This is more complex. "Not affected" is also an issue that isn't present in the > > code - like when we have a version that has never had the vulnerability. > > Those are also currently 'Patched' in cve-check. > > > > This work is in sync with what VEX is doing, is it the use-case > > Matsanaga-Shinji? > > > > Regards, > > Marta > > > > On Wed, Oct 25, 2023 at 8:44 AM Andrej Valek <andrej.v@skyrain.eu> wrote: > >> 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 <shin.matsunaga@fujitsu.com> > >>> Signed-off-by: Shunsuke Tokumoto <s-tokumoto@fujitsu.com> > >>> --- > >>> > >>> 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 (#189667): https://lists.openembedded.org/g/openembedded-core/message/189667 > >>> Mute This Topic: https://lists.openembedded.org/mt/102172913/3619876 > >>> Group Owner: openembedded-core+owner@lists.openembedded.org > >>> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [andrej.v@skyrain.eu] > >>> -=-=-=-=-=-=-=-=-=-=-=- > >>>
Hello Matsunaga-Shinji, On Wed, 25 Oct 2023 14:13:44 +0900 "Matsunaga-Shinji" <shin.matsunaga@fujitsu.com> 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 <shin.matsunaga@fujitsu.com> > Signed-off-by: Shunsuke Tokumoto <s-tokumoto@fujitsu.com> This patch triggers a selftest failure: AssertionError: 'Not affected' != 'Patched' - Not affected + Patched https://autobuilder.yoctoproject.org/typhoon/#/builders/127/builds/2329/steps/15/logs/stdio https://autobuilder.yoctoproject.org/typhoon/#/builders/80/builds/5912/steps/14/logs/stdio https://autobuilder.yoctoproject.org/typhoon/#/builders/79/builds/5962/steps/14/logs/stdio Luca
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)