From fd1c03e0be4f8333416d75416e79a2bafc0fe5ac Mon Sep 17 00:00:00 2001 From: Jiewei Qian Date: Tue, 10 Dec 2024 00:14:21 +0000 Subject: [PATCH] metadata: revise CPE validation Use the "official" XML schema validation pattern from published CPE schema. Add a error message to tell owners that they need to provide at least one component (other than part) in CPE URN format. Bug: 378273455 Change-Id: I5ac957f02a0f899d069161cdce54fff499fb35f3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/6073136 Commit-Queue: Jiewei Qian Reviewed-by: Anne Redulla --- metadata/fields/custom/cpe_prefix.py | 74 +++++++++++++--------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/metadata/fields/custom/cpe_prefix.py b/metadata/fields/custom/cpe_prefix.py index 2a04152f9..5cfa9842f 100644 --- a/metadata/fields/custom/cpe_prefix.py +++ b/metadata/fields/custom/cpe_prefix.py @@ -19,48 +19,37 @@ import metadata.fields.field_types as field_types import metadata.fields.util as util import metadata.validation_result as vr -# Pattern for CPE 2.3 URI binding (also compatible with CPE 2.2). -_PATTERN_CPE_URI = re.compile( - r"^c[pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\-~%]*){0,6}$") - -# Pattern that will match CPE 2.3 formatted string binding. -_PATTERN_CPE_FORMATTED_STRING = re.compile(r"^cpe:2\.3:[aho\-\*](:[^:]+){10}$") - - -def is_uri_cpe(value: str) -> bool: - """Returns whether the value conforms to the CPE 2.3 URI binding - (which is compatible with CPE 2.2), with the additional constraint - that at least one component other than "part" has been specified. - - For reference, see section 6.1 of the CPE Naming Specification - Version 2.3 at - https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf. +# Pattern that will match CPE 2.2 and CPE 2.3 URN format. +# +# Adapted from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd. +# We added a capture group (i.e. `match.group(1)`) for components-list, so we +# can determine if the CPE prefix provides sufficient information (see +# `is_adequate_cpe_urn` below). +_PATTERN_CPE_URN = re.compile( + r"^[c][pP][eE]:/[AHOaho]?((:[A-Za-z0-9\._\-~%]*){0,6})$") + +# Pattern that will match CPE 2.3 string format. +# Taken from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd +_PATTERN_CPE_FORMATTED_STRING = re.compile( + r"^cpe:2\.3:[aho\*\-](:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!\"#$$%&'\(\)\+,/:;<=>@\[\]\^`\{\|}~]))+(\?*|\*?))|[\*\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\*\-]))(:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!\"#$$%&'\(\)\+,/:;<=>@\[\]\^`\{\|}~]))+(\?*|\*?))|[\*\-])){4}$" +) + + +def is_adequate_cpe_urn(value) -> bool: + """Returns whether a given `value` conforms to CPE 2.3 and CPE 2.2 URN + format, and the given `value` provides at least one component other than + "part". + + See CPE Naming Specification: https://csrc.nist.gov/pubs/ir/7695/final. """ - if not util.matches(_PATTERN_CPE_URI, value): - return False - - components = value.split(":") - if len(components) < 3: - # At most, only part was provided. + m = _PATTERN_CPE_URN.match(value) + if not m: + # Didn't match CPE URN format. return False - # Check at least one component other than "part" has been specified. - for component in components[2:]: - if component: - return True - - return False - - -def is_formatted_string_cpe(value: str) -> bool: - """Returns whether the value conforms to the CPE 2.3 formatted - string binding. - - For reference, see section 6.2 of the CPE Naming Specification - Version 2.3 at - https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf. - """ - return util.matches(_PATTERN_CPE_FORMATTED_STRING, value) + cpe_components = m.group(1).split(':') + non_empty_components = list(filter(len, cpe_components)) + return len(non_empty_components) > 0 class CPEPrefixField(field_types.SingleLineTextField): @@ -69,8 +58,8 @@ class CPEPrefixField(field_types.SingleLineTextField): super().__init__(name="CPEPrefix") def _is_valid(self, value: str) -> bool: - return (util.is_unknown(value) or is_formatted_string_cpe(value) - or is_uri_cpe(value)) + return util.is_unknown(value) or is_adequate_cpe_urn( + value) or util.matches(_PATTERN_CPE_FORMATTED_STRING, value) def validate(self, value: str) -> Optional[vr.ValidationResult]: """Checks the given value is either 'unknown', or conforms to @@ -86,6 +75,9 @@ class CPEPrefixField(field_types.SingleLineTextField): "or 'unknown'.", "Search for a CPE tag for the package at " "https://nvd.nist.gov/products/cpe/search.", + "Please provide at least one CPE component other than part " + "(e.g. a for Application, h for Hardware, o for Operating " + "System)." f"Current value: '{value}'.", ])