Skip to content

Commit 7735f81

Browse files
committed
Add validation for contradicting remediations in CSAF documents
Introduced test "6.1.35" to validate and detect contradicting remediation categories for the same product. Added traits and implementations to abstract remediation and vulnerability handling for easier extensibility across CSAF versions. Updated test infrastructure and presets to include the new validation logic.
1 parent b159259 commit 7735f81

File tree

10 files changed

+158
-22
lines changed

10 files changed

+158
-22
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::ops::Deref;
2+
use std::str::FromStr;
3+
use crate::csaf::csaf2_0::schema::{CommonSecurityAdvisoryFramework, Remediation, Vulnerability};
4+
use crate::csaf::csaf2_1::schema::CategoryOfTheRemediation as Remediation21;
5+
use crate::csaf::getter_traits::{CsafTrait, RemediationTrait, VulnerabilityTrait};
6+
7+
impl RemediationTrait for Remediation {
8+
fn get_category(&self) -> Remediation21 {
9+
// Categories are identical, so this should never fail
10+
Remediation21::from_str(self.category.to_string().as_str()).unwrap()
11+
}
12+
13+
fn get_product_ids(&self) -> Option<Vec<String>> {
14+
match &self.product_ids {
15+
None => None,
16+
Some(product_ids) => Some(product_ids.deref().iter().map(|x| x.to_string()).collect())
17+
}
18+
}
19+
}
20+
21+
impl VulnerabilityTrait for Vulnerability {
22+
type RemediationType = Remediation;
23+
24+
fn get_remediations(&self) -> Vec<Self::RemediationType> {
25+
self.remediations.clone()
26+
}
27+
}
28+
29+
impl CsafTrait for CommonSecurityAdvisoryFramework {
30+
type VulnerabilityType = Vulnerability;
31+
32+
fn get_vulnerabilities(&self) -> Vec<Self::VulnerabilityType> {
33+
self.vulnerabilities.clone()
34+
}
35+
}

csaf-lib/src/csaf/csaf2_0/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod loader;
22
mod product_helper;
33
pub mod schema;
44
pub mod validation;
5+
pub mod getter_implementations;

csaf-lib/src/csaf/csaf2_0/validation.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,25 @@ use crate::csaf::helpers::find_duplicates;
66

77
impl Validatable<CommonSecurityAdvisoryFramework> for CommonSecurityAdvisoryFramework {
88
fn presets(&self) -> HashMap<ValidationPreset, Vec<&str>> {
9+
let basic_tests = Vec::from(["6.1.1", "6.1.2"]);
10+
// More tests may be added in extend() here later
11+
let extended_tests: Vec<&str> = basic_tests.clone();
12+
// extended_tests.extend(["foo"].iter());
13+
let full_tests: Vec<&str> = extended_tests.clone();
14+
// full_tests.extend(["bar"].iter());
915
HashMap::from([
10-
(ValidationPreset::Basic, Vec::from(["6.1.1", "6.1.2"])),
11-
(ValidationPreset::Extended, Vec::from(["6.1.1", "6.1.2"])),
12-
(ValidationPreset::Full, Vec::from(["6.1.1", "6.1.2"])),
16+
(ValidationPreset::Basic, basic_tests),
17+
(ValidationPreset::Extended, extended_tests),
18+
(ValidationPreset::Full, full_tests),
1319
])
1420
}
1521

1622
fn tests(&self) -> HashMap<&str, Test<CommonSecurityAdvisoryFramework>> {
17-
HashMap::<&str, Test<CommonSecurityAdvisoryFramework>>::from([
18-
("6.1.1", test_6_01_01_missing_definition_of_product_id),
19-
("6.1.2", test_6_01_02_multiple_definition_of_product_id),
20-
]
21-
as [(&str, Test<CommonSecurityAdvisoryFramework>); 2])
23+
type CsafTest = Test<CommonSecurityAdvisoryFramework>;
24+
HashMap::from([
25+
("6.1.1", test_6_01_01_missing_definition_of_product_id as CsafTest),
26+
("6.1.2", test_6_01_02_multiple_definition_of_product_id as CsafTest),
27+
])
2228
}
2329

2430
fn doc(&self) -> &CommonSecurityAdvisoryFramework {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use crate::csaf::csaf2_1::schema::{CategoryOfTheRemediation, CommonSecurityAdvisoryFramework, Remediation, Vulnerability};
2+
use crate::csaf::getter_traits::{CsafTrait, RemediationTrait, VulnerabilityTrait};
3+
use std::ops::Deref;
4+
5+
impl RemediationTrait for Remediation {
6+
fn get_category(&self) -> CategoryOfTheRemediation {
7+
self.category.clone()
8+
}
9+
10+
fn get_product_ids(&self) -> Option<Vec<String>> {
11+
match &self.product_ids {
12+
None => None,
13+
Some(product_ids) => Some(product_ids.deref().iter().map(|x| x.to_string()).collect())
14+
}
15+
}
16+
}
17+
18+
impl VulnerabilityTrait for Vulnerability {
19+
type RemediationType = Remediation;
20+
21+
fn get_remediations(&self) -> Vec<Self::RemediationType> {
22+
self.remediations.clone()
23+
}
24+
}
25+
26+
impl CsafTrait for CommonSecurityAdvisoryFramework {
27+
type VulnerabilityType = Vulnerability;
28+
29+
fn get_vulnerabilities(&self) -> Vec<Self::VulnerabilityType> {
30+
self.vulnerabilities.clone()
31+
}
32+
}

csaf-lib/src/csaf/csaf2_1/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod loader;
22
mod product_helper;
33
pub mod schema;
44
pub mod validation;
5+
pub mod getter_implementations;

csaf-lib/src/csaf/csaf2_1/validation.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
use super::product_helper::*;
22
use super::schema::CommonSecurityAdvisoryFramework;
33
use crate::csaf::helpers::find_duplicates;
4-
use crate::csaf::validation::{Test, Validatable, ValidationPreset};
4+
use crate::csaf::validation::{test_6_01_35_contradicting_remediations, Test, Validatable, ValidationPreset};
55
use std::collections::{HashMap, HashSet};
66

77
impl Validatable<CommonSecurityAdvisoryFramework> for CommonSecurityAdvisoryFramework {
88
fn presets(&self) -> HashMap<ValidationPreset, Vec<&str>> {
9+
let basic_tests = Vec::from(["6.1.1", "6.1.2", "6.1.34", "6.1.35"]);
10+
// More tests may be added in extend() here later
11+
let extended_tests: Vec<&str> = basic_tests.clone();
12+
// extended_tests.extend(["foo"].iter());
13+
let full_tests: Vec<&str> = extended_tests.clone();
14+
// full_tests.extend(["bar"].iter());
915
HashMap::from([
10-
(
11-
ValidationPreset::Basic,
12-
Vec::from(["6.1.1", "6.1.2", "6.1.34"]),
13-
),
14-
(ValidationPreset::Extended, Vec::from(["6.1.1", "6.1.2"])),
15-
(ValidationPreset::Full, Vec::from(["6.1.1", "6.1.2"])),
16+
(ValidationPreset::Basic, basic_tests),
17+
(ValidationPreset::Extended, extended_tests),
18+
(ValidationPreset::Full, full_tests),
1619
])
1720
}
1821

1922
fn tests(&self) -> HashMap<&str, Test<CommonSecurityAdvisoryFramework>> {
20-
HashMap::<&str, Test<CommonSecurityAdvisoryFramework>>::from([
21-
("6.1.1", test_6_01_01_missing_definition_of_product_id),
22-
("6.1.2", test_6_01_02_multiple_definition_of_product_id),
23-
("6.1.34", test_6_01_34_branches_recursion_depth),
24-
]
25-
as [(&str, Test<CommonSecurityAdvisoryFramework>); 3])
23+
type CsafTest = Test<CommonSecurityAdvisoryFramework>;
24+
HashMap::from([
25+
("6.1.1", test_6_01_01_missing_definition_of_product_id as CsafTest),
26+
("6.1.2", test_6_01_02_multiple_definition_of_product_id as CsafTest),
27+
("6.1.34", test_6_01_34_branches_recursion_depth as CsafTest),
28+
("6.1.35", test_6_01_35_contradicting_remediations as CsafTest),
29+
])
2630
}
2731

2832
fn doc(&self) -> &CommonSecurityAdvisoryFramework {
@@ -82,6 +86,7 @@ mod tests {
8286
validation::test_6_01_02_multiple_definition_of_product_id,
8387
validation::test_6_01_34_branches_recursion_depth,
8488
};
89+
use crate::csaf::validation::test_6_01_35_contradicting_remediations;
8590

8691
#[test]
8792
fn test_test_6_01_01() {
@@ -111,4 +116,13 @@ mod tests {
111116
Err(String::from("Recursion depth too big: 31"))
112117
)
113118
}
119+
120+
#[test]
121+
fn test_test_6_01_35() {
122+
let doc = load_document("../csaf/csaf_2.1/test/validator/data/mandatory/oasis_csaf_tc-csaf_2_1-2024-6-1-35-01.json").unwrap();
123+
assert_eq!(
124+
test_6_01_35_contradicting_remediations(&doc),
125+
Err(String::from("Product CSAFPID-9080700 has contradicting remediations: no_fix_planned and vendor_fix"))
126+
)
127+
}
114128
}

csaf-lib/src/csaf/getter_traits.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use crate::csaf::csaf2_1::schema::CategoryOfTheRemediation;
2+
3+
pub trait CsafTrait {
4+
type VulnerabilityType: VulnerabilityTrait;
5+
6+
fn get_vulnerabilities(&self) -> Vec<Self::VulnerabilityType>;
7+
}
8+
9+
pub trait VulnerabilityTrait {
10+
type RemediationType: RemediationTrait;
11+
12+
fn get_remediations(&self) -> Vec<Self::RemediationType>;
13+
}
14+
15+
pub trait RemediationTrait {
16+
fn get_category(&self) -> CategoryOfTheRemediation;
17+
fn get_product_ids(&self) -> Option<Vec<String>>;
18+
}

csaf-lib/src/csaf/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod csaf2_0;
22
pub mod csaf2_1;
33
mod helpers;
44
pub mod validation;
5+
pub mod getter_traits;

csaf-lib/src/csaf/validation.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::collections::HashMap;
22
use std::str::FromStr;
3+
use crate::csaf::csaf2_1::schema::CategoryOfTheRemediation;
4+
use crate::csaf::getter_traits::{CsafTrait, RemediationTrait, VulnerabilityTrait};
35

46
pub enum ValidationError {}
57

@@ -83,3 +85,29 @@ pub fn validate_by_test<VersionedDocument>(
8385
println!("Test with ID {} is missing implementation", test_id);
8486
}
8587
}
88+
89+
pub fn test_6_01_35_contradicting_remediations(
90+
target: &impl CsafTrait,
91+
) -> Result<(), String> {
92+
for v in target.get_vulnerabilities().iter() {
93+
let mut product_categories: HashMap<String, CategoryOfTheRemediation> = HashMap::new();
94+
for r in v.get_remediations().iter() {
95+
if let Some(product_ids) = r.get_product_ids() {
96+
let category = r.get_category();
97+
for p in product_ids.iter() {
98+
if let Some(existing_category) = product_categories.get(p) {
99+
if existing_category != &category {
100+
return Err(format!(
101+
"Product {} has contradicting remediations: {} and {}",
102+
p, existing_category, category
103+
));
104+
}
105+
}
106+
product_categories.insert(p.clone(), category.clone());
107+
}
108+
}
109+
}
110+
}
111+
Ok(())
112+
}
113+

0 commit comments

Comments
 (0)