diff --git a/base/CMakeLists.txt b/base/CMakeLists.txt index 77ea724b..2e9bb1d4 100644 --- a/base/CMakeLists.txt +++ b/base/CMakeLists.txt @@ -70,6 +70,9 @@ if (BUILD_TESTS) EXCLUDE_FROM_ALL array_tests.c) + add_link_options (-g -lgcov --coverage) + add_compile_options (-g -ftest-coverage -fprofile-arcs) + add_test (array-test array-test) target_include_directories (array-test PRIVATE ${CGREEN_INCLUDE_DIRS}) diff --git a/base/cvss.c b/base/cvss.c index de1628f7..11e15f9c 100644 --- a/base/cvss.c +++ b/base/cvss.c @@ -7,10 +7,18 @@ * @file * @brief CVSS utility functions * - * This file contains utility functions for handling CVSS v2 and v3. + * This file contains utility functions for handling CVSS v2, v3 and v4. * get_cvss_score_from_base_metrics calculates the CVSS base score from a CVSS * base vector. * + * CVSS v4.0: + * + * See the CVSS v4 calculator reference implementation at + * https://github.com/FIRSTdotorg/cvss-v4-calculator and the CVSS 4.0 + * specification document at + * https://www.first.org/cvss/v4.0/specification-document + * (especially sections 7., 8.2 and 8.3). + * * CVSS v3.1: * * See equations at https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator and @@ -79,6 +87,9 @@ static double get_cvss_score_from_base_metrics_v3 (const char *); +static double +get_cvss_score_from_metrics_v4 (const char *); + /* CVSS v2. */ // clang-format off @@ -126,7 +137,7 @@ get_cvss_score_from_base_metrics_v3 (const char *); // clang-format on /** - * @brief Base metrics. + * @brief CVSS v2 Base metrics. */ enum base_metrics { @@ -199,6 +210,222 @@ static const struct impact_item impact_map[][3] = { }, }; +// CVSS 4.0 + +/** + * @brief CVSS 4.0 metrics. + */ +typedef enum +{ + // Base (11 metrics) + CVSS4_AV, /**< Attack Vector */ + CVSS4_AC, /**< Attack Complexity */ + CVSS4_AT, /**< Attack Requirements */ + CVSS4_PR, /**< Privileges Required */ + CVSS4_UI, /**< User Interaction */ + CVSS4_VC, /**< Confidentiality Impact to the Vulnerable System */ + CVSS4_VI, /**< Integrity Impact to the Vulnerable System */ + CVSS4_VA, /**< Availability Impact to the Vulnerable System */ + CVSS4_SC, /**< Confidentiality Impact to the Subsequent System */ + CVSS4_SI, /**< Integrity Impact to the Subsequent System */ + CVSS4_SA, /**< Availability Impact to the Subsequent System */ + // Threat (1 metric) + CVSS4_E, /**< Exploit Maturity */ + // Environmental (14 metrics) + CVSS4_CR, /**< Confidentiality Requirement */ + CVSS4_IR, /**< Integrity Requirement */ + CVSS4_AR, /**< Availability Requirement */ + CVSS4_MAV, /**< Modified Attack Vector */ + CVSS4_MAC, /**< Modified Attack Complexity */ + CVSS4_MAT, /**< Modified Attack Requirements */ + CVSS4_MPR, /**< Modified Privileges Required */ + CVSS4_MUI, /**< Modified User Interaction */ + CVSS4_MVC, /**< Modified Confidentiality Impact to the Vulnerable System */ + CVSS4_MVI, /**< Modified Integrity Impact to the Vulnerable System */ + CVSS4_MVA, /**< Modified Availability Impact to the Vulnerable System */ + CVSS4_MSC, /**< Modified Confidentiality Impact to the Subsequent System */ + CVSS4_MSI, /**< Modified Integrity Impact to the Subsequent System */ + CVSS4_MSA, /**< Modified Availability Impact to the Subsequent System */ + // Supplemental (6 metrics) + CVSS4_S, /**< Safety */ + CVSS4_AU, /**< Automatable */ + CVSS4_R, /**< Recovery */ + CVSS4_V, /**< Value Density */ + CVSS4_RE, /**< Vulnerability Response Effort */ + CVSS4_U, /**< Provider Urgency */ + // Maximum number + CVSS4_METRICS_MAX, /**< Maximum number of metrics */ +} cvss4_metric_t; + +/** + * @brief Blank simplified CVSS 4.0 metrics string + */ +#define CVSS_METRICS_STR_BLANK "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + +/** + * @brief Blank simplified CVSS 4.0 macrovector string + */ +#define CVSS_MACROVECTOR_BLANK "XXXXXX" + +/** + * @brief String to enum mapping and allowed values for a CVSS 4.0 metric. + * + * This assumes all allowed values are single characters. + * The Provider Urgency metric can be longer, so it needs special handling + * only using the first letter. + */ +typedef struct +{ + const char *metric_str; /**< The metric abbreviation string */ + const cvss4_metric_t metric; /**< The metric enum value */ + const char *values; /**< String of characters allowed as values */ +} cvss4_metric_def_t; + +/** + * @brief String to enum mappings and allowed values for CVSS 4.0 metrics. + * + * Notes: + * - The Provider Urgency metric can be longer than one character, + * so it needs special handling. + * - The orginal specification only lists the value S (Safety) for the + * modified metrics MSI and MSA, but the calculator reference implementation + * also uses it for the unmodified ones, SI and SA. + */ +static cvss4_metric_def_t cvss4_metric_defs[] = { + // Base (11 metrics) + {"AV", CVSS4_AV, "NALP"}, + {"AC", CVSS4_AC, "LH"}, + {"AT", CVSS4_AT, "NP"}, + {"PR", CVSS4_PR, "NLH"}, + {"UI", CVSS4_UI, "NPA"}, + {"VC", CVSS4_VC, "HLN"}, + {"VI", CVSS4_VI, "HLN"}, + {"VA", CVSS4_VA, "HLN"}, + {"SC", CVSS4_SC, "HLN"}, + {"SI", CVSS4_SI, "HLNS"}, + {"SA", CVSS4_SA, "HLNS"}, + // Threat (1 metric) + {"E", CVSS4_E, "XAPU"}, + // Environmental (14 metrics) + {"CR", CVSS4_CR, "XHML"}, + {"IR", CVSS4_IR, "XHML"}, + {"AR", CVSS4_AR, "XHML"}, + {"MAV", CVSS4_MAV, "XNALP"}, + {"MAC", CVSS4_MAC, "XLH"}, + {"MAT", CVSS4_MAT, "XNP"}, + {"MPR", CVSS4_MPR, "XNLH"}, + {"MUI", CVSS4_MUI, "XNPA"}, + {"MVC", CVSS4_MVC, "XHLN"}, + {"MVI", CVSS4_MVI, "XHLN"}, + {"MVA", CVSS4_MVA, "XHLN"}, + {"MSC", CVSS4_MSC, "XHLN"}, + {"MSI", CVSS4_MSI, "XHLNS"}, + {"MSA", CVSS4_MSA, "XHLNS"}, + // Supplemental (6 metrics) + {"S", CVSS4_S, "XNP"}, + {"AU", CVSS4_AU, "XNY"}, + {"R", CVSS4_R, "XAUI"}, + {"V", CVSS4_V, "XDC"}, + {"RE", CVSS4_RE, "XLMH"}, + {"U", CVSS4_U, "XCGAR"}, // Abbreviated to first letters + // Max number / array terminator + {NULL, CVSS4_METRICS_MAX, NULL}}; + +/** + * @brief Key-Value mappings of CVSS 4.0 macrovectors to scores. + */ +typedef struct +{ + const char *vector; + double score; +} cvss4_macrovector_mapping_t; + +/** + * @brief CVSS 4.0 macrovector mappings + * + * This list has been generated from the lookup table in the + * FIRST CVSS calculator reference implementation at + * https://github.com/FIRSTdotorg/cvss-v4-calculator/blob/main/cvss_lookup.js + */ +static const cvss4_macrovector_mapping_t cvss4_macrovector_mappings[] = { + {"000000", 10}, {"000001", 9.9}, {"000010", 9.8}, {"000011", 9.5}, + {"000020", 9.5}, {"000021", 9.2}, {"000100", 10}, {"000101", 9.6}, + {"000110", 9.3}, {"000111", 8.7}, {"000120", 9.1}, {"000121", 8.1}, + {"000200", 9.3}, {"000201", 9}, {"000210", 8.9}, {"000211", 8}, + {"000220", 8.1}, {"000221", 6.8}, {"001000", 9.8}, {"001001", 9.5}, + {"001010", 9.5}, {"001011", 9.2}, {"001020", 9}, {"001021", 8.4}, + {"001100", 9.3}, {"001101", 9.2}, {"001110", 8.9}, {"001111", 8.1}, + {"001120", 8.1}, {"001121", 6.5}, {"001200", 8.8}, {"001201", 8}, + {"001210", 7.8}, {"001211", 7}, {"001220", 6.9}, {"001221", 4.8}, + {"002001", 9.2}, {"002011", 8.2}, {"002021", 7.2}, {"002101", 7.9}, + {"002111", 6.9}, {"002121", 5}, {"002201", 6.9}, {"002211", 5.5}, + {"002221", 2.7}, {"010000", 9.9}, {"010001", 9.7}, {"010010", 9.5}, + {"010011", 9.2}, {"010020", 9.2}, {"010021", 8.5}, {"010100", 9.5}, + {"010101", 9.1}, {"010110", 9}, {"010111", 8.3}, {"010120", 8.4}, + {"010121", 7.1}, {"010200", 9.2}, {"010201", 8.1}, {"010210", 8.2}, + {"010211", 7.1}, {"010220", 7.2}, {"010221", 5.3}, {"011000", 9.5}, + {"011001", 9.3}, {"011010", 9.2}, {"011011", 8.5}, {"011020", 8.5}, + {"011021", 7.3}, {"011100", 9.2}, {"011101", 8.2}, {"011110", 8}, + {"011111", 7.2}, {"011120", 7}, {"011121", 5.9}, {"011200", 8.4}, + {"011201", 7}, {"011210", 7.1}, {"011211", 5.2}, {"011220", 5}, + {"011221", 3}, {"012001", 8.6}, {"012011", 7.5}, {"012021", 5.2}, + {"012101", 7.1}, {"012111", 5.2}, {"012121", 2.9}, {"012201", 6.3}, + {"012211", 2.9}, {"012221", 1.7}, {"100000", 9.8}, {"100001", 9.5}, + {"100010", 9.4}, {"100011", 8.7}, {"100020", 9.1}, {"100021", 8.1}, + {"100100", 9.4}, {"100101", 8.9}, {"100110", 8.6}, {"100111", 7.4}, + {"100120", 7.7}, {"100121", 6.4}, {"100200", 8.7}, {"100201", 7.5}, + {"100210", 7.4}, {"100211", 6.3}, {"100220", 6.3}, {"100221", 4.9}, + {"101000", 9.4}, {"101001", 8.9}, {"101010", 8.8}, {"101011", 7.7}, + {"101020", 7.6}, {"101021", 6.7}, {"101100", 8.6}, {"101101", 7.6}, + {"101110", 7.4}, {"101111", 5.8}, {"101120", 5.9}, {"101121", 5}, + {"101200", 7.2}, {"101201", 5.7}, {"101210", 5.7}, {"101211", 5.2}, + {"101220", 5.2}, {"101221", 2.5}, {"102001", 8.3}, {"102011", 7}, + {"102021", 5.4}, {"102101", 6.5}, {"102111", 5.8}, {"102121", 2.6}, + {"102201", 5.3}, {"102211", 2.1}, {"102221", 1.3}, {"110000", 9.5}, + {"110001", 9}, {"110010", 8.8}, {"110011", 7.6}, {"110020", 7.6}, + {"110021", 7}, {"110100", 9}, {"110101", 7.7}, {"110110", 7.5}, + {"110111", 6.2}, {"110120", 6.1}, {"110121", 5.3}, {"110200", 7.7}, + {"110201", 6.6}, {"110210", 6.8}, {"110211", 5.9}, {"110220", 5.2}, + {"110221", 3}, {"111000", 8.9}, {"111001", 7.8}, {"111010", 7.6}, + {"111011", 6.7}, {"111020", 6.2}, {"111021", 5.8}, {"111100", 7.4}, + {"111101", 5.9}, {"111110", 5.7}, {"111111", 5.7}, {"111120", 4.7}, + {"111121", 2.3}, {"111200", 6.1}, {"111201", 5.2}, {"111210", 5.7}, + {"111211", 2.9}, {"111220", 2.4}, {"111221", 1.6}, {"112001", 7.1}, + {"112011", 5.9}, {"112021", 3}, {"112101", 5.8}, {"112111", 2.6}, + {"112121", 1.5}, {"112201", 2.3}, {"112211", 1.3}, {"112221", 0.6}, + {"200000", 9.3}, {"200001", 8.7}, {"200010", 8.6}, {"200011", 7.2}, + {"200020", 7.5}, {"200021", 5.8}, {"200100", 8.6}, {"200101", 7.4}, + {"200110", 7.4}, {"200111", 6.1}, {"200120", 5.6}, {"200121", 3.4}, + {"200200", 7}, {"200201", 5.4}, {"200210", 5.2}, {"200211", 4}, + {"200220", 4}, {"200221", 2.2}, {"201000", 8.5}, {"201001", 7.5}, + {"201010", 7.4}, {"201011", 5.5}, {"201020", 6.2}, {"201021", 5.1}, + {"201100", 7.2}, {"201101", 5.7}, {"201110", 5.5}, {"201111", 4.1}, + {"201120", 4.6}, {"201121", 1.9}, {"201200", 5.3}, {"201201", 3.6}, + {"201210", 3.4}, {"201211", 1.9}, {"201220", 1.9}, {"201221", 0.8}, + {"202001", 6.4}, {"202011", 5.1}, {"202021", 2}, {"202101", 4.7}, + {"202111", 2.1}, {"202121", 1.1}, {"202201", 2.4}, {"202211", 0.9}, + {"202221", 0.4}, {"210000", 8.8}, {"210001", 7.5}, {"210010", 7.3}, + {"210011", 5.3}, {"210020", 6}, {"210021", 5}, {"210100", 7.3}, + {"210101", 5.5}, {"210110", 5.9}, {"210111", 4}, {"210120", 4.1}, + {"210121", 2}, {"210200", 5.4}, {"210201", 4.3}, {"210210", 4.5}, + {"210211", 2.2}, {"210220", 2}, {"210221", 1.1}, {"211000", 7.5}, + {"211001", 5.5}, {"211010", 5.8}, {"211011", 4.5}, {"211020", 4}, + {"211021", 2.1}, {"211100", 6.1}, {"211101", 5.1}, {"211110", 4.8}, + {"211111", 1.8}, {"211120", 2}, {"211121", 0.9}, {"211200", 4.6}, + {"211201", 1.8}, {"211210", 1.7}, {"211211", 0.7}, {"211220", 0.8}, + {"211221", 0.2}, {"212001", 5.3}, {"212011", 2.4}, {"212021", 1.4}, + {"212101", 2.4}, {"212111", 1.2}, {"212121", 0.5}, {"212201", 1}, + {"212211", 0.3}, {"212221", 0.1}, {NULL, 0.0}}; + +/** + * @brief Hashtable for quick lookup of CVSS macrovector scores. + * + * Macrovector scores should be looked up with cvss4_macrovector_score + * which ensures the table is initialized and returns the scores as + * double values instead of pointers. + */ +static GHashTable *cvss4_macrovector_table = NULL; + /** * @brief Determine base metric enumeration from a string. * @@ -367,6 +594,8 @@ get_cvss_score_from_base_metrics (const char *cvss_str) || g_str_has_prefix (cvss_str, "CVSS:3.0/")) return get_cvss_score_from_base_metrics_v3 (cvss_str + strlen ("CVSS:3.X/")); + if (g_str_has_prefix (cvss_str, "CVSS:4.0/")) + return get_cvss_score_from_metrics_v4 (cvss_str + strlen ("CVSS:4.X/")); memset (&cvss, 0x00, sizeof (struct cvss)); @@ -603,3 +832,1052 @@ get_cvss_score_from_base_metrics_v3 (const char *cvss_str) return roundup (base); } + +/** + * @brief Initialize the CVSS 4.0 macrovector lookup table. + */ +static void +cvss4_init_macrovector_table () +{ + if (cvss4_macrovector_table) + return; + + int index = 0; + cvss4_macrovector_table = g_hash_table_new (g_str_hash, g_str_equal); + while (cvss4_macrovector_mappings[index].vector != NULL) + { + g_hash_table_insert (cvss4_macrovector_table, + (gpointer) cvss4_macrovector_mappings[index].vector, + (gpointer) &cvss4_macrovector_mappings[index].score); + + index++; + } +} + +/** + * @brief Get the CVSS 4.0 score for a given macrovector string. + * + * @param[in] vector The macrovector to look up. + * + * @return The score of the given vector or -1.0 if the macrovector is invalid. + */ +static inline double +cvss4_macrovector_score (const char *vector) +{ + double *score_ptr; + + cvss4_init_macrovector_table (); + score_ptr = g_hash_table_lookup (cvss4_macrovector_table, vector); + if (score_ptr) + return *score_ptr; + return -1.0; +} + +/** + * @brief Get the effective value of a metric in a simplified CVSS4 vector. + * + * As this only returns the first character, the Provider Urgency metric + * (CVSS4_U) needs special handling to get the full string. + * + * @param[in] simplified_vec The simplified vector string to get value from. + * @param[in] metric The metric to get the value of. + * + * @return The metric value as a single character. + */ +static char +cvss4_m (const char *simplified_vec, cvss4_metric_t metric) +{ + char selected = simplified_vec[metric]; + + // If E=X it will default to the worst case i.e. E=A + if (metric == CVSS4_E && selected == 'X') + return 'A'; + + // If CR=X, IR=X or AR=X they will default to the worst case + // i.e. CR=H, IR=H and AR=H + if ((metric == CVSS4_CR || metric == CVSS4_IR || metric == CVSS4_AR) + && selected == 'X') + return 'H'; + + // All other environmental metrics just overwrite base score values, + // so if they’re not defined just use the base score value. + if (metric >= CVSS4_AV && metric <= CVSS4_SA) + { + char modified_selected = simplified_vec[metric - CVSS4_AV + CVSS4_MAV]; + if (modified_selected != 'X') + return modified_selected; + } + + return selected; +} + +/** + * @brief Simplify CVSS 4.0 base vector so metrics can be indexed by enum. + * + * The vector is simplified to a strictly ordered character array with + * each character index corresponding to the cvss4_base_metrics enum value + * and using 'X' for undefined metric values. + * + * This relies on all allowed values being single characters, or having + * unique first characters in case of the Provider Urgency metric. + * + * @param[in] cvss_str The original vector without the prefix "CVSS:4.0/". + * + * @return A simplified vector string as described above or NULL on error. + */ +static gchar * +simplify_cvss4_vector (const char *cvss_str) +{ + gchar **split_cvss_str, **split_cvss_point; + gboolean valid = TRUE; + gchar *vec = NULL; + cvss4_metric_t metric; + + if (cvss_str == NULL || strcmp (cvss_str, "") == 0) + return NULL; + + vec = g_strdup (CVSS_METRICS_STR_BLANK); + + split_cvss_str = g_strsplit (cvss_str, "/", -1); + split_cvss_point = split_cvss_str; + while (valid && *split_cvss_point) + { + if (strcmp (*split_cvss_point, "") == 0) + { + split_cvss_point++; + continue; + } + + gchar **split_component = g_strsplit (*split_cvss_point, ":", 2); + const gchar *metric_str = split_component[0], *value = split_component[1]; + + valid = FALSE; + + if (value == NULL) + { + g_debug ("%s: value for metric %s missing", __func__, metric_str); + break; + } + else if (strcasecmp (metric_str, "U") == 0) + { + // Special case for the Provider Urgency metric + if (strcasecmp (value, "Red") && strcasecmp (value, "Amber") + && strcasecmp (value, "Green") && strcasecmp (value, "Clear") + && strcasecmp (value, "X")) + { + g_debug ("%s: value for metric %s must be one of" + " 'Red', 'Amber', 'Green', 'Clear', 'X'", + __func__, metric_str); + break; + } + else + valid = TRUE; + } + else if (strlen (value) != 1) + { + g_debug ("%s: value for metric %s must be 1 character", __func__, + metric_str); + break; + } + + cvss4_metric_def_t *metric_def = &cvss4_metric_defs[0]; + while (metric_def->metric_str) + { + if (strcasecmp (metric_str, metric_def->metric_str) == 0) + { + char value_char = g_ascii_toupper (value[0]); + + // Reject duplicate metrics + if (vec[metric_def->metric] != 'X') + { + g_debug ("%s: duplicate metric %s", __func__, metric_str); + break; + } + + // Set the metric in the simplified vector + if (strchr (metric_def->values, value_char)) + { + valid = TRUE; + vec[metric_def->metric] = value_char; + } + else + { + g_debug ("%s: invalid metric: %s:%c", __func__, metric_str, + value_char); + } + break; + } + metric_def++; + } + + split_cvss_point++; + g_strfreev (split_component); + } + g_strfreev (split_cvss_str); + + for (metric = CVSS4_AV; valid && metric <= CVSS4_SA; metric++) + { + if (vec[metric] == 'X') + { + g_debug ("%s: mandatory metric %s is undefined", __func__, + cvss4_metric_defs[metric].metric_str); + valid = FALSE; + } + } + + if (!valid) + { + g_debug ("%s: vector %s is invalid", __func__, cvss_str); + g_free (vec); + return NULL; + } + + return vec; +} + +/** + * @brief Expands a simplified CVSS 4.0 vector into its full string form + * + * @param[in] vec The simplified vector to expand + * + * @return The full vector, including the "CVSS:4.0/" prefix + */ +static gchar * +cvss4_vector_expand (const char *vec) +{ + cvss4_metric_t metric; + GString *str = g_string_new ("CVSS:4.0"); + for (metric = 0; metric < CVSS4_METRICS_MAX; metric++) + { + const char *expanded_value; + if (vec[metric] == 'X') + continue; + cvss4_metric_def_t def = cvss4_metric_defs[metric]; + if (metric == CVSS4_U) + { + switch (vec[metric]) + { + case 'R': + expanded_value = "Red"; + break; + case 'A': + expanded_value = "Amber"; + break; + case 'G': + expanded_value = "Green"; + break; + case 'C': + expanded_value = "Clear"; + break; + default: + expanded_value = NULL; + } + } + else + expanded_value = NULL; + + if (expanded_value) + g_string_append_printf (str, "/%s:%s", def.metric_str, expanded_value); + else + g_string_append_printf (str, "/%s:%c", def.metric_str, vec[metric]); + } + return g_string_free (str, FALSE); +} + +/** + * @brief Calculate CVSS 4.0 macrovector from a simplified vector. + * + * @param[in] vec The simplified vector to get the macrovector of + * + * @return The macrovector. + */ +static inline gchar * +cvss4_macrovector (const char *vec) +{ + gchar *macrovector; + if (vec == NULL) + return NULL; + + macrovector = g_strdup (CVSS_MACROVECTOR_BLANK); + + // EQ1: 0-AV:N and PR:N and UI:N + // 1-(AV:N or PR:N or UI:N) and not (AV:N and PR:N and UI:N) and not AV:P + // 2-AV:P or not(AV:N or PR:N or UI:N) + char av = cvss4_m (vec, CVSS4_AV); + char pr = cvss4_m (vec, CVSS4_PR); + char ui = cvss4_m (vec, CVSS4_UI); + + if (av == 'N' && pr == 'N' && ui == 'N') + macrovector[0] = '0'; + else if ((av == 'N' || pr == 'N' || ui == 'N') && !(av == 'P')) + macrovector[0] = '1'; + else + macrovector[0] = '2'; + + // EQ2: 0-(AC:L and AT:N) + // 1-(not(AC:L and AT:N)) + char ac = cvss4_m (vec, CVSS4_AC); + char at = cvss4_m (vec, CVSS4_AT); + + if (ac == 'L' && at == 'N') + macrovector[1] = '0'; + else + macrovector[1] = '1'; + + // EQ3: 0-(VC:H and VI:H) + // 1-(not(VC:H and VI:H) and (VC:H or VI:H or VA:H)) + // 2-not (VC:H or VI:H or VA:H) + char vc = cvss4_m (vec, CVSS4_VC); + char vi = cvss4_m (vec, CVSS4_VI); + char va = cvss4_m (vec, CVSS4_VA); + + if (vc == 'H' && vi == 'H') + macrovector[2] = '0'; + else if (vc == 'H' || vi == 'H' || va == 'H') + macrovector[2] = '1'; + else + macrovector[2] = '2'; + + // EQ4: 0-(MSI:S or MSA:S) + // 1-not (MSI:S or MSA:S) and (SC:H or SI:H or SA:H) + // 2-not (MSI:S or MSA:S) and not (SC:H or SI:H or SA:H) + // + // "Effective" SI and SA are the same as MSI and MSA for the purposes of + // checking for the "Safety" value. + char sc = cvss4_m (vec, CVSS4_SC); + char si = cvss4_m (vec, CVSS4_SI); + char sa = cvss4_m (vec, CVSS4_SA); + if (si == 'S' || sa == 'S') + macrovector[3] = '0'; + else if (sc == 'H' || si == 'H' || sa == 'H') + macrovector[3] = '1'; + else + macrovector[3] = '2'; + + // EQ5: 0-E:A + // 1-E:P + // 2-E:U + char e = cvss4_m (vec, CVSS4_E); + if (e == 'A') + macrovector[4] = '0'; + else if (e == 'P') + macrovector[4] = '1'; + else + macrovector[4] = '2'; + + // EQ6: 0-(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H) + // 1-not[(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)] + char cr = cvss4_m (vec, CVSS4_CR); + char ir = cvss4_m (vec, CVSS4_IR); + char ar = cvss4_m (vec, CVSS4_AR); + if ((cr == 'H' && vc == 'H') || (ir == 'H' && vi == 'H') + || (ar == 'H' && va == 'H')) + macrovector[5] = '0'; + else + macrovector[5] = '1'; + + return macrovector; +} + +/** + * @brief Calulate the maximal scoring differences from a CVSS 4.0 macrovector. + * + * @param[in] macrovector + * @param[out] available_distance_eq1 Maximal scoring diff. for EQ1 + * @param[out] available_distance_eq2 Maximal scoring diff. for EQ2 + * @param[out] available_distance_eq3eq6 Maximal scoring diff. for EQ3 and EQ6 + * @param[out] available_distance_eq4 Maximal scoring diff. for EQ4 + * @param[out] available_distance_eq5 Maximal scoring diff. for EQ5 + */ +static void +cvss4_maximal_scoring_differences (const char *macrovector, + double *available_distance_eq1, + double *available_distance_eq2, + double *available_distance_eq3eq6, + double *available_distance_eq4, + double *available_distance_eq5) +{ + double value = cvss4_macrovector_score (macrovector); + double score_eq1_next_lower_macro, score_eq2_next_lower_macro; + double score_eq3eq6_next_lower_macro; + double score_eq4_next_lower_macro, score_eq5_next_lower_macro; + + // Next lower macrovector for EQ1 only exists if EQ1 is 0 or 1. + if (macrovector[0] <= '1') + { + gchar *eq1_next_lower_macro = g_strdup (macrovector); + eq1_next_lower_macro[0]++; + score_eq1_next_lower_macro = + cvss4_macrovector_score (eq1_next_lower_macro); + g_free (eq1_next_lower_macro); + } + else + score_eq1_next_lower_macro = -1.0; + + // Next lower macrovector for EQ2 only exists if EQ2 is 0. + if (macrovector[1] == '0') + { + gchar *eq2_next_lower_macro = g_strdup (macrovector); + eq2_next_lower_macro[1]++; + score_eq2_next_lower_macro = + cvss4_macrovector_score (eq2_next_lower_macro); + } + else + score_eq2_next_lower_macro = -1.0; + + // Next lower macrovector for EQ3. + if ((macrovector[2] == '0' || macrovector[2] == '1') && macrovector[5] == '1') + { + gchar *eq3eq6_next_lower_macro = g_strdup (macrovector); + eq3eq6_next_lower_macro[2]++; + score_eq3eq6_next_lower_macro = + cvss4_macrovector_score (eq3eq6_next_lower_macro); + g_free (eq3eq6_next_lower_macro); + } + else if (macrovector[2] == '1' && macrovector[5] == '0') + { + gchar *eq3eq6_next_lower_macro = g_strdup (macrovector); + eq3eq6_next_lower_macro[5]++; + score_eq3eq6_next_lower_macro = + cvss4_macrovector_score (eq3eq6_next_lower_macro); + g_free (eq3eq6_next_lower_macro); + } + else if (macrovector[2] == '0' && macrovector[5] == '0') + { + gchar *eq3eq6_next_lower_macro_left = g_strdup (macrovector); + eq3eq6_next_lower_macro_left[5]++; + gchar *eq3eq6_next_lower_macro_right = g_strdup (macrovector); + eq3eq6_next_lower_macro_right[2]++; + double score_eq3eq6_next_lower_macro_left = + cvss4_macrovector_score (eq3eq6_next_lower_macro_left); + double score_eq3eq6_next_lower_macro_right = + cvss4_macrovector_score (eq3eq6_next_lower_macro_right); + + if (score_eq3eq6_next_lower_macro_left + > score_eq3eq6_next_lower_macro_right) + score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_left; + else + score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_right; + + g_free (eq3eq6_next_lower_macro_left); + g_free (eq3eq6_next_lower_macro_right); + } + else + score_eq3eq6_next_lower_macro = -1.0; + + // Next lower macrovector for EQ4 only exists if EQ4 is 0 or 1. + if (macrovector[3] <= '1') + { + gchar *eq4_next_lower_macro = g_strdup (macrovector); + eq4_next_lower_macro[3]++; + score_eq4_next_lower_macro = + cvss4_macrovector_score (eq4_next_lower_macro); + g_free (eq4_next_lower_macro); + } + else + score_eq4_next_lower_macro = -1.0; + + // Next lower macrovector for EQ5 only exists if EQ5 is 0 or 1. + if (macrovector[4] <= '1') + { + gchar *eq5_next_lower_macro = g_strdup (macrovector); + eq5_next_lower_macro[4]++; + score_eq5_next_lower_macro = + cvss4_macrovector_score (eq5_next_lower_macro); + g_free (eq5_next_lower_macro); + } + else + score_eq5_next_lower_macro = -1.0; + + if (value != -1.0) + { + *available_distance_eq1 = score_eq1_next_lower_macro != -1.0 + ? value - score_eq1_next_lower_macro + : -1.0; + *available_distance_eq2 = score_eq2_next_lower_macro != -1.0 + ? value - score_eq2_next_lower_macro + : -1.0; + *available_distance_eq3eq6 = score_eq3eq6_next_lower_macro != -1.0 + ? value - score_eq3eq6_next_lower_macro + : -1.0; + *available_distance_eq4 = score_eq4_next_lower_macro != -1.0 + ? value - score_eq4_next_lower_macro + : -1.0; + *available_distance_eq5 = score_eq5_next_lower_macro != -1.0 + ? value - score_eq5_next_lower_macro + : -1.0; + } + else + { + *available_distance_eq1 = -1.0; + *available_distance_eq2 = -1.0; + *available_distance_eq3eq6 = -1.0; + *available_distance_eq4 = -1.0; + *available_distance_eq5 = -1.0; + } +} + +/** + * @brief Composes a list of max vectors for the given CVSS 4.0 macrovector. + * + * @param[in] macrovector The macrovector to get the max vectors of. + * + * @return NULL-terminated array of vectors in simplified form. + */ +static gchar ** +cvss4_max_vectors (const char *macrovector) +{ + const char **eq1_maxes, **eq2_maxes, **eq3eq6_maxes; + const char *eq4_max, *eq5_max; + gchar **ret; + + // EQ1 + static const char *eq1_maxes_0[] = {"AV:N/PR:N/UI:N/", NULL}; + static const char *eq1_maxes_1[] = {"AV:A/PR:N/UI:N/", "AV:N/PR:L/UI:N/", + "AV:N/PR:N/UI:P/", NULL}; + static const char *eq1_maxes_2[] = {"AV:P/PR:N/UI:N/", "AV:A/PR:L/UI:P/", + NULL}; + if (macrovector[0] == '0') + eq1_maxes = eq1_maxes_0; + else if (macrovector[0] == '1') + eq1_maxes = eq1_maxes_1; + else + eq1_maxes = eq1_maxes_2; + + // EQ2 + static const char *eq2_maxes_0[] = {"AC:L/AT:N/", NULL}; + static const char *eq2_maxes_1[] = {"AC:H/AT:N/", "AC:L/AT:P/", NULL}; + if (macrovector[1] == '0') + eq2_maxes = eq2_maxes_0; + else + eq2_maxes = eq2_maxes_1; + + // EQ3+EQ6 + static const char *eq3eq6_maxes_00[] = {"VC:H/VI:H/VA:H/CR:H/IR:H/AR:H/", + NULL}; + static const char *eq3eq6_maxes_01[] = { + "VC:H/VI:H/VA:L/CR:M/IR:M/AR:H/", "VC:H/VI:H/VA:H/CR:M/IR:M/AR:M/", NULL}; + static const char *eq3eq6_maxes_10[] = { + "VC:L/VI:H/VA:H/CR:H/IR:H/AR:H/", "VC:H/VI:L/VA:H/CR:H/IR:H/AR:H/", NULL}; + static const char *eq3eq6_maxes_11[] = { + "VC:L/VI:H/VA:L/CR:H/IR:M/AR:H/", "VC:L/VI:H/VA:H/CR:H/IR:M/AR:M/", + "VC:H/VI:L/VA:H/CR:M/IR:H/AR:M/", "VC:H/VI:L/VA:L/CR:M/IR:H/AR:H/", + "VC:L/VI:L/VA:H/CR:H/IR:H/AR:M/", NULL}; + static const char *eq3eq6_maxes_21[] = {"VC:L/VI:L/VA:L/CR:H/IR:H/AR:H/", + NULL}; + if ((macrovector[2] == '0')) + { + if (macrovector[5] == '0') + eq3eq6_maxes = eq3eq6_maxes_00; + else + eq3eq6_maxes = eq3eq6_maxes_01; + } + else if ((macrovector[2] == '1')) + { + if (macrovector[5] == '0') + eq3eq6_maxes = eq3eq6_maxes_10; + else + eq3eq6_maxes = eq3eq6_maxes_11; + } + else + eq3eq6_maxes = eq3eq6_maxes_21; + + // EQ4 + if (macrovector[3] == '0') + eq4_max = "SC:H/SI:S/SA:S/"; + else if (macrovector[3] == '1') + eq4_max = "SC:H/SI:H/SA:H/"; + else + eq4_max = "SC:L/SI:L/SA:L/"; + + // EQ5 + if (macrovector[4] == '0') + eq5_max = "E:A/"; + else if (macrovector[4] == '1') + eq5_max = "E:P/"; + else + eq5_max = "E:U/"; + + GPtrArray *max_vectors = g_ptr_array_new (); + const char **eq1_max, **eq2_max, **eq3eq6_max; + for (eq1_max = eq1_maxes; *eq1_max != NULL; eq1_max++) + { + for (eq2_max = eq2_maxes; *eq2_max != NULL; eq2_max++) + { + for (eq3eq6_max = eq3eq6_maxes; *eq3eq6_max != NULL; eq3eq6_max++) + { + gchar *full_vector = + g_strdup_printf ("%s%s%s%s%s", *eq1_max, *eq2_max, *eq3eq6_max, + eq4_max, eq5_max); + gchar *vector = simplify_cvss4_vector (full_vector); + if (vector == NULL) + g_warning ("%s: generated vector %s is invalid", __func__, + full_vector); + else + g_ptr_array_add (max_vectors, vector); + g_free (full_vector); + } + } + } + + g_ptr_array_add (max_vectors, NULL); + + ret = (gchar **) max_vectors->pdata; + g_ptr_array_free (max_vectors, FALSE); + return ret; +} + +/** + * @brief Get the index of a CVSS 4.0 metric value for severity distances. + * + * @param[in] metric The metric to check. + * @param[in] value The value of the given metric. + * + * @return The index value + */ +static double +cvss4_metric_level (cvss4_metric_t metric, char value) +{ + switch (metric) + { + case CVSS4_AV: + switch (value) + { + case 'N': + return 0.0; + case 'A': + return 0.1; + case 'L': + return 0.2; + case 'P': + return 0.3; + default: + return -99.0; + } + break; + case CVSS4_PR: + switch (value) + { + case 'N': + return 0.0; + case 'L': + return 0.1; + case 'H': + return 0.2; + default: + return -99.0; + } + break; + case CVSS4_UI: + switch (value) + { + case 'N': + return 0.0; + case 'P': + return 0.1; + case 'A': + return 0.2; + default: + return -99.0; + } + break; + case CVSS4_AC: + switch (value) + { + case 'L': + return 0.0; + case 'H': + return 0.1; + default: + return -99.0; + } + break; + case CVSS4_AT: + switch (value) + { + case 'N': + return 0.0; + case 'P': + return 0.1; + default: + return -99.0; + } + break; + case CVSS4_VC: + case CVSS4_VI: + case CVSS4_VA: + switch (value) + { + case 'H': + return 0.0; + case 'L': + return 0.1; + case 'N': + return 0.2; + default: + return -99.0; + } + break; + case CVSS4_SC: + case CVSS4_SI: + case CVSS4_SA: + switch (value) + { + case 'S': + return 0.0; + case 'H': + return 0.1; + case 'L': + return 0.2; + case 'N': + return 0.3; + default: + return -99.0; + } + break; + case CVSS4_CR: + case CVSS4_IR: + case CVSS4_AR: + switch (value) + { + case 'H': + return 0.0; + case 'M': + return 0.1; + case 'L': + return 0.2; + default: + return -99.0; + } + break; + + // The Exploit Maturity metric is included in the reference implementation + // but never used + /* + case CVSS4_E: + switch (value) + { + case 'A': return 0.0; + case 'P': return 0.1; + case 'U': return 0.2; + } + break; + */ + default: + return -99.0; + } +} + +/** + * @brief Calculate severity distance for a metric in two CVSS 4.0 vectors. + * + * @param[in] metric The metric to calculate severity distance for. + * @param[in] vec The vector to be scored in simplified form. + * @param[in] max_vec The max vector to subtract in simplified form. + * + * @return The severity distance. + */ +static inline double +cvss4_severity_distance (cvss4_metric_t metric, const char *vec, + const char *max_vec) +{ + return cvss4_metric_level (metric, cvss4_m (vec, metric)) + - cvss4_metric_level (metric, max_vec[metric]); +} + +/** + * @brief Calculate current severity distances for given CVSS 4.0 vector + * + * @param[in] vec The vector in simplified form + * @param[in] macrovector Corresponding macrovector + * @param[out] current_severity_distance_eq1 Distance for EQ1 + * @param[out] current_severity_distance_eq2 Distance for EQ2 + * @param[out] current_severity_distance_eq3eq6 Distance for EQ3 and EQ6 + * @param[out] current_severity_distance_eq4 Distance for EQ4 + * @param[out] current_severity_distance_eq5 Distance for EQ5 + */ +static void +cvss4_current_severity_distances (const char *vec, const char *macrovector, + double *current_severity_distance_eq1, + double *current_severity_distance_eq2, + double *current_severity_distance_eq3eq6, + double *current_severity_distance_eq4, + double *current_severity_distance_eq5) +{ + double severity_distance_AV, severity_distance_PR, severity_distance_UI; + double severity_distance_AC, severity_distance_AT; + double severity_distance_VC, severity_distance_VI, severity_distance_VA; + double severity_distance_SC, severity_distance_SI, severity_distance_SA; + double severity_distance_CR, severity_distance_IR, severity_distance_AR; + + severity_distance_AV = severity_distance_PR = severity_distance_UI = -99.0; + severity_distance_AC = severity_distance_AT = -99.0; + severity_distance_VC = severity_distance_VI = severity_distance_VA = -99.0; + severity_distance_SC = severity_distance_SI = severity_distance_SA = -99.0; + severity_distance_CR = severity_distance_IR = severity_distance_AR = -99.0; + + char **max_vectors, **max_vec; + max_vectors = cvss4_max_vectors (macrovector); + for (max_vec = max_vectors; *max_vec != NULL; max_vec++) + { + severity_distance_AV = cvss4_severity_distance (CVSS4_AV, vec, *max_vec); + severity_distance_PR = cvss4_severity_distance (CVSS4_PR, vec, *max_vec); + severity_distance_UI = cvss4_severity_distance (CVSS4_UI, vec, *max_vec); + + severity_distance_AC = cvss4_severity_distance (CVSS4_AC, vec, *max_vec); + severity_distance_AT = cvss4_severity_distance (CVSS4_AT, vec, *max_vec); + + severity_distance_VC = cvss4_severity_distance (CVSS4_VC, vec, *max_vec); + severity_distance_VI = cvss4_severity_distance (CVSS4_VI, vec, *max_vec); + severity_distance_VA = cvss4_severity_distance (CVSS4_VA, vec, *max_vec); + + severity_distance_SC = cvss4_severity_distance (CVSS4_SC, vec, *max_vec); + severity_distance_SI = cvss4_severity_distance (CVSS4_SI, vec, *max_vec); + severity_distance_SA = cvss4_severity_distance (CVSS4_SA, vec, *max_vec); + + severity_distance_CR = cvss4_severity_distance (CVSS4_CR, vec, *max_vec); + severity_distance_IR = cvss4_severity_distance (CVSS4_IR, vec, *max_vec); + severity_distance_AR = cvss4_severity_distance (CVSS4_AR, vec, *max_vec); + + if (severity_distance_AV < 0.0 || severity_distance_PR < 0.0 + || severity_distance_UI < 0.0 || severity_distance_AC < 0.0 + || severity_distance_AT < 0.0 || severity_distance_VC < 0.0 + || severity_distance_VI < 0.0 || severity_distance_VA < 0.0 + || severity_distance_SC < 0.0 || severity_distance_SI < 0.0 + || severity_distance_SA < 0.0 || severity_distance_CR < 0.0 + || severity_distance_IR < 0.0 || severity_distance_AR < 0.0) + continue; + + g_debug ("%s AV:%0.1f PR:%0.1f UI:%0.1f |" + " AC:%0.1f AT:%0.1f |" + " VC:%0.1f VI:%0.1f VA:%0.1f |" + " SC:%0.1f SI:%0.1f SA:%0.1f |" + " CR:%0.1f IR:%0.1f AR:%0.1f", + __func__, severity_distance_AV, severity_distance_PR, + severity_distance_UI, severity_distance_AC, severity_distance_AT, + severity_distance_VC, severity_distance_VI, severity_distance_VA, + severity_distance_SC, severity_distance_SI, severity_distance_SA, + severity_distance_CR, severity_distance_IR, + severity_distance_AR); + break; + } + + gchar *max_vec_expanded = cvss4_vector_expand (*max_vec); + g_debug ("%s: max_vec: %s", __func__, max_vec_expanded); + g_free (max_vec_expanded); + g_strfreev (max_vectors); + + *current_severity_distance_eq1 = + severity_distance_AV + severity_distance_PR + severity_distance_UI; + *current_severity_distance_eq2 = severity_distance_AC + severity_distance_AT; + *current_severity_distance_eq3eq6 = + severity_distance_VC + severity_distance_VI + severity_distance_VA + + severity_distance_CR + severity_distance_IR + severity_distance_AR; + *current_severity_distance_eq4 = + severity_distance_SC + severity_distance_SI + severity_distance_SA; + *current_severity_distance_eq5 = 0.0; +} + +/** + * @brief Get the max severity values for a CVSS 4.0 macrovector + * + * The values are the MaxSeverity values already multiplied by 0.1 + * + * @param[in] macrovector The macrovector to get the max severity values for + * @param[out] max_severity_eq1 Max severity for EQ1 + * @param[out] max_severity_eq2 Max severity for EQ2 + * @param[out] max_severity_eq3eq6 Max severity for EQ3 and EQ6 + * @param[out] max_severity_eq4 Max severity for EQ4 + */ +static void +cvss4_max_severities (const char *macrovector, double *max_severity_eq1, + double *max_severity_eq2, double *max_severity_eq3eq6, + double *max_severity_eq4) +{ + switch (macrovector[0]) + { + case '0': + *max_severity_eq1 = 0.1; + break; + case '1': + *max_severity_eq1 = 0.4; + break; + case '2': + *max_severity_eq1 = 0.5; + break; + default: + *max_severity_eq1 = -99.0; + } + + switch (macrovector[1]) + { + case '0': + *max_severity_eq2 = 0.1; + break; + case '1': + *max_severity_eq2 = 0.2; + break; + default: + *max_severity_eq2 = -99.0; + } + + switch (macrovector[2]) + { + case '0': + if (macrovector[5] == '0') + *max_severity_eq3eq6 = 0.7; + else + *max_severity_eq3eq6 = 0.6; + break; + case '1': + *max_severity_eq3eq6 = 0.8; + break; + case '2': + *max_severity_eq3eq6 = 1.0; + break; + default: + *max_severity_eq3eq6 = -99.0; + } + + switch (macrovector[3]) + { + case '0': + *max_severity_eq4 = 0.6; + break; + case '1': + *max_severity_eq4 = 0.5; + break; + case '2': + *max_severity_eq4 = 0.4; + break; + default: + *max_severity_eq4 = -99.0; + } +} + +/** + * @brief Calculate CVSS 4.0 Score. + * + * @param cvss_str Vector from which to compute score, without prefix. + * + * @return CVSS score, or -1 on error. + */ +static double +get_cvss_score_from_metrics_v4 (const char *cvss_str) +{ + char *vec = NULL; + char *macrovector = NULL; + + double available_distance_eq1, available_distance_eq2; + double available_distance_eq3eq6; + double available_distance_eq4, available_distance_eq5; + + double current_severity_distance_eq1, current_severity_distance_eq2; + double current_severity_distance_eq3eq6; + double current_severity_distance_eq4, current_severity_distance_eq5; + + double max_severity_eq1, max_severity_eq2, max_severity_eq3eq6; + double max_severity_eq4; + + double mean_distance, value; + + int n_existing_lower = 0; + + // Convert vector to simplified, enum-indexed string + g_debug ("%s: CVSS string: %s", __func__, cvss_str); + vec = simplify_cvss4_vector (cvss_str); + g_debug ("%s: simplified vector: %s", __func__, vec); + if (vec == NULL) + return -1.0; + + // Calculate macrovector + macrovector = cvss4_macrovector (vec); + value = cvss4_macrovector_score (macrovector); + g_debug ("%s: macrovector: %s, value: %0.1f", __func__, macrovector, value); + if (macrovector == NULL || value == -1.0) + { + g_free (vec); + return -1.0; + } + + // Calculate maximum distances + cvss4_maximal_scoring_differences ( + macrovector, &available_distance_eq1, &available_distance_eq2, + &available_distance_eq3eq6, &available_distance_eq4, + &available_distance_eq5); + g_debug ("%s: maximal scoring diffs:" + " EQ1:%0.1f EQ2:%0.1f EQ3+EQ6:%0.1f EQ5:%0.1f EQ6:%0.1f", + __func__, available_distance_eq1, available_distance_eq2, + available_distance_eq3eq6, available_distance_eq4, + available_distance_eq5); + + // Calculate current severity distances + cvss4_current_severity_distances ( + vec, macrovector, ¤t_severity_distance_eq1, + ¤t_severity_distance_eq2, ¤t_severity_distance_eq3eq6, + ¤t_severity_distance_eq4, ¤t_severity_distance_eq5); + + g_debug ("%s: current severity distances:" + "EQ1:%0.1f EQ2:%0.1f EQ3+EQ6:%0.1f EQ4:%0.1f EQ5:%0.1f", + __func__, current_severity_distance_eq1, + current_severity_distance_eq2, current_severity_distance_eq3eq6, + current_severity_distance_eq4, current_severity_distance_eq5); + + // Get MaxSeverity + cvss4_max_severities (macrovector, &max_severity_eq1, &max_severity_eq2, + &max_severity_eq3eq6, &max_severity_eq4); + + g_free (vec); + g_free (macrovector); + + // Calculate mean distances + mean_distance = 0.0; + if (available_distance_eq1 >= 0.0) + { + n_existing_lower++; + double percent_to_next_severity = + (current_severity_distance_eq1) / max_severity_eq1; + mean_distance += (available_distance_eq1 * percent_to_next_severity); + } + + if (available_distance_eq2 >= 0.0) + { + n_existing_lower++; + double percent_to_next_severity = + (current_severity_distance_eq2) / max_severity_eq2; + mean_distance += (available_distance_eq2 * percent_to_next_severity); + } + + if (available_distance_eq3eq6 >= 0.0) + { + n_existing_lower++; + double percent_to_next_severity = + (current_severity_distance_eq3eq6) / max_severity_eq3eq6; + mean_distance += (available_distance_eq3eq6 * percent_to_next_severity); + } + + if (available_distance_eq4 >= 0.0) + { + n_existing_lower++; + double percent_to_next_severity = + (current_severity_distance_eq4) / max_severity_eq4; + mean_distance += (available_distance_eq4 * percent_to_next_severity); + } + + if (available_distance_eq5 >= 0.0) + { + // For EQ5 the percentage is always 0 + n_existing_lower++; + } + + mean_distance = mean_distance / n_existing_lower; + + // Get and adjust macrovector score + value = value - mean_distance; + if (value < 0.0) + value = 0.0; + else if (value > 10.0) + value = 10.0; + + return round (value * 10.0) / 10.0; +} diff --git a/base/cvss_tests.c b/base/cvss_tests.c index 8060b359..caad53c2 100644 --- a/base/cvss_tests.c +++ b/base/cvss_tests.c @@ -77,6 +77,75 @@ Ensure (cvss, get_cvss_score_from_base_metrics_succeeds_v3) CHECK ("CVSS:3.1/av:n/ac:l/pr:n/ui:n/s:u/c:h/i:l/a:n", 8.2); } +Ensure (cvss, get_cvss_score_from_base_metrics_succeeds_v4) +{ + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + 10.0); + + /* Trailing separator. */ + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/", + 10.0); + + /* We support any case in metrics. */ + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + 10.0); + + /* Test various base vectors */ + CHECK ("CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:A/VC:H/VI:L/VA:N/SC:N/SI:L/SA:H", + 6.9); + + CHECK ("CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:L/VI:L/VA:L/SC:H/SI:H/SA:H", + 5.8); + + CHECK ("CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:L/VI:L/VA:L/SC:H/SI:H/SA:H/" + "MSI:S", + 7.0); + + CHECK ("CVSS:4.0/AV:P/AC:H/AT:P/PR:H/UI:A/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N", + 1.0); + + CHECK ("CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:A/VC:L/VI:L/VA:H/SC:N/SI:N/SA:N", + 4.4); + + /* Test cases for picking one of two macrovector scores + * when EQ3 and EQ6 of the macrovector are 0*/ + + CHECK ("CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:A/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N", + 7.1); + + CHECK ("CVSS:4.0/AV:A/AC:H/AT:P/PR:H/UI:A/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N", + 5.3); + + /* Test vectors with Requirements metric */ + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:L/SI:L/SA:L/" + "CR:M", + 8.0); + + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:L/SI:L/SA:L/" + "IR:L", + 8.7); + + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N/" + "CR:M/IR:L/AR:L", + 8.9); + + /* Test vectors with Exploit Maturity metric */ + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:L/SI:L/SA:L/E:P", + 7.8); + + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:L/SI:L/SA:L/E:U", + 6.7); + + /* Provider Urgency is a special case with longer values */ + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/" + "U:Amber", + 10.0); + + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/" + "U:X", + 10.0); +} + Ensure (cvss, get_cvss_score_from_base_metrics_fails) { CHECK ("", -1.0); @@ -113,6 +182,52 @@ Ensure (cvss, get_cvss_score_from_base_metrics_fails) CHECK ("cvss:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", -1.0); } +Ensure (cvss, get_cvss_score_from_base_metrics_fails_v4) +{ + /* No metrics given */ + CHECK ("CVSS:4.0", -1.0); + + /* Metric name is invalid */ + CHECK ("CVSS:4.0/AXXXX:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + -1.0); + + /* Metric name is missing */ + CHECK ("CVSS:4.0/:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", -1.0); + + /* Metric value is invalid */ + CHECK ("CVSS:4.0/AV:Y/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + -1.0); + + /* Metric value is too long */ + CHECK ("CVSS:4.0/AV:XYZ/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + -1.0); + + /* Metric value is missing */ + CHECK ("CVSS:4.0/AV:/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + -1.0); + + CHECK ("CVSS:4.0/AV/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", -1.0); + + /* Duplicate Metric */ + CHECK ("CVSS:4.0/AV:N/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + -1.0); + + /* Missing mandatory metrics */ + CHECK ("CVSS:4.0/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", -1.0); + + CHECK ("CVSS:4.0/AV:N/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", -1.0); + + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H", -1.0); + + /* Version must be uppercase. */ + CHECK ("cvss:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H", + -1.0); + + /* Invalid Provider Urgency */ + CHECK ("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/U:R", + -1.0); +} + Ensure (cvss, get_cvss_score_from_base_metrics_all_in_feed_match) { /* Every distinct CVSSv2 vector and score shipped in the feed nvdcve files. */ @@ -1752,6 +1867,10 @@ main (int argc, char **argv) add_test_with_context (suite, cvss, get_cvss_score_from_base_metrics_fails); add_test_with_context (suite, cvss, get_cvss_score_from_base_metrics_succeeds_v3); + add_test_with_context (suite, cvss, + get_cvss_score_from_base_metrics_succeeds_v4); + add_test_with_context (suite, cvss, + get_cvss_score_from_base_metrics_fails_v4); add_test_with_context (suite, cvss, get_cvss_score_from_base_metrics_all_in_feed_match);