Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 111 additions & 69 deletions csaf-rs/src/validations/test_6_1_36.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ use crate::csaf_traits::{CsafTrait, ProductStatusGroup, ProductStatusGroupMap, R
use crate::schema::csaf2_1::schema::CategoryOfTheRemediation;
use crate::validation::{TestFinding, TestFindingData};

/// Remediation categories that conflict with the product status "affected".
const AFFECTED_PROHIBITED: &[CategoryOfTheRemediation] = &[CategoryOfTheRemediation::OptionalPatch];

/// Remediation categories that conflict with the product status "not affected".
const NOT_AFFECTED_CONFLICTS: &[CategoryOfTheRemediation] = &[
const NOT_AFFECTED_PROHIBITED: &[CategoryOfTheRemediation] = &[
CategoryOfTheRemediation::Workaround,
CategoryOfTheRemediation::Mitigation,
CategoryOfTheRemediation::VendorFix,
CategoryOfTheRemediation::NoneAvailable,
];

/// Remediation categories that conflict with "fixed" product statuses.
const FIXED_CONFLICTS: &[CategoryOfTheRemediation] = &[
const FIXED_PROHIBITED: &[CategoryOfTheRemediation] = &[
CategoryOfTheRemediation::NoneAvailable,
CategoryOfTheRemediation::FixPlanned,
CategoryOfTheRemediation::NoFixPlanned,
Expand All @@ -20,79 +23,69 @@ const FIXED_CONFLICTS: &[CategoryOfTheRemediation] = &[
CategoryOfTheRemediation::Workaround,
];

fn create_affected_conflict_error(
product_id: &str,
category: &CategoryOfTheRemediation,
v_i: usize,
r_i: usize,
) -> TestFinding {
TestFinding::Error(TestFindingData {
message: format!(
"Product {product_id} is listed as affected but has conflicting remediation category {category}"
),
instance_path: format!("/vulnerabilities/{v_i}/remediations/{r_i}"),
})
}
const PROHIBITED_COMBINATIONS: &[(ProductStatusGroup, &[CategoryOfTheRemediation])] = &[
(ProductStatusGroup::Affected, AFFECTED_PROHIBITED),
(ProductStatusGroup::NotAffected, NOT_AFFECTED_PROHIBITED),
(ProductStatusGroup::Fixed, FIXED_PROHIBITED),
];

fn create_not_affected_conflict_error(
fn create_prohibited_combination_error(
product_id: &str,
status_group: &ProductStatusGroup,
category: &CategoryOfTheRemediation,
v_i: usize,
r_i: usize,
vulnerability_index: usize,
remediation_index: usize,
) -> TestFinding {
TestFinding::Error(TestFindingData {
message: format!(
"Product {product_id} is listed as not affected but has conflicting remediation category {category}"
"Product {product_id} is listed as {status_group} but has prohibited remediation category: {category}"
),
instance_path: format!("/vulnerabilities/{v_i}/remediations/{r_i}"),
})
}

fn create_fixed_conflict_error(
product_id: &str,
category: &CategoryOfTheRemediation,
v_i: usize,
r_i: usize,
) -> TestFinding {
TestFinding::Error(TestFindingData {
message: format!("Product {product_id} is listed as fixed but has conflicting remediation category {category}"),
instance_path: format!("/vulnerabilities/{v_i}/remediations/{r_i}"),
instance_path: format!("/vulnerabilities/{vulnerability_index}/remediations/{remediation_index}"),
})
}

pub fn test_6_1_36_status_group_contradicting_remediation_categories(
doc: &impl CsafTrait,
) -> Result<(), Vec<TestFinding>> {
for (v_i, v) in doc.get_vulnerabilities().iter().enumerate() {
if let Some(product_status) = v.get_product_status() {
let mut errors = Vec::new();

for (vulnerability_index, vulnerability) in doc.get_vulnerabilities().iter().enumerate() {
if let Some(product_status) = vulnerability.get_product_status() {
// Group the product IDs by their corresponding status group
let status_map = ProductStatusGroupMap::from(product_status);

// Iterate over remediations
for (r_i, r) in v.get_remediations().iter().enumerate() {
// Only handle Remediations having product IDs associated
if let Some(product_ids) = r.get_all_product_ids(doc) {
for (remediation_index, remediation) in vulnerability.get_remediations().iter().enumerate() {
// Only handle remediations associated with products, directly or via product groups
if let Some(remediation_product_ids) = remediation.get_all_product_ids(doc) {
// Category of current remediation
let cat = r.get_category();
let category = remediation.get_category();

// Iterate over product IDs
for p in product_ids {
if status_map.contains(&ProductStatusGroup::Affected, &p)
&& cat == CategoryOfTheRemediation::OptionalPatch
{
return Err(vec![create_affected_conflict_error(&p, &cat, v_i, r_i)]);
}
if status_map.contains(&ProductStatusGroup::NotAffected, &p)
&& NOT_AFFECTED_CONFLICTS.contains(&cat)
{
return Err(vec![create_not_affected_conflict_error(&p, &cat, v_i, r_i)]);
}
if status_map.contains(&ProductStatusGroup::Fixed, &p) && FIXED_CONFLICTS.contains(&cat) {
return Err(vec![create_fixed_conflict_error(&p, &cat, v_i, r_i)]);
for product_id in remediation_product_ids {
for (status_group, prohibited_categories) in PROHIBITED_COMBINATIONS {
// If the product belongs to the current status group
// and the remediation category is prohibited for that group,
// add an error.
if status_map.contains(status_group, &product_id)
&& prohibited_categories.contains(&category)
{
errors.push(create_prohibited_combination_error(
&product_id,
status_group,
&category,
vulnerability_index,
remediation_index,
));
}
}
}
}
}
}
}
Ok(())

if errors.is_empty() { Ok(()) } else { Err(errors) }
}

crate::test_validation::impl_validator!(
Expand All @@ -109,32 +102,81 @@ mod tests {

#[test]
fn test_test_6_1_36() {
// Only CSAF 2.1 has this test with 8 test cases (4 error cases, 4 success cases)
TESTS_2_1.test_6_1_36.expect(ExpectedResults {
case_01: Err(vec![create_not_affected_conflict_error(
"CSAFPID-9080700",
&CategoryOfTheRemediation::VendorFix,
0,
0,
)]),
case_02: Err(vec![create_fixed_conflict_error(
let case_01 = Err(vec![create_prohibited_combination_error(
"CSAFPID-9080700",
&ProductStatusGroup::NotAffected,
&CategoryOfTheRemediation::VendorFix,
0,
0,
)]);

let case_02 = Err(vec![
create_prohibited_combination_error(
"CSAFPID-9080703",
&ProductStatusGroup::Fixed,
&CategoryOfTheRemediation::NoneAvailable,
0,
0,
)]),
case_03: Err(vec![create_affected_conflict_error(
),
create_prohibited_combination_error(
"CSAFPID-9080700",
&CategoryOfTheRemediation::OptionalPatch,
&ProductStatusGroup::Fixed,
&CategoryOfTheRemediation::Mitigation,
0,
1,
),
create_prohibited_combination_error(
"CSAFPID-9080701",
&ProductStatusGroup::Fixed,
&CategoryOfTheRemediation::Mitigation,
0,
)]),
case_04: Err(vec![create_fixed_conflict_error(
"CSAFPID-9080700",
&CategoryOfTheRemediation::NoFixPlanned,
1,
),
create_prohibited_combination_error(
"CSAFPID-9080702",
&ProductStatusGroup::Fixed,
&CategoryOfTheRemediation::Mitigation,
0,
1,
),
create_prohibited_combination_error(
"CSAFPID-9080701",
&ProductStatusGroup::Fixed,
&CategoryOfTheRemediation::VendorFix,
0,
)]),
2,
),
create_prohibited_combination_error(
"CSAFPID-9080702",
&ProductStatusGroup::Fixed,
&CategoryOfTheRemediation::VendorFix,
0,
2,
),
]);

let case_03 = Err(vec![create_prohibited_combination_error(
"CSAFPID-9080700",
&ProductStatusGroup::Affected,
&CategoryOfTheRemediation::OptionalPatch,
0,
0,
)]);

let case_04 = Err(vec![create_prohibited_combination_error(
"CSAFPID-9080700",
&ProductStatusGroup::Fixed,
&CategoryOfTheRemediation::NoFixPlanned,
0,
0,
)]);

// Only CSAF 2.1 has this test with 8 test cases (4 error cases, 4 success cases)
TESTS_2_1.test_6_1_36.expect(ExpectedResults {
case_01,
case_02,
case_03,
case_04,
case_11: Ok(()),
case_12: Ok(()),
case_13: Ok(()),
Expand Down
Loading