-
Notifications
You must be signed in to change notification settings - Fork 9
feat: add test 6.2.19 for CSAF 2.0 #614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
peinjoh
wants to merge
21
commits into
main
Choose a base branch
from
feat/test-6-2-19-for-csaf-20
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
aeb128c
feat: add test 6.2.19 for CSAF 2.0
peinjoh a00871e
fix: fmt and clippy
peinjoh f9e6fd0
fix: clippy
peinjoh cac8063
fix: cr comments
peinjoh 2ea6e0e
fix: cr comments
peinjoh 553d574
fix as_str usage on product_id
tziemek daf85dc
rework errors
tziemek 68958f2
add cvss_4
tziemek 5cdf245
fix some bugs
tziemek 021dcc4
fix json
tziemek e85f112
Merge branch 'main' into feat/test-6-2-19-for-csaf-20
peinjoh e4724f5
fix: correct test
peinjoh a375090
fix: correct test data
peinjoh 41d92c7
feat: add prop score zero suppl test cases for CSAF 2.0
peinjoh 45ee72a
feat: add prop score zero suppl test cases for CSAF 2.1
peinjoh 4019cac
feat: refactor CSAF 2.0 to return both erros
peinjoh ffe668d
feat: more 2.0 tests
peinjoh c85ccfe
feat: fix for CSAF 2.1
peinjoh a632cc4
fix: align test naming
peinjoh c39ecaa
fix: wrong reported result
peinjoh 31b3d26
Merge branch 'main' into feat/test-6-2-19-for-csaf-20
peinjoh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| use std::str::FromStr; | ||
|
|
||
| use crate::csaf_traits::{ | ||
| ContentTrait, CsafTrait, MetricTrait, ProductStatusAndPath, ProductStatusGroup, ProductStatusGroupMap, | ||
| VulnerabilityTrait, | ||
| }; | ||
| use crate::cvss::{deserialize_cvss, is_zero_score}; | ||
| use crate::validation::ValidationError; | ||
| use cvss_rs::Cvss; | ||
| use cvss_rs::v2_0::{CvssV2, TargetDistribution}; | ||
| use cvss_rs::v3::{CvssV3, Impact}; | ||
|
|
||
| fn create_cvss_for_fixed_products_error( | ||
| product_id: &str, | ||
| statuses: &[ProductStatusAndPath], | ||
| path: &str, | ||
| ) -> ValidationError { | ||
| let status_list: Vec<String> = statuses.iter().map(|s| s.status.to_string()).collect(); | ||
| ValidationError { | ||
| message: format!( | ||
| "Product '{}' is listed as fixed (status(es): '{}') but has a CVSS environmental score that is not 0.0", | ||
| product_id, | ||
| status_list.join(", ") | ||
| ), | ||
| instance_path: path.to_string(), | ||
| } | ||
| } | ||
|
|
||
| /// Checks if a CVSS v2 score has an environmental score of 0. | ||
| fn cvss_v2_has_env_score_zero(cvss_v2: CvssV2) -> bool { | ||
| let has_target_distribution_none = | ||
| |cvss_v2: &CvssV2| -> bool { matches!(cvss_v2.target_distribution, Some(TargetDistribution::None)) }; | ||
|
|
||
| // check env score provided in json | ||
| if let Some(env_score) = cvss_v2.environmental_score | ||
| && is_zero_score(env_score) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| // check if json contains prop that would set env score to zero | ||
| if has_target_distribution_none(&cvss_v2) { | ||
| return true; | ||
| } | ||
|
|
||
| // generate cvss object from vector | ||
| match CvssV2::from_str(&cvss_v2.vector_string) { | ||
| Err(_) => false, // #409 nondeterminable | ||
| // check if vector contains prop that would set env score to zero | ||
| Ok(from_vector) => has_target_distribution_none(&from_vector), | ||
| } | ||
| } | ||
|
|
||
| /// Checks if a CVSS v3 score has an environmental score of 0. | ||
| fn cvss_v3_has_env_score_zero(cvss_v3: CvssV3) -> bool { | ||
| let has_all_modified_impacts_none = |cvss_v3: &CvssV3| -> bool { | ||
| matches!( | ||
| ( | ||
| &cvss_v3.modified_availability_impact, | ||
| &cvss_v3.modified_confidentiality_impact, | ||
| &cvss_v3.modified_integrity_impact | ||
| ), | ||
| (Some(Impact::None), Some(Impact::None), Some(Impact::None)) | ||
| ) | ||
| }; | ||
|
|
||
| // check env score provided in json | ||
| if let Some(env_score) = cvss_v3.environmental_score | ||
| && is_zero_score(env_score) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| // check if json contains prop that would set env score to zero | ||
| if has_all_modified_impacts_none(&cvss_v3) { | ||
| return true; | ||
| } | ||
|
|
||
| // generate cvss object from vector | ||
| match CvssV3::from_str(&cvss_v3.vector_string) { | ||
| Err(_) => false, // #409 nondeterminable | ||
| // check if vector contains prop that would set env score to zero | ||
| Ok(from_vector) => has_all_modified_impacts_none(&from_vector), | ||
| } | ||
| } | ||
|
peinjoh marked this conversation as resolved.
|
||
|
|
||
| /// Returns true if all CVSS scores in this content have environmental score of 0. | ||
| /// If both v2 and v3 are present, both must have an environmental score of 0. | ||
| fn content_has_all_cvss_env_score_zero(content: &impl ContentTrait) -> bool { | ||
| // check if cvss_v2 prop is set | ||
|
peinjoh marked this conversation as resolved.
|
||
| if let Some(cvss_v2) = content.get_cvss_v2() { | ||
| // deserialize cvss, we only care about result, not errors | ||
| let Some(deserialized) = deserialize_cvss(cvss_v2, "", &mut None) else { | ||
| // TODO: Nondeterminable #409, could not deserialize | ||
| return false; | ||
| }; | ||
| let v2_is_zero = match deserialized { | ||
| Cvss::V2(v2) => cvss_v2_has_env_score_zero(v2), | ||
| // TODO: Nondeterminable #409 - deserialized into wrong version | ||
| _ => false, | ||
| }; | ||
| if !v2_is_zero { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // check if the cvss_v3 prop is set | ||
| if let Some(cvss_v3) = content.get_cvss_v3() { | ||
| // deserialize cvss, we only care about result, not errors | ||
| let Some(deserialized) = deserialize_cvss(cvss_v3, "", &mut None) else { | ||
| // TODO: Nondeterminable #409, could not deserialize | ||
| return false; | ||
| }; | ||
| let v3_is_zero = match deserialized { | ||
| Cvss::V3_0(v3) | Cvss::V3_1(v3) => cvss_v3_has_env_score_zero(v3), | ||
| // TODO: Nondeterminable #409 - deserialized into wrong version | ||
| _ => false, | ||
| }; | ||
| if !v3_is_zero { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // true if there are no CVSS scores, or all present CVSS scores have env score of zero | ||
| true | ||
| } | ||
|
|
||
| /// 6.2.19 CVSS for Fixed Products | ||
| /// | ||
| /// For each item in the fixed products group (first_fixed and fixed) it MUST be tested that | ||
| /// a CVSS applying to this product has an environmental score of 0. | ||
| /// The test SHALL pass if none of the Product IDs listed within product status fixed or | ||
| /// first_fixed is found in products of any item of the scores element. | ||
| pub fn test_6_2_19_cvss_for_fixed_products(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> { | ||
| let mut errors: Option<Vec<ValidationError>> = None; | ||
| for (v_i, vuln) in doc.get_vulnerabilities().iter().enumerate() { | ||
| // collect fixed product IDs using the aggregation map | ||
| let status_map = match vuln.get_product_status() { | ||
| Some(product_status) => ProductStatusGroupMap::from(product_status), | ||
| // there are no product statuses | ||
| None => continue, | ||
| }; | ||
| let fixed_products = match status_map.get(&ProductStatusGroup::Fixed) { | ||
| Some(products) => products, | ||
| // there are no products with status group fixed | ||
| None => continue, | ||
| }; | ||
|
|
||
| // check each metric/score | ||
| if let Some(metrics) = vuln.get_metrics() { | ||
| let metrics_path = vuln.get_metrics_path(); | ||
| for (m_i, metric) in metrics.iter().enumerate() { | ||
| let content = metric.get_content(); | ||
| for (p_i, product_id) in metric.get_products().enumerate() { | ||
| // if the metric/score is relevant to a product | ||
| if let Some(statuses) = fixed_products.get(product_id) { | ||
| // and the product does not have an env score of zero, generate an error | ||
| if !content_has_all_cvss_env_score_zero(content) { | ||
| errors | ||
| .get_or_insert_default() | ||
| .push(create_cvss_for_fixed_products_error( | ||
| product_id, | ||
| statuses, | ||
| &format!("/vulnerabilities/{v_i}/{metrics_path}/{m_i}/products/{p_i}"), | ||
| )); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| errors.map_or(Ok(()), Err) | ||
| } | ||
|
|
||
| crate::test_validation::impl_validator!(ValidatorForTest6_2_19, test_6_2_19_cvss_for_fixed_products); | ||
|
peinjoh marked this conversation as resolved.
|
||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use crate::csaf_traits::ProductStatus; | ||
| use crate::csaf2_0::testcases::TESTS_2_0; | ||
|
|
||
| #[test] | ||
| fn test_test_6_2_19() { | ||
| // Test data only contains two paths, so we can share the error messages | ||
| let err_fixed = Err(vec![create_cvss_for_fixed_products_error( | ||
| "CSAFPID-9080700", | ||
| &[ProductStatusAndPath { | ||
| status: ProductStatus::Fixed, | ||
| index: 0, | ||
| }], | ||
| "/vulnerabilities/0/scores/0/products/0", | ||
| )]); | ||
| let err_first_fixed = Err(vec![create_cvss_for_fixed_products_error( | ||
| "CSAFPID-9080700", | ||
| &[ProductStatusAndPath { | ||
| status: ProductStatus::FirstFixed, | ||
| index: 0, | ||
| }], | ||
| "/vulnerabilities/0/scores/0/products/0", | ||
| )]); | ||
|
|
||
| // Case 01: CVSS v3.1, no metric that sets to 0, status fixed | ||
| // Case 02: CVSS v3.1, JSON modifiedAvailabilityImpact is not set to None, status fixed | ||
| // Case 03: CVSS v2, JSON targetDistribution is not set to None, status fixed | ||
| // Case 04: CVSS v2, no metric that sets to 0, status fixed | ||
| // Case 05: CVSS v3.0, no metric that sets to 0, status first_fixed | ||
| // Case 06: CVSS v3.0, JSON modifiedAvailabilityImpact is not set to None, status fixed | ||
|
|
||
| // Case 11: CVSS v3.1, all modifiedImpact metrics are None in vector, status fixed | ||
| // Case 12: CVSS v3.1, all modifiedImpact metrics are None in JSON, status fixed | ||
| // Case 13: CVSS v2, targetDistribution is None in JSON, status fixed | ||
| // Case 14: CVSS v2, targetDistribution is None in vector, status fixed | ||
| // Case 15: CVSS v3.0, all modifiedImpact metrics are None in vector, status first_fixed | ||
| // Case 16: CVSS v3.1, all modifiedImpact metrics are None in JSON, status fixed | ||
| // Case 17: product status known_affected | ||
|
|
||
| TESTS_2_0.test_6_2_19.expect( | ||
| err_fixed.clone(), | ||
| err_fixed.clone(), | ||
| err_fixed.clone(), | ||
| err_fixed.clone(), | ||
| err_first_fixed, | ||
| err_fixed, | ||
| Ok(()), | ||
| Ok(()), | ||
| Ok(()), | ||
| Ok(()), | ||
| Ok(()), | ||
| Ok(()), | ||
| Ok(()), | ||
| ); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.