Rename cve_utils to vuln_utils and abstract CVE-specific code
- Renamed cve_utils library to vuln_utils throughout the codebase
- Renamed cve_utils directory to vuln_utils using git mv
- Abstracted all CVE-specific code into src/providers/cve directory
- Implemented factory design pattern for vulnerability providers
- Added provider-agnostic CLI with --provider flag (defaults to 'cve')
- Changed --cve to --id for vulnerability IDs (kept --cve as hidden legacy support)
- Updated all imports and references from cve_utils to vuln_utils
- Removed trailing whitespaces from modified files
This refactoring prepares the codebase to support additional vulnerability
providers by implementing the VulnerabilityProvider trait.
Signed-off-by: Sasha Levin <sashal@kernel.org>
diff --git a/tools/Cargo.toml b/tools/Cargo.toml
index c18b6df..55a80f0 100644
--- a/tools/Cargo.toml
+++ b/tools/Cargo.toml
@@ -63,43 +63,43 @@
# CVE Utils binaries
[[bin]]
name = "cve_search"
-path = "cve_utils/src/bin/cve_search.rs"
+path = "vuln_utils/src/bin/cve_search.rs"
[[bin]]
name = "cve_reject"
-path = "cve_utils/src/bin/cve_reject.rs"
+path = "vuln_utils/src/bin/cve_reject.rs"
[[bin]]
name = "cve_update"
-path = "cve_utils/src/bin/cve_update.rs"
+path = "vuln_utils/src/bin/cve_update.rs"
[[bin]]
name = "cve_publish"
-path = "cve_utils/src/bin/cve_publish.rs"
+path = "vuln_utils/src/bin/cve_publish.rs"
[[bin]]
name = "cve_create"
-path = "cve_utils/src/bin/cve_create.rs"
+path = "vuln_utils/src/bin/cve_create.rs"
[[bin]]
name = "cve_review"
-path = "cve_utils/src/bin/cve_review.rs"
+path = "vuln_utils/src/bin/cve_review.rs"
[[bin]]
name = "update_dyad"
-path = "cve_utils/src/bin/update_dyad.rs"
+path = "vuln_utils/src/bin/update_dyad.rs"
[[bin]]
name = "strak"
-path = "cve_utils/src/bin/strak.rs"
+path = "vuln_utils/src/bin/strak.rs"
[[bin]]
name = "score"
-path = "cve_utils/src/bin/score.rs"
+path = "vuln_utils/src/bin/score.rs"
[[bin]]
name = "cve_stats"
-path = "cve_utils/src/bin/cve_stats.rs"
+path = "vuln_utils/src/bin/cve_stats.rs"
[[bin]]
name = "cve_classifier"
@@ -117,5 +117,5 @@
required-features = []
[lib]
-name = "cve_utils"
-path = "cve_utils/src/lib.rs"
+name = "vuln_utils"
+path = "vuln_utils/src/lib.rs"
diff --git a/tools/bippy/src/commands/json.rs b/tools/bippy/src/commands/json.rs
index 925b256..e4ccc3f 100644
--- a/tools/bippy/src/commands/json.rs
+++ b/tools/bippy/src/commands/json.rs
@@ -2,31 +2,23 @@
//
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
-use anyhow::{Context, Result};
-use serde::Serialize;
-use serde_json::ser::{PrettyFormatter, Serializer};
-use std::collections::HashSet;
+use anyhow::Result;
+use crate::models::DyadEntry;
+use crate::providers::{ProviderFactory, VulnerabilityRecordParams};
-use crate::models::{
- AffectedProduct, CnaData, Containers, CpeApplicability, CpeNodes, CveMetadata, CveRecord,
- Description, DyadEntry, Generator, ProviderMetadata, Reference,
-};
-use crate::utils::{
- determine_default_status, generate_cpe_ranges, generate_git_ranges, generate_version_ranges,
- read_uuid,
-};
-
-/// Parameters for generating a JSON CVE record
-pub struct CveRecordParams<'a> {
- /// CVE identifier (e.g., "CVE-2023-12345")
- pub cve_number: &'a str,
+/// Parameters for generating a JSON vulnerability record
+pub struct VulnRecordParams<'a> {
+ /// Provider type (e.g., "cve", "gsd", "euvd")
+ pub provider_type: &'a str,
+ /// Vulnerability identifier
+ pub vuln_id: &'a str,
/// Full Git SHA of the commit that fixes the vulnerability
pub git_sha_full: &'a str,
/// Subject line of the commit
pub commit_subject: &'a str,
- /// Name of the user creating the CVE
+ /// Name of the user creating the record
pub user_name: &'a str,
- /// Email of the user creating the CVE
+ /// Email of the user creating the record
pub user_email: &'a str,
/// Dyad entries containing vulnerability and fix information
pub dyad_entries: Vec<DyadEntry>,
@@ -42,411 +34,21 @@
pub affected_files: &'a Vec<String>,
}
-/// Get UUID information
-fn get_uuid() -> Result<String> {
- // Get vulns directory using cve_utils
- let vulns_dir =
- cve_utils::find_vulns_dir().with_context(|| "Failed to find vulns directory")?;
-
- // Get the script directory from vulns directory
- let script_dir = vulns_dir.join("scripts");
- if !script_dir.exists() {
- return Err(anyhow::anyhow!(
- "Scripts directory not found at {}",
- script_dir.display()
- ));
- }
-
- // Read the UUID from the linux.uuid file
- let uuid = read_uuid(&script_dir).with_context(|| "Failed to read UUID")?;
-
- Ok(uuid)
-}
-
-/// Prepare dyad entries and affected files
-fn prepare_vulnerability_data(
- git_sha_full: &str,
- in_dyad_entries: &[DyadEntry],
-) -> Result<Vec<DyadEntry>> {
- // Clone dyad entries since we might need to modify them
- let mut dyad_entries = in_dyad_entries.to_vec();
-
- // If no entries were created, use the fix commit as a fallback
- if dyad_entries.is_empty() {
- // Create a dummy entry using the fix commit
- if let Ok(entry) = DyadEntry::from_str(&format!("0:0:0:{git_sha_full}")) {
- dyad_entries.push(entry);
- }
- }
-
- Ok(dyad_entries)
-}
-
-/// Create affected products (kernel and git)
-fn create_affected_products(
- dyad_entries: &[DyadEntry],
- affected_files: Vec<String>,
-) -> (AffectedProduct, AffectedProduct, Vec<CpeNodes>) {
- // Determine default status
- let default_status = determine_default_status(dyad_entries);
-
- // Generate version ranges for kernel product
- let kernel_versions = generate_version_ranges(dyad_entries, default_status);
- let kernel_product = AffectedProduct {
- product: "Linux".to_string(),
- vendor: "Linux".to_string(),
- default_status: default_status.to_string(),
- repo: "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git".to_string(),
- program_files: affected_files.clone(),
- versions: kernel_versions,
+/// Generate a JSON for the vulnerability record
+pub fn generate_json(params: &VulnRecordParams) -> Result<String> {
+ let provider = ProviderFactory::create(params.provider_type)?;
+ let vuln_params = VulnerabilityRecordParams {
+ vuln_id: params.vuln_id,
+ git_sha_full: params.git_sha_full,
+ commit_subject: params.commit_subject,
+ user_name: params.user_name,
+ user_email: params.user_email,
+ dyad_entries: params.dyad_entries.clone(),
+ script_name: params.script_name,
+ script_version: params.script_version,
+ additional_references: params.additional_references,
+ commit_text: params.commit_text,
+ affected_files: params.affected_files,
};
-
- // Generate git ranges for git product
- let git_versions = generate_git_ranges(dyad_entries);
- let git_product = AffectedProduct {
- product: "Linux".to_string(),
- vendor: "Linux".to_string(),
- default_status: "unaffected".to_string(),
- repo: "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git".to_string(),
- program_files: affected_files,
- versions: git_versions,
- };
-
- // Generate CPE ranges
- let cpe_nodes = generate_cpe_ranges(dyad_entries);
-
- (kernel_product, git_product, cpe_nodes)
-}
-
-/// Generate references from dyad entries and additional references
-fn generate_references(
- dyad_entries: &[DyadEntry],
- additional_references: &[String],
- git_sha_full: &str,
-) -> Vec<Reference> {
- let mut references = Vec::new();
- let mut seen_refs: HashSet<String> = HashSet::new();
-
- // Add references for all entries
- for entry in dyad_entries {
- // Add fixed commit reference if available
- if !entry.fixed.is_empty() {
- let url = format!("https://git.kernel.org/stable/c/{}", entry.fixed.git_id());
- if !seen_refs.contains(&url) {
- seen_refs.insert(url.clone());
- references.push(Reference { url });
- }
- }
- }
-
- // Add any additional references from the reference file
- for url in additional_references {
- if !seen_refs.contains(url) {
- seen_refs.insert(url.clone());
- references.push(Reference { url: url.clone() });
- }
- }
-
- // If no references were found, add the main fix commit
- if references.is_empty() {
- let main_fix_url = format!("https://git.kernel.org/stable/c/{git_sha_full}");
- references.push(Reference { url: main_fix_url });
- }
-
- references
-}
-
-/// Process commit description text and handle truncation
-fn process_description(commit_text: &str) -> String {
- // Truncate description to 3982 characters (CVE backend limit) if needed
- let max_length = 3982; // CVE backend limit
-
- if commit_text.len() <= max_length {
- // If already under the limit, just ensure no trailing newline
- return commit_text.trim_end().to_string();
- }
-
- // Get the truncated text limited to max_length
- let truncated = &commit_text[..max_length];
-
- // Special case: if only over by a trailing newline, just trim it
- if commit_text.len() == max_length + 1 && commit_text.ends_with('\n') {
- truncated.to_string()
- } else {
- // Add truncation marker, with proper newline handling
- let separator = if truncated.ends_with('\n') { "" } else { "\n" };
- format!("{truncated}{separator}---truncated---")
- }
-}
-
-/// Parameters for creating a CVE record
-struct CveRecordCreationParams<'a> {
- uuid: String,
- cve_number: &'a str,
- commit_subject: &'a str,
- user_email: &'a str,
- script_name: &'a str,
- script_version: &'a str,
- truncated_description: String,
- kernel_product: AffectedProduct,
- git_product: AffectedProduct,
- cpe_nodes: Vec<CpeNodes>,
- references: Vec<Reference>,
-}
-
-/// Create the CVE record structure
-fn create_cve_record(params: CveRecordCreationParams) -> CveRecord {
- CveRecord {
- containers: Containers {
- cna: CnaData {
- provider_metadata: ProviderMetadata {
- org_id: params.uuid.clone(),
- },
- descriptions: vec![Description {
- lang: "en".to_string(),
- value: params.truncated_description,
- }],
- affected: vec![params.git_product, params.kernel_product],
- cpe_applicability: vec![CpeApplicability {
- nodes: params.cpe_nodes,
- }],
- references: params.references,
- title: params.commit_subject.to_string(),
- x_generator: Generator {
- engine: format!("{}-{}", params.script_name, params.script_version),
- },
- },
- },
- cve_metadata: CveMetadata {
- assigner_org_id: params.uuid,
- cve_id: params.cve_number.to_string(),
- requester_user_id: params.user_email.to_string(),
- serial: "1".to_string(),
- state: "PUBLISHED".to_string(),
- },
- data_type: "CVE_RECORD".to_string(),
- data_version: "5.0".to_string(),
- }
-}
-
-/// Serialize the CVE record to JSON
-fn serialize_cve_record(cve_record: &CveRecord) -> Result<String> {
- // Use a custom formatter with 3-space indentation
- let formatter = PrettyFormatter::with_indent(b" ");
- let mut output = Vec::new();
- let mut serializer = Serializer::with_formatter(&mut output, formatter);
-
- cve_record
- .serialize(&mut serializer)
- .map_err(|e| anyhow::anyhow!("Error serializing JSON: {e}"))?;
-
- let json_string = String::from_utf8(output)
- .map_err(|e| anyhow::anyhow!("Error converting JSON to string: {e}"))?;
-
- // Ensure the JSON output ends with a newline
- if json_string.ends_with('\n') {
- Ok(json_string)
- } else {
- Ok(json_string + "\n")
- }
-}
-
-/// Generate a JSON for the CVE
-pub fn generate_json(params: &CveRecordParams) -> Result<String> {
- let CveRecordParams {
- cve_number,
- git_sha_full,
- commit_subject,
- user_name: _user_name, // Not used in this function
- user_email,
- dyad_entries: in_dyad_entries,
- script_name,
- script_version,
- additional_references,
- commit_text,
- affected_files,
- } = params;
-
- // Initialize environment and get repository information
- let uuid = get_uuid()?;
-
- // Prepare dyad entries
- let dyad_entries = prepare_vulnerability_data(git_sha_full, in_dyad_entries)?;
-
- // Create affected products
- let (kernel_product, git_product, cpe_nodes) =
- create_affected_products(&dyad_entries, affected_files.to_vec());
-
- // Generate references
- let references = generate_references(&dyad_entries, additional_references, git_sha_full);
-
- // Process description
- let truncated_description = process_description(commit_text);
-
- // Create CVE record
- let cve_record = create_cve_record(CveRecordCreationParams {
- uuid,
- cve_number,
- commit_subject,
- user_email,
- script_name,
- script_version,
- truncated_description,
- kernel_product,
- git_product,
- cpe_nodes,
- references,
- });
-
- // Serialize CVE record to JSON
- serialize_cve_record(&cve_record)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::models::CveRecord;
-
- #[test]
- fn test_process_description() {
- // Test short description (under limit)
- let short_text = "This is a short description.";
- assert_eq!(process_description(short_text), short_text);
-
- // Test description at exactly the limit
- let at_limit_text = "a".repeat(3982);
- assert_eq!(process_description(&at_limit_text), at_limit_text);
-
- // Test description over the limit
- let over_limit_text = "a".repeat(4000);
- let processed = process_description(&over_limit_text);
- assert!(processed.len() <= 3982 + 16); // Max length + truncated marker length
- assert!(processed.ends_with("---truncated---"));
-
- // Test with trailing newline exactly over limit
- let newline_text = "a".repeat(3981) + "\n";
- assert_eq!(process_description(&newline_text), "a".repeat(3981));
- }
-
- #[test]
- fn test_generate_references() {
- use crate::models::dyad::DyadEntry;
- use cve_utils::Kernel;
-
- // Helper function to create test kernels
- fn create_test_kernel(_version: &str, git_id: &str) -> Kernel {
- // In tests, we don't have real git commit IDs to look up,
- // so we'll create dummy kernels with the provided info
- let kernel = Kernel::from_id(git_id).unwrap_or_else(|_| Kernel::empty_kernel());
-
- // We can't directly modify the fields, but for testing
- // purposes, we're assuming these are valid git IDs and versions
- kernel
- }
-
- // Create test dyad entries
- let fixed_kernel1 = create_test_kernel("5.15", "11c52d250b34a0862edc29db03fbec23b30db6da");
- let fixed_kernel2 = create_test_kernel("5.10", "22c52d250b34a0862edc29db03fbec23b30db6db");
- let vuln_kernel = create_test_kernel("5.4", "33c52d250b34a0862edc29db03fbec23b30db6dc");
-
- let entries = vec![
- DyadEntry {
- vulnerable: vuln_kernel.clone(),
- fixed: fixed_kernel1,
- },
- DyadEntry {
- vulnerable: vuln_kernel,
- fixed: fixed_kernel2,
- },
- ];
-
- let additional_refs = vec![
- "https://example.com/ref1".to_string(),
- "https://example.com/ref2".to_string(),
- ];
-
- let git_sha_full = "abcdef1234567890";
-
- // Test reference generation
- let references = generate_references(&entries, &additional_refs, git_sha_full);
-
- // With our updated Kernel::from_id implementation, our test kernels may not
- // generate references in the same way, so we'll check for at least the main fix
- // reference and the additional refs
- assert!(references.len() >= 3);
-
- // With our test kernels, we can't reliably check for specific git URLs,
- // so we'll only check for the additional references
-
- // Check that additional references were added
- assert!(references
- .iter()
- .any(|r| r.url == "https://example.com/ref1"));
- assert!(references
- .iter()
- .any(|r| r.url == "https://example.com/ref2"));
-
- // Test with no dyad entries and no additional references
- let references = generate_references(&[], &[], git_sha_full);
-
- // Should have 1 reference (main fix commit)
- assert_eq!(references.len(), 1);
- assert_eq!(
- references[0].url,
- format!("https://git.kernel.org/stable/c/{git_sha_full}")
- );
- }
-
- #[test]
- fn test_serialize_cve_record() {
- // Create a minimal CVE record for testing
- let cve_record = CveRecord {
- containers: Containers {
- cna: CnaData {
- provider_metadata: ProviderMetadata {
- org_id: "test-uuid".to_string(),
- },
- descriptions: vec![Description {
- lang: "en".to_string(),
- value: "Test description".to_string(),
- }],
- affected: vec![],
- cpe_applicability: vec![],
- references: vec![],
- title: "Test CVE".to_string(),
- x_generator: Generator {
- engine: "test-engine".to_string(),
- },
- },
- },
- cve_metadata: CveMetadata {
- assigner_org_id: "test-uuid".to_string(),
- cve_id: "CVE-2023-1234".to_string(),
- requester_user_id: "test@example.com".to_string(),
- serial: "1".to_string(),
- state: "PUBLISHED".to_string(),
- },
- data_type: "CVE_RECORD".to_string(),
- data_version: "5.0".to_string(),
- };
-
- // Serialize the record
- let json = serialize_cve_record(&cve_record).unwrap();
-
- // Verify it's valid JSON by parsing it
- let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
-
- // Check basic structure
- assert_eq!(parsed["dataType"], "CVE_RECORD");
- assert_eq!(parsed["dataVersion"], "5.0");
- assert_eq!(parsed["cveMetadata"]["cveID"], "CVE-2023-1234");
- assert_eq!(parsed["cveMetadata"]["state"], "PUBLISHED");
- assert_eq!(parsed["containers"]["cna"]["title"], "Test CVE");
-
- // Check that the output ends with a newline
- assert!(json.ends_with('\n'));
-
- // Check for 3-space indentation
- assert!(json.contains("\n "));
- }
-}
+ provider.generate_json(&vuln_params)
+}
\ No newline at end of file
diff --git a/tools/bippy/src/commands/mbox.rs b/tools/bippy/src/commands/mbox.rs
index 31c54aa..f47e523 100644
--- a/tools/bippy/src/commands/mbox.rs
+++ b/tools/bippy/src/commands/mbox.rs
@@ -2,22 +2,23 @@
//
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
-use cve_utils::version_utils::compare_kernel_versions;
-use std::fmt::Write;
-
+use anyhow::Result;
use crate::models::DyadEntry;
+use crate::providers::{ProviderFactory, VulnerabilityRecordParams};
-/// Parameters for generating an mbox-formatted CVE announcement
+/// Parameters for generating an mbox-formatted vulnerability announcement
pub struct MboxParams<'a> {
- /// CVE identifier (e.g., "CVE-2023-12345")
- pub cve_number: &'a str,
+ /// Provider type (e.g., "cve", "gsd", "euvd")
+ pub provider_type: &'a str,
+ /// Vulnerability identifier
+ pub vuln_id: &'a str,
/// Full Git SHA of the commit that fixes the vulnerability
pub git_sha_full: &'a str,
/// Subject line of the commit
pub commit_subject: &'a str,
- /// Name of the user creating the CVE
+ /// Name of the user creating the announcement
pub user_name: &'a str,
- /// Email of the user creating the CVE
+ /// Email of the user creating the announcement
pub user_email: &'a str,
/// Dyad entries containing vulnerability and fix information
pub dyad_entries: &'a [DyadEntry],
@@ -33,431 +34,21 @@
pub affected_files: &'a Vec<String>,
}
-/// Parse dyad entries into vulnerability information strings
-fn parse_dyad_entries(dyad_entries: &[DyadEntry]) -> Vec<String> {
- let mut vuln_array_mbox = Vec::new();
-
- for entry in dyad_entries {
- // Handle unfixed vulnerabilities
- if entry.fixed.is_empty() {
- // Issue is not fixed, so say that:
- vuln_array_mbox.push(format!(
- "Issue introduced in {} with commit {}",
- entry.vulnerable.version(),
- entry.vulnerable.git_id()
- ));
- continue;
- }
-
- // Skip entries where the vulnerability is in the same version it was fixed
- if entry.vulnerable.version() == entry.fixed.version() {
- continue;
- }
-
- // Handle different types of entries
- if entry.vulnerable.is_empty() {
- // We do not know when it showed up, so just say it is fixed
- vuln_array_mbox.push(format!(
- "Fixed in {} with commit {}",
- entry.fixed.version(),
- entry.fixed.git_id()
- ));
- } else {
- // Report when it was introduced and when it was fixed
- vuln_array_mbox.push(format!(
- "Issue introduced in {} with commit {} and fixed in {} with commit {}",
- entry.vulnerable.version(),
- entry.vulnerable.git_id(),
- entry.fixed.version(),
- entry.fixed.git_id()
- ));
- }
- }
-
- // If no vulnerabilities were found, do NOT create a CVE at all!
- assert!(!vuln_array_mbox.is_empty(), "No vulnerable:fixed kernel versions, aborting!");
-
- vuln_array_mbox
-}
-
-/// Collect reference URLs from dyad entries and additional references
-fn collect_reference_urls(
- dyad_entries: &[DyadEntry],
- additional_references: &[String],
- git_sha_full: &str,
-) -> Vec<String> {
- // First add all fix commit URLs from dyad entries (except the main fix)
- let mut version_url_pairs = Vec::new();
- for entry in dyad_entries {
- if !entry.fixed.is_empty() && entry.fixed.git_id() != git_sha_full {
- let fix_url = format!("https://git.kernel.org/stable/c/{}", entry.fixed.git_id());
- if !version_url_pairs.iter().any(|(_, url)| url == &fix_url) {
- version_url_pairs.push((entry.fixed.version().clone(), fix_url));
- }
- }
- }
-
- // Sort the URLs by kernel version
- version_url_pairs.sort_by(|(ver_a, _), (ver_b, _)| {
- // Use the shared compare_kernel_versions function
- compare_kernel_versions(ver_a, ver_b)
- });
-
- // Build the URL array from the sorted pairs
- let mut url_array = version_url_pairs
- .into_iter()
- .map(|(_, url)| url)
- .collect::<Vec<_>>();
-
- // Add the main fix commit URL at the end
- url_array.push(format!("https://git.kernel.org/stable/c/{git_sha_full}"));
-
- // Add any additional references from the reference file
- for url in additional_references {
- if !url_array.contains(url) {
- url_array.push(url.clone());
- }
- }
-
- url_array
-}
-
-/// Format various sections for the mbox content
-fn format_mbox_sections(
- vuln_array_mbox: Vec<String>,
- affected_files: Vec<String>,
- url_array: Vec<String>,
-) -> (String, String, String) {
- // Format the vulnerability summary section
- let mut vuln_section = String::new();
- for line in vuln_array_mbox {
- writeln!(vuln_section, "\t{line}").unwrap();
- }
-
- // Format the affected files section
- let mut files_section = String::new();
- for file in affected_files {
- writeln!(files_section, "\t{file}").unwrap();
- }
-
- // Format the mitigation section with URLs
- let mut url_section = String::new();
- for url in url_array {
- writeln!(url_section, "\t{url}").unwrap();
- }
-
- (vuln_section, files_section, url_section)
-}
-
-/// Parameters for creating mbox content
-struct MboxContentParams<'a> {
- from_line: &'a str,
- user_name: &'a str,
- user_email: &'a str,
- cve_number: &'a str,
- commit_subject: &'a str,
- commit_text: &'a str,
- vuln_section: &'a str,
- files_section: &'a str,
- url_section: &'a str,
-}
-
-/// Create the final mbox content
-fn create_mbox_content(params: &MboxContentParams) -> String {
- // The full formatted mbox content
- let result = format!(
- "{}\n\
- From: {} <{}>\n\
- To: <linux-cve-announce@vger.kernel.org>\n\
- Reply-to: <cve@kernel.org>, <linux-kernel@vger.kernel.org>\n\
- Subject: {}: {}\n\
- \n\
- Description\n\
- ===========\n\
- \n\
- {}\n\
- \n\
- The Linux kernel CVE team has assigned {} to this issue.\n\
- \n\
- \n\
- Affected and fixed versions\n\
- ===========================\n\
- \n\
- {}\n\
- Please see https://www.kernel.org for a full list of currently supported\n\
- kernel versions by the kernel community.\n\
- \n\
- Unaffected versions might change over time as fixes are backported to\n\
- older supported kernel versions. The official CVE entry at\n\
- \thttps://cve.org/CVERecord/?id={}\n\
- will be updated if fixes are backported, please check that for the most\n\
- up to date information about this issue.\n\
- \n\
- \n\
- Affected files\n\
- ==============\n\
- \n\
- The file(s) affected by this issue are:\n\
- {}\n\
- \n\
- Mitigation\n\
- ==========\n\
- \n\
- The Linux kernel CVE team recommends that you update to the latest\n\
- stable kernel version for this, and many other bugfixes. Individual\n\
- changes are never tested alone, but rather are part of a larger kernel\n\
- release. Cherry-picking individual commits is not recommended or\n\
- supported by the Linux kernel community at all. If however, updating to\n\
- the latest release is impossible, the individual changes to resolve this\n\
- issue can be found at these commits:\n\
- {}",
- params.from_line,
- params.user_name,
- params.user_email,
- params.cve_number,
- params.commit_subject,
- params.commit_text.trim_end(), // Trim any trailing newlines
- params.cve_number,
- params.vuln_section,
- params.cve_number,
- params.files_section,
- params.url_section
- );
-
- // Ensure the result ends with a newline
- if result.ends_with('\n') {
- result
- } else {
- result + "\n"
- }
-}
-
-/// Generate an mbox file for the CVE
-pub fn generate_mbox(params: &MboxParams) -> String {
- let MboxParams {
- cve_number,
- git_sha_full,
- commit_subject,
- user_name,
- user_email,
- dyad_entries,
- script_name,
- script_version,
- additional_references,
- commit_text,
- affected_files,
- } = params;
-
- // For the From line we need the script name and version
- let from_line = format!("From {script_name}-{script_version} Mon Sep 17 00:00:00 2001");
-
- // Parse dyad entries into vulnerability information
- let vuln_array_mbox = parse_dyad_entries(dyad_entries);
-
- // Collect reference URLs
- let url_array = collect_reference_urls(dyad_entries, additional_references, git_sha_full);
-
- // Format sections for the mbox content
- let (vuln_section, files_section, url_section) =
- format_mbox_sections(vuln_array_mbox, affected_files.to_vec(), url_array);
-
- // Create the final mbox content
- create_mbox_content(&MboxContentParams {
- from_line: &from_line,
- user_name,
- user_email,
- cve_number,
- commit_subject,
- commit_text,
- vuln_section: &vuln_section,
- files_section: &files_section,
- url_section: &url_section,
- })
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::models::DyadEntry;
- use cve_utils::Kernel;
-
- fn create_test_kernel(_version: &str, git_id: &str) -> Kernel {
- // In tests, we don't have real git commit IDs to look up,
- // so we'll create dummy kernels with the provided info
- let kernel = Kernel::from_id(git_id).unwrap_or_else(|_| Kernel::empty_kernel());
-
- // We can't directly modify the fields, but for testing
- // purposes, we're assuming these are valid git IDs and versions
- kernel
- }
-
- #[test]
- fn test_parse_dyad_entries() {
- // Create test data
- let entries = vec![
- // Entry with both vulnerable and fixed
- DyadEntry {
- vulnerable: create_test_kernel("5.15", "11c52d250b34a0862edc29db03fbec23b30db6da"),
- fixed: create_test_kernel("5.16", "2b503c8598d1b232e7fc7526bce9326d92331541"),
- },
- // Entry with unknown vulnerable but known fixed
- DyadEntry {
- vulnerable: Kernel::empty_kernel(),
- fixed: create_test_kernel("5.10", "3b503c8598d1b232e7fc7526bce9326d92331542"),
- },
- // Entry with unfixed vulnerability
- DyadEntry {
- vulnerable: create_test_kernel("5.4", "4b503c8598d1b232e7fc7526bce9326d92331543"),
- fixed: Kernel::empty_kernel(),
- },
- // Entry with same version (should be ignored)
- DyadEntry {
- vulnerable: create_test_kernel("6.1", "5b503c8598d1b232e7fc7526bce9326d92331544"),
- fixed: create_test_kernel("6.1", "6b503c8598d1b232e7fc7526bce9326d92331545"),
- },
- ];
-
- // With our updated Kernel implementation, the exact behavior may be different
- // We'll check that we get at least some entries
- let vuln_info = parse_dyad_entries(&entries);
- assert!(!vuln_info.is_empty());
-
- // We can't check for specific content with our test kernels,
- // so we'll just verify that entries were generated
-
- // Test with an array containing only same-version entries
- let _same_version_entries = vec![DyadEntry {
- vulnerable: create_test_kernel("6.1", "5b503c8598d1b232e7fc7526bce9326d92331544"),
- fixed: create_test_kernel("6.1", "6b503c8598d1b232e7fc7526bce9326d92331545"),
- }];
-
- // Testing directly will panic, so we can't test that case directly
- }
-
- #[test]
- fn test_collect_reference_urls() {
- // Create test data
- let entries = vec![
- DyadEntry {
- vulnerable: create_test_kernel("5.15", "11c52d250b34a0862edc29db03fbec23b30db6da"),
- fixed: create_test_kernel("5.16", "22c52d250b34a0862edc29db03fbec23b30db6db"),
- },
- DyadEntry {
- vulnerable: create_test_kernel("5.10", "33c52d250b34a0862edc29db03fbec23b30db6dc"),
- fixed: create_test_kernel("5.10.1", "44c52d250b34a0862edc29db03fbec23b30db6dd"),
- },
- ];
-
- let additional_refs = vec![
- "https://example.com/ref1".to_string(),
- "https://example.com/ref2".to_string(),
- ];
-
- let git_sha_full = "main_fix_id";
-
- // Collect the references
- let urls = collect_reference_urls(&entries, &additional_refs, git_sha_full);
-
- // With our updated Kernel implementation, we may not get all references
- // but we should at least get the main fix and additional refs
- assert!(urls.len() >= 3);
- assert!(urls.contains(&"https://git.kernel.org/stable/c/main_fix_id".to_string()));
-
- // With our test kernels, we can't check specific git URLs
- assert!(urls.contains(&"https://git.kernel.org/stable/c/main_fix_id".to_string()));
- assert!(urls.contains(&"https://example.com/ref1".to_string()));
- assert!(urls.contains(&"https://example.com/ref2".to_string()));
-
- // Verify only that additional refs appear at the end
- assert_eq!(urls.last(), Some(&"https://example.com/ref2".to_string()));
- }
-
- #[test]
- fn test_format_mbox_sections() {
- // Create test data
- let vuln_array = vec![
- "Issue introduced in 5.15 with commit 11c52d250b34a0862edc29db03fbec23b30db6da"
- .to_string(),
- "Fixed in 5.10 with commit 22c52d250b34a0862edc29db03fbec23b30db6db".to_string(),
- ];
-
- let affected_files = vec![
- "drivers/net/ethernet/test.c".to_string(),
- "include/linux/test.h".to_string(),
- ];
-
- let url_array = vec![
- "https://git.kernel.org/stable/c/11c52d250b34a0862edc29db03fbec23b30db6da".to_string(),
- "https://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db".to_string(),
- ];
-
- // Format the sections
- let (vuln_section, files_section, url_section) =
- format_mbox_sections(vuln_array, affected_files, url_array);
-
- // Check that each line is properly indented with a tab
- assert!(vuln_section.lines().all(|line| line.starts_with('\t')));
- assert!(files_section.lines().all(|line| line.starts_with('\t')));
- assert!(url_section.lines().all(|line| line.starts_with('\t')));
-
- // Check that all content is present
- assert!(vuln_section.contains("Issue introduced in 5.15"));
- assert!(vuln_section.contains("Fixed in 5.10"));
-
- assert!(files_section.contains("drivers/net/ethernet/test.c"));
- assert!(files_section.contains("include/linux/test.h"));
-
- assert!(url_section
- .contains("https://git.kernel.org/stable/c/11c52d250b34a0862edc29db03fbec23b30db6da"));
- assert!(url_section
- .contains("https://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db"));
- }
-
- #[test]
- fn test_create_mbox_content() {
- // Create test parameters
- let params = MboxContentParams {
- from_line: "From test-script-1.0 Mon Sep 17 00:00:00 2001",
- user_name: "Test User",
- user_email: "test@example.com",
- cve_number: "CVE-2023-1234",
- commit_subject: "Test CVE",
- commit_text: "This is a test commit message.\n\nIt contains details about the vulnerability.",
- vuln_section: "\tIssue introduced in 5.15 with commit 11c52d250b34a0862edc29db03fbec23b30db6da\n\tFixed in 5.10 with commit 22c52d250b34a0862edc29db03fbec23b30db6db\n",
- files_section: "\tdrivers/net/ethernet/test.c\n\tinclude/linux/test.h\n",
- url_section: "\thttps://git.kernel.org/stable/c/11c52d250b34a0862edc29db03fbec23b30db6da\n\thttps://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db\n",
- };
-
- // Create the mbox content
- let mbox = create_mbox_content(¶ms);
-
- // Check the basic structure
- assert!(mbox.starts_with("From test-script-1.0 Mon Sep 17 00:00:00 2001"));
- assert!(mbox.contains("From: Test User <test@example.com>"));
- assert!(mbox.contains("To: <linux-cve-announce@vger.kernel.org>"));
- assert!(mbox.contains("Subject: CVE-2023-1234: Test CVE"));
-
- // Check section headers
- assert!(mbox.contains("Description\n==========="));
- assert!(mbox.contains("Affected and fixed versions\n==========================="));
- assert!(mbox.contains("Affected files\n=============="));
- assert!(mbox.contains("Mitigation\n=========="));
-
- // Check that the commit message is included
- assert!(mbox.contains(
- "This is a test commit message.\n\nIt contains details about the vulnerability."
- ));
-
- // Check that other sections are included
- assert!(mbox.contains(
- "\tIssue introduced in 5.15 with commit 11c52d250b34a0862edc29db03fbec23b30db6da"
- ));
- assert!(mbox.contains("\tdrivers/net/ethernet/test.c"));
- assert!(mbox.contains(
- "\thttps://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db"
- ));
-
- // Check that the result ends with a newline
- assert!(mbox.ends_with('\n'));
- }
-}
+/// Generate an mbox file for the vulnerability announcement
+pub fn generate_mbox(params: &MboxParams) -> Result<String> {
+ let provider = ProviderFactory::create(params.provider_type)?;
+ let vuln_params = VulnerabilityRecordParams {
+ vuln_id: params.vuln_id,
+ git_sha_full: params.git_sha_full,
+ commit_subject: params.commit_subject,
+ user_name: params.user_name,
+ user_email: params.user_email,
+ dyad_entries: params.dyad_entries.to_vec(),
+ script_name: params.script_name,
+ script_version: params.script_version,
+ additional_references: params.additional_references,
+ commit_text: params.commit_text,
+ affected_files: params.affected_files,
+ };
+ provider.generate_mbox(&vuln_params)
+}
\ No newline at end of file
diff --git a/tools/bippy/src/lib.rs b/tools/bippy/src/lib.rs
index 90f19a4..8486a91 100644
--- a/tools/bippy/src/lib.rs
+++ b/tools/bippy/src/lib.rs
@@ -4,4 +4,5 @@
pub mod models;
pub mod utils;
-pub mod commands;
\ No newline at end of file
+pub mod commands;
+pub mod providers;
\ No newline at end of file
diff --git a/tools/bippy/src/main.rs b/tools/bippy/src/main.rs
index 087d3a4..47c3b16 100644
--- a/tools/bippy/src/main.rs
+++ b/tools/bippy/src/main.rs
@@ -4,8 +4,8 @@
use anyhow::{Context, Result};
use clap::Parser;
-use cve_utils::git_config;
-use cve_utils::git_utils::{get_object_full_sha, resolve_reference, get_affected_files};
+use vuln_utils::git_config;
+use vuln_utils::git_utils::{get_object_full_sha, resolve_reference, get_affected_files};
use git2::Repository;
use log::{debug, error, warn};
use std::env;
@@ -13,10 +13,12 @@
mod commands;
mod models;
+mod providers;
mod utils;
-use commands::{generate_json, generate_mbox, json::CveRecordParams, mbox::MboxParams};
+use commands::{generate_json, generate_mbox, json::VulnRecordParams, mbox::MboxParams};
use models::{Args, DyadEntry};
+use providers::ProviderFactory;
use utils::{
apply_diff_to_text, get_commit_subject, get_commit_text, read_tags_file, run_dyad,
strip_commit_text,
@@ -49,7 +51,7 @@
}
/// Type alias for argument validation return value
-type ArgsResult = (String, String, String, Vec<String>, Vec<String>);
+type ArgsResult = (String, String, String, String, Vec<String>, Vec<String>);
/// Validate command line arguments and environment variables
fn validate_args_and_env(args: &Args) -> ArgsResult {
@@ -62,9 +64,12 @@
std::process::exit(1);
}
+ // Handle legacy CVE argument
+ let vuln_id = args.id.as_ref().or(args.cve.as_ref());
+
// Check for required arguments
- if args.cve.is_none() {
- error!("Missing required argument: cve");
+ if vuln_id.is_none() {
+ error!("Missing required argument: id (vulnerability ID)");
std::process::exit(1);
}
@@ -78,11 +83,28 @@
std::process::exit(1);
}
- // Check for CVE_USER environment variable if user is not specified
+ // Create provider to get provider-specific configuration
+ let provider = match ProviderFactory::create(&args.provider) {
+ Ok(p) => p,
+ Err(e) => {
+ error!("Invalid provider '{}': {}", args.provider, e);
+ std::process::exit(1);
+ }
+ };
+
+ // Validate vulnerability ID format
+ let vuln_id_str = vuln_id.unwrap();
+ if let Err(e) = provider.validate_id(vuln_id_str) {
+ error!("{}", e);
+ std::process::exit(1);
+ }
+
+ // Check for provider-specific USER environment variable if user is not specified
+ let user_env_var = provider.user_env_var();
let user_email = args.user.as_ref().map_or_else(
|| {
- env::var("CVE_USER").unwrap_or_else(|_| {
- error!("Missing required argument: user (-u/--user) and CVE_USER environment variable is not set");
+ env::var(user_env_var).unwrap_or_else(|_| {
+ error!("Missing required argument: user (-u/--user) and {} environment variable is not set", user_env_var);
std::process::exit(1);
})
},
@@ -97,7 +119,8 @@
}
// Extract values from args
- let cve_number = args.cve.as_ref().unwrap().clone();
+ let provider_type = args.provider.clone();
+ let vuln_number = vuln_id_str.clone();
let git_shas: Vec<String> = args
.sha
.iter()
@@ -124,7 +147,8 @@
.unwrap_or_else(|| git_config::get_git_config("user.name").unwrap_or_default());
// Debug output if verbose is enabled
- debug!("CVE_NUMBER={cve_number}");
+ debug!("PROVIDER={provider_type}");
+ debug!("VULN_ID={vuln_number}");
debug!("GIT_SHAS={git_shas:?}");
debug!("JSON_FILE={:?}", args.json);
debug!("MBOX_FILE={:?}", args.mbox);
@@ -132,14 +156,14 @@
debug!("REFERENCE_FILE={:?}", args.reference);
debug!("GIT_VULNERABLE={vulnerable_shas:?}");
- (cve_number, user_name, user_email, git_shas, vulnerable_shas)
+ (provider_type, vuln_number, user_name, user_email, git_shas, vulnerable_shas)
}
/// Get repository and script directories
fn get_directories() -> Result<(String, PathBuf, String, String)> {
- // Get vulns directory using cve_utils
+ // Get vulns directory using vuln_utils
let vulns_dir =
- cve_utils::find_vulns_dir().with_context(|| "Failed to find vulns directory")?;
+ vuln_utils::find_vulns_dir().with_context(|| "Failed to find vulns directory")?;
// Get scripts directory
let script_dir = vulns_dir.join("scripts");
@@ -312,7 +336,8 @@
/// Output parameters for generating files
struct OutputParams<'a> {
- cve_number: &'a str,
+ provider_type: &'a str,
+ vuln_id: &'a str,
git_sha_full: &'a str,
commit_subject: &'a str,
user_name: &'a str,
@@ -335,7 +360,8 @@
if let Some(path) = mbox_path {
// Create MboxParams from OutputParams
let mbox_params = MboxParams {
- cve_number: params.cve_number,
+ provider_type: params.provider_type,
+ vuln_id: params.vuln_id,
git_sha_full: params.git_sha_full,
commit_subject: params.commit_subject,
user_name: params.user_name,
@@ -348,20 +374,26 @@
affected_files: params.affected_files,
};
- let mbox_content = generate_mbox(&mbox_params);
-
- if let Err(err) = std::fs::write(path, mbox_content) {
- error!("Warning: Failed to write mbox file to {}: {err}", path.display());
- } else {
- debug!("Wrote mbox file to {path}", path = path.display());
+ match generate_mbox(&mbox_params) {
+ Ok(mbox_content) => {
+ if let Err(err) = std::fs::write(path, mbox_content) {
+ error!("Warning: Failed to write mbox file to {}: {err}", path.display());
+ } else {
+ debug!("Wrote mbox file to {path}", path = path.display());
+ }
+ }
+ Err(err) => {
+ error!("Error: Failed to generate mbox: {err}");
+ }
}
}
// Generate JSON file if requested
if let Some(path) = json_path {
- // Create CveRecordParams from OutputParams
- let json_params = CveRecordParams {
- cve_number: params.cve_number,
+ // Create VulnRecordParams from OutputParams
+ let json_params = VulnRecordParams {
+ provider_type: params.provider_type,
+ vuln_id: params.vuln_id,
git_sha_full: params.git_sha_full,
commit_subject: params.commit_subject,
user_name: params.user_name,
@@ -401,7 +433,7 @@
log_command_args();
// Validate arguments and environment variables
- let (cve_number, user_name, user_email, git_shas, vulnerable_shas) =
+ let (provider_type, vuln_id, user_name, user_email, git_shas, vulnerable_shas) =
validate_args_and_env(&args);
// Get required directories and script information
@@ -418,8 +450,8 @@
// ***POLICY***
// Determine if we actually have any "pairs" of commits that are in a released kernel such that
- // we should be issuing a CVE or not. This is a policy decision from cve.org which requires
- // that an actual release is vulnerable in order for a CVE to be issued, it's not just the fact
+ // we should be issuing a vulnerability record or not. This is a policy decision which requires
+ // that an actual release is vulnerable in order for a record to be issued, it's not just the fact
// that we created and fixed a bug in a git range.
let mut is_vulnerable = false;
for entry in &dyad_entries {
@@ -430,7 +462,7 @@
is_vulnerable = true;
}
if !is_vulnerable {
- error!("Despite having some vulnerable:fixed kernels, none were in an actual release, so aborting and not assigning a CVE to {git_sha_full}");
+ error!("Despite having some vulnerable:fixed kernels, none were in an actual release, so aborting and not assigning a vulnerability ID to {git_sha_full}");
std::process::exit(1);
}
@@ -439,7 +471,8 @@
// Create output parameters
let output_params = OutputParams {
- cve_number: &cve_number,
+ provider_type: &provider_type,
+ vuln_id: &vuln_id,
git_sha_full: &git_sha_full,
commit_subject: &commit_subject,
user_name: &user_name,
@@ -465,7 +498,7 @@
#[cfg(test)]
mod tests {
use super::*;
- use cve_utils::version_utils::{version_is_mainline, version_is_queue, version_is_rc};
+ use vuln_utils::version_utils::{version_is_mainline, version_is_queue, version_is_rc};
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
@@ -758,7 +791,7 @@
file.write_all(uuid_content.as_bytes()).unwrap();
// Test reading the UUID file
- let uuid = utils::file::read_uuid(dir.path()).unwrap();
+ let uuid = utils::file::read_uuid(dir.path(), "linux.uuid").unwrap();
assert_eq!(uuid, "12345678-abcd-efgh-ijkl-mnopqrstuvwx");
// Test with empty file
@@ -766,7 +799,7 @@
let empty_path = dir.path().join("linux.uuid");
let mut file = File::create(&empty_path).unwrap();
file.write_all(b"").unwrap();
- let result = utils::file::read_uuid(dir.path());
+ let result = utils::file::read_uuid(dir.path(), "linux.uuid");
assert!(result.is_err());
}
@@ -792,4 +825,4 @@
"Version should only contain digits and dots"
);
}
-}
+}
\ No newline at end of file
diff --git a/tools/bippy/src/models/cli.rs b/tools/bippy/src/models/cli.rs
index 5b919ae..d2caa73 100644
--- a/tools/bippy/src/models/cli.rs
+++ b/tools/bippy/src/models/cli.rs
@@ -9,9 +9,13 @@
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None, disable_version_flag = true, trailing_var_arg = true)]
pub struct Args {
- /// CVE number (e.g., "CVE-2021-1234")
- #[clap(short, long)]
- pub cve: Option<String>,
+ /// Provider type (e.g., "cve", "gsd", "euvd")
+ #[clap(short = 'p', long, default_value = "cve")]
+ pub provider: String,
+
+ /// Vulnerability ID (e.g., "CVE-2021-1234", "GSD-2021-1234")
+ #[clap(short = 'i', long)]
+ pub id: Option<String>,
/// Git SHA(s) of the commit(s)
#[clap(short, long, num_args = 1..)]
@@ -52,4 +56,9 @@
/// Catch any trailing arguments
#[clap(hide = true)]
pub remaining_parameters: Vec<String>,
+
+ // Legacy support
+ /// CVE number (deprecated: use --id instead)
+ #[clap(short = 'c', long, hide = true)]
+ pub cve: Option<String>,
}
diff --git a/tools/bippy/src/models/cve.rs b/tools/bippy/src/models/cve.rs
deleted file mode 100644
index c45e393..0000000
--- a/tools/bippy/src/models/cve.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-//
-// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
-
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct CveMetadata {
- #[serde(rename = "assignerOrgId")]
- pub assigner_org_id: String,
- #[serde(rename = "cveID")]
- pub cve_id: String,
- #[serde(rename = "requesterUserId")]
- pub requester_user_id: String,
- pub serial: String,
- pub state: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct Description {
- pub lang: String,
- pub value: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct ProviderMetadata {
- #[serde(rename = "orgId")]
- pub org_id: String,
-}
-
-#[derive(Debug, Serialize, Deserialize, Default)]
-pub struct VersionRange {
- // Version string, in a specific type, see versionType below for the valid types
- // 0 means "beginning of time"
- pub version: String,
-
- #[serde(rename = "lessThan", skip_serializing_if = "Option::is_none")]
- pub less_than: Option<String>,
-
- #[serde(rename = "lessThanOrEqual", skip_serializing_if = "Option::is_none")]
- pub less_than_or_equal: Option<String>,
-
- // valid values are "affected", "unaffected", or "unknown"
- pub status: String,
-
- // valid values are "custom", "git", "maven", "python", "rpm", or "semver"
- // We will just stick with "git" or "semver" as that's the most sane for us, even though
- // "semver" is NOT what Linux kernel release numbers represent at all.
- #[serde(rename = "versionType", skip_serializing_if = "Option::is_none")]
- pub version_type: Option<String>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct AffectedProduct {
- pub product: String,
- pub vendor: String,
- #[serde(rename = "defaultStatus")]
- pub default_status: String,
- pub repo: String,
- #[serde(rename = "programFiles")]
- pub program_files: Vec<String>,
- #[serde(skip_serializing_if = "Vec::is_empty")]
- pub versions: Vec<VersionRange>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct Reference {
- pub url: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct Generator {
- pub engine: String,
-}
-
-#[derive(Debug, Serialize, Deserialize, Default)]
-pub struct CpeMatch {
- // boolean value, must be "true" or "false"
- pub vulnerable: bool,
-
- // critera for us is always going to be: "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*"
- pub criteria: String,
-
- #[serde(rename = "versionStartIncluding")]
- #[serde(skip_serializing_if = "String::is_empty")]
- pub version_start_including: String,
-
- #[serde(rename = "versionEndExcluding")]
- #[serde(skip_serializing_if = "String::is_empty")]
- pub version_end_excluding: String,
-
- // Odds are we will not use the following fields, but they are here
- // just to round out the documentation of the schema
- #[serde(rename = "matchCriteriaId")]
- #[serde(skip_serializing_if = "String::is_empty")]
- pub match_criteria_id: String,
-
- #[serde(rename = "versionStartExcluding")]
- #[serde(skip_serializing_if = "String::is_empty")]
- pub version_start_excluding: String,
-
- #[serde(rename = "versionEndIncluding")]
- #[serde(skip_serializing_if = "String::is_empty")]
- pub version_end_including: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct CpeNodes {
- // must be "OR" or "AND"
- pub operator: String,
- // boolean value, must be "true" or "false"
- pub negate: bool,
- #[serde(rename = "cpeMatch")]
- pub cpe_match: Vec<CpeMatch>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct CpeApplicability {
- pub nodes: Vec<CpeNodes>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct CnaData {
- #[serde(rename = "providerMetadata")]
- pub provider_metadata: ProviderMetadata,
- pub descriptions: Vec<Description>,
- pub affected: Vec<AffectedProduct>,
- #[serde(rename = "cpeApplicability")]
- #[serde(skip_serializing_if = "Vec::is_empty")]
- pub cpe_applicability: Vec<CpeApplicability>,
- pub references: Vec<Reference>,
- pub title: String,
- #[serde(rename = "x_generator")]
- pub x_generator: Generator,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct Containers {
- pub cna: CnaData,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct CveRecord {
- pub containers: Containers,
- #[serde(rename = "cveMetadata")]
- pub cve_metadata: CveMetadata,
- #[serde(rename = "dataType")]
- pub data_type: String,
- #[serde(rename = "dataVersion")]
- pub data_version: String,
-}
diff --git a/tools/bippy/src/models/dyad.rs b/tools/bippy/src/models/dyad.rs
index 4b65fff..c25913d 100644
--- a/tools/bippy/src/models/dyad.rs
+++ b/tools/bippy/src/models/dyad.rs
@@ -3,7 +3,7 @@
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
use crate::models::errors::BippyError;
-use cve_utils::Kernel;
+use vuln_utils::Kernel;
/// `DyadEntry` represents a kernel vulnerability range entry from the dyad script
#[derive(Debug, Clone)]
diff --git a/tools/bippy/src/models/mod.rs b/tools/bippy/src/models/mod.rs
index 25d6f09..fda4b8d 100644
--- a/tools/bippy/src/models/mod.rs
+++ b/tools/bippy/src/models/mod.rs
@@ -3,10 +3,8 @@
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
pub mod cli;
-pub mod cve;
pub mod dyad;
pub mod errors;
pub use cli::Args;
-pub use cve::*;
pub use dyad::DyadEntry;
diff --git a/tools/bippy/src/providers/common.rs b/tools/bippy/src/providers/common.rs
new file mode 100644
index 0000000..27d5757
--- /dev/null
+++ b/tools/bippy/src/providers/common.rs
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
+
+//! Common data structures used across different vulnerability providers
+
+use serde::{Deserialize, Serialize};
+
+/// Version range information used by providers
+#[derive(Debug, Serialize, Deserialize, Default)]
+pub struct VersionRange {
+ /// Version string, in a specific type, see versionType below for the valid types
+ /// 0 means "beginning of time"
+ pub version: String,
+
+ #[serde(rename = "lessThan", skip_serializing_if = "Option::is_none")]
+ pub less_than: Option<String>,
+
+ #[serde(rename = "lessThanOrEqual", skip_serializing_if = "Option::is_none")]
+ pub less_than_or_equal: Option<String>,
+
+ /// valid values are "affected", "unaffected", or "unknown"
+ pub status: String,
+
+ /// valid values are "custom", "git", "maven", "python", "rpm", or "semver"
+ /// We will just stick with "git" or "semver" as that's the most sane for us, even though
+ /// "semver" is NOT what Linux kernel release numbers represent at all.
+ #[serde(rename = "versionType", skip_serializing_if = "Option::is_none")]
+ pub version_type: Option<String>,
+}
+
+/// CPE (Common Platform Enumeration) match information
+#[derive(Debug, Serialize, Deserialize, Default)]
+pub struct CpeMatch {
+ /// boolean value, must be "true" or "false"
+ pub vulnerable: bool,
+
+ /// critera for us is always going to be: "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*"
+ pub criteria: String,
+
+ #[serde(rename = "versionStartIncluding")]
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub version_start_including: String,
+
+ #[serde(rename = "versionEndExcluding")]
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub version_end_excluding: String,
+
+ /// Odds are we will not use the following fields, but they are here
+ /// just to round out the documentation of the schema
+ #[serde(rename = "matchCriteriaId")]
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub match_criteria_id: String,
+
+ #[serde(rename = "versionStartExcluding")]
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub version_start_excluding: String,
+
+ #[serde(rename = "versionEndIncluding")]
+ #[serde(skip_serializing_if = "String::is_empty")]
+ pub version_end_including: String,
+}
+
+/// CPE nodes for vulnerability applicability
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CpeNodes {
+ /// must be "OR" or "AND"
+ pub operator: String,
+ /// boolean value, must be "true" or "false"
+ pub negate: bool,
+ #[serde(rename = "cpeMatch")]
+ pub cpe_match: Vec<CpeMatch>,
+}
\ No newline at end of file
diff --git a/tools/bippy/src/providers/cve/json.rs b/tools/bippy/src/providers/cve/json.rs
new file mode 100644
index 0000000..b284653
--- /dev/null
+++ b/tools/bippy/src/providers/cve/json.rs
@@ -0,0 +1,433 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
+
+use anyhow::Result;
+use serde::Serialize;
+use serde_json::ser::{PrettyFormatter, Serializer};
+use std::collections::HashSet;
+
+use super::models::{
+ AffectedProduct, CnaData, Containers, CpeApplicability, CveMetadata, CveRecord,
+ Description, Generator, ProviderMetadata, Reference,
+};
+use crate::providers::CpeNodes;
+use crate::models::DyadEntry;
+use crate::utils::{
+ determine_default_status, generate_cpe_ranges, generate_git_ranges, generate_version_ranges,
+};
+
+/// Parameters for generating a JSON CVE record
+pub struct CveRecordParams<'a> {
+ /// Organization UUID
+ pub uuid: &'a str,
+ /// CVE identifier (e.g., "CVE-2023-12345")
+ pub cve_number: &'a str,
+ /// Full Git SHA of the commit that fixes the vulnerability
+ pub git_sha_full: &'a str,
+ /// Subject line of the commit
+ pub commit_subject: &'a str,
+ /// Name of the user creating the CVE
+ pub user_name: &'a str,
+ /// Email of the user creating the CVE
+ pub user_email: &'a str,
+ /// Dyad entries containing vulnerability and fix information
+ pub dyad_entries: Vec<DyadEntry>,
+ /// Name of the script generating the record
+ pub script_name: &'a str,
+ /// Version of the script generating the record
+ pub script_version: &'a str,
+ /// Additional reference URLs
+ pub additional_references: &'a [String],
+ /// Full commit text/description
+ pub commit_text: &'a str,
+ /// List of affected files
+ pub affected_files: &'a Vec<String>,
+}
+
+
+/// Prepare dyad entries and affected files
+fn prepare_vulnerability_data(
+ git_sha_full: &str,
+ in_dyad_entries: &[DyadEntry],
+) -> Result<Vec<DyadEntry>> {
+ // Clone dyad entries since we might need to modify them
+ let mut dyad_entries = in_dyad_entries.to_vec();
+
+ // If no entries were created, use the fix commit as a fallback
+ if dyad_entries.is_empty() {
+ // Create a dummy entry using the fix commit
+ if let Ok(entry) = DyadEntry::from_str(&format!("0:0:0:{git_sha_full}")) {
+ dyad_entries.push(entry);
+ }
+ }
+
+ Ok(dyad_entries)
+}
+
+/// Create affected products (kernel and git)
+fn create_affected_products(
+ dyad_entries: &[DyadEntry],
+ affected_files: Vec<String>,
+) -> (AffectedProduct, AffectedProduct, Vec<CpeNodes>) {
+ // Determine default status
+ let default_status = determine_default_status(dyad_entries);
+
+ // Generate version ranges for kernel product
+ let kernel_versions = generate_version_ranges(dyad_entries, default_status);
+ let kernel_product = AffectedProduct {
+ product: "Linux".to_string(),
+ vendor: "Linux".to_string(),
+ default_status: default_status.to_string(),
+ repo: "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git".to_string(),
+ program_files: affected_files.clone(),
+ versions: kernel_versions,
+ };
+
+ // Generate git ranges for git product
+ let git_versions = generate_git_ranges(dyad_entries);
+ let git_product = AffectedProduct {
+ product: "Linux".to_string(),
+ vendor: "Linux".to_string(),
+ default_status: "unaffected".to_string(),
+ repo: "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git".to_string(),
+ program_files: affected_files,
+ versions: git_versions,
+ };
+
+ // Generate CPE ranges
+ let cpe_nodes = generate_cpe_ranges(dyad_entries);
+
+ (kernel_product, git_product, cpe_nodes)
+}
+
+/// Generate references from dyad entries and additional references
+fn generate_references(
+ dyad_entries: &[DyadEntry],
+ additional_references: &[String],
+ git_sha_full: &str,
+) -> Vec<Reference> {
+ let mut references = Vec::new();
+ let mut seen_refs: HashSet<String> = HashSet::new();
+
+ // Add references for all entries
+ for entry in dyad_entries {
+ // Add fixed commit reference if available
+ if !entry.fixed.is_empty() {
+ let url = format!("https://git.kernel.org/stable/c/{}", entry.fixed.git_id());
+ if !seen_refs.contains(&url) {
+ seen_refs.insert(url.clone());
+ references.push(Reference { url });
+ }
+ }
+ }
+
+ // Add any additional references from the reference file
+ for url in additional_references {
+ if !seen_refs.contains(url) {
+ seen_refs.insert(url.clone());
+ references.push(Reference { url: url.clone() });
+ }
+ }
+
+ // If no references were found, add the main fix commit
+ if references.is_empty() {
+ let main_fix_url = format!("https://git.kernel.org/stable/c/{git_sha_full}");
+ references.push(Reference { url: main_fix_url });
+ }
+
+ references
+}
+
+/// Process commit description text and handle truncation
+fn process_description(commit_text: &str) -> String {
+ // Truncate description to 3982 characters (CVE backend limit) if needed
+ let max_length = 3982; // CVE backend limit
+
+ if commit_text.len() <= max_length {
+ // If already under the limit, just ensure no trailing newline
+ return commit_text.trim_end().to_string();
+ }
+
+ // Get the truncated text limited to max_length
+ let truncated = &commit_text[..max_length];
+
+ // Special case: if only over by a trailing newline, just trim it
+ if commit_text.len() == max_length + 1 && commit_text.ends_with('\n') {
+ truncated.to_string()
+ } else {
+ // Add truncation marker, with proper newline handling
+ let separator = if truncated.ends_with('\n') { "" } else { "\n" };
+ format!("{truncated}{separator}---truncated---")
+ }
+}
+
+/// Parameters for creating a CVE record
+struct CveRecordCreationParams<'a> {
+ uuid: String,
+ cve_number: &'a str,
+ commit_subject: &'a str,
+ user_email: &'a str,
+ script_name: &'a str,
+ script_version: &'a str,
+ truncated_description: String,
+ kernel_product: AffectedProduct,
+ git_product: AffectedProduct,
+ cpe_nodes: Vec<CpeNodes>,
+ references: Vec<Reference>,
+}
+
+/// Create the CVE record structure
+fn create_cve_record(params: CveRecordCreationParams) -> CveRecord {
+ CveRecord {
+ containers: Containers {
+ cna: CnaData {
+ provider_metadata: ProviderMetadata {
+ org_id: params.uuid.clone(),
+ },
+ descriptions: vec![Description {
+ lang: "en".to_string(),
+ value: params.truncated_description,
+ }],
+ affected: vec![params.git_product, params.kernel_product],
+ cpe_applicability: vec![CpeApplicability {
+ nodes: params.cpe_nodes,
+ }],
+ references: params.references,
+ title: params.commit_subject.to_string(),
+ x_generator: Generator {
+ engine: format!("{}-{}", params.script_name, params.script_version),
+ },
+ },
+ },
+ cve_metadata: CveMetadata {
+ assigner_org_id: params.uuid.to_string(),
+ cve_id: params.cve_number.to_string(),
+ requester_user_id: params.user_email.to_string(),
+ serial: "1".to_string(),
+ state: "PUBLISHED".to_string(),
+ },
+ data_type: "CVE_RECORD".to_string(),
+ data_version: "5.0".to_string(),
+ }
+}
+
+/// Serialize the CVE record to JSON
+fn serialize_cve_record(cve_record: &CveRecord) -> Result<String> {
+ // Use a custom formatter with 3-space indentation
+ let formatter = PrettyFormatter::with_indent(b" ");
+ let mut output = Vec::new();
+ let mut serializer = Serializer::with_formatter(&mut output, formatter);
+
+ cve_record
+ .serialize(&mut serializer)
+ .map_err(|e| anyhow::anyhow!("Error serializing JSON: {e}"))?;
+
+ let json_string = String::from_utf8(output)
+ .map_err(|e| anyhow::anyhow!("Error converting JSON to string: {e}"))?;
+
+ // Ensure the JSON output ends with a newline
+ if json_string.ends_with('\n') {
+ Ok(json_string)
+ } else {
+ Ok(json_string + "\n")
+ }
+}
+
+/// Generate a JSON for the CVE
+pub fn generate_json(params: &CveRecordParams) -> Result<String> {
+ let CveRecordParams {
+ uuid,
+ cve_number,
+ git_sha_full,
+ commit_subject,
+ user_name: _user_name, // Not used in this function
+ user_email,
+ dyad_entries: in_dyad_entries,
+ script_name,
+ script_version,
+ additional_references,
+ commit_text,
+ affected_files,
+ } = params;
+
+ // Prepare dyad entries
+ let dyad_entries = prepare_vulnerability_data(git_sha_full, in_dyad_entries)?;
+
+ // Create affected products
+ let (kernel_product, git_product, cpe_nodes) =
+ create_affected_products(&dyad_entries, affected_files.to_vec());
+
+ // Generate references
+ let references = generate_references(&dyad_entries, additional_references, git_sha_full);
+
+ // Process description
+ let truncated_description = process_description(commit_text);
+
+ // Create CVE record
+ let cve_record = create_cve_record(CveRecordCreationParams {
+ uuid: uuid.to_string(),
+ cve_number,
+ commit_subject,
+ user_email,
+ script_name,
+ script_version,
+ truncated_description,
+ kernel_product,
+ git_product,
+ cpe_nodes,
+ references,
+ });
+
+ // Serialize CVE record to JSON
+ serialize_cve_record(&cve_record)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::providers::cve::models::CveRecord;
+
+ #[test]
+ fn test_process_description() {
+ // Test short description (under limit)
+ let short_text = "This is a short description.";
+ assert_eq!(process_description(short_text), short_text);
+
+ // Test description at exactly the limit
+ let at_limit_text = "a".repeat(3982);
+ assert_eq!(process_description(&at_limit_text), at_limit_text);
+
+ // Test description over the limit
+ let over_limit_text = "a".repeat(4000);
+ let processed = process_description(&over_limit_text);
+ assert!(processed.len() <= 3982 + 16); // Max length + truncated marker length
+ assert!(processed.ends_with("---truncated---"));
+
+ // Test with trailing newline exactly over limit
+ let newline_text = "a".repeat(3981) + "\n";
+ assert_eq!(process_description(&newline_text), "a".repeat(3981));
+ }
+
+ #[test]
+ fn test_generate_references() {
+ use crate::models::dyad::DyadEntry;
+ use vuln_utils::Kernel;
+
+ // Helper function to create test kernels
+ fn create_test_kernel(_version: &str, git_id: &str) -> Kernel {
+ // In tests, we don't have real git commit IDs to look up,
+ // so we'll create dummy kernels with the provided info
+ let kernel = Kernel::from_id(git_id).unwrap_or_else(|_| Kernel::empty_kernel());
+
+ // We can't directly modify the fields, but for testing
+ // purposes, we're assuming these are valid git IDs and versions
+ kernel
+ }
+
+ // Create test dyad entries
+ let fixed_kernel1 = create_test_kernel("5.15", "11c52d250b34a0862edc29db03fbec23b30db6da");
+ let fixed_kernel2 = create_test_kernel("5.10", "22c52d250b34a0862edc29db03fbec23b30db6db");
+ let vuln_kernel = create_test_kernel("5.4", "33c52d250b34a0862edc29db03fbec23b30db6dc");
+
+ let entries = vec![
+ DyadEntry {
+ vulnerable: vuln_kernel.clone(),
+ fixed: fixed_kernel1,
+ },
+ DyadEntry {
+ vulnerable: vuln_kernel,
+ fixed: fixed_kernel2,
+ },
+ ];
+
+ let additional_refs = vec![
+ "https://example.com/ref1".to_string(),
+ "https://example.com/ref2".to_string(),
+ ];
+
+ let git_sha_full = "abcdef1234567890";
+
+ // Test reference generation
+ let references = generate_references(&entries, &additional_refs, git_sha_full);
+
+ // With our updated Kernel::from_id implementation, our test kernels may not
+ // generate references in the same way, so we'll check for at least the main fix
+ // reference and the additional refs
+ assert!(references.len() >= 3);
+
+ // With our test kernels, we can't reliably check for specific git URLs,
+ // so we'll only check for the additional references
+
+ // Check that additional references were added
+ assert!(references
+ .iter()
+ .any(|r| r.url == "https://example.com/ref1"));
+ assert!(references
+ .iter()
+ .any(|r| r.url == "https://example.com/ref2"));
+
+ // Test with no dyad entries and no additional references
+ let references = generate_references(&[], &[], git_sha_full);
+
+ // Should have 1 reference (main fix commit)
+ assert_eq!(references.len(), 1);
+ assert_eq!(
+ references[0].url,
+ format!("https://git.kernel.org/stable/c/{git_sha_full}")
+ );
+ }
+
+ #[test]
+ fn test_serialize_cve_record() {
+ // Create a minimal CVE record for testing
+ let cve_record = CveRecord {
+ containers: Containers {
+ cna: CnaData {
+ provider_metadata: ProviderMetadata {
+ org_id: "test-uuid".to_string(),
+ },
+ descriptions: vec![Description {
+ lang: "en".to_string(),
+ value: "Test description".to_string(),
+ }],
+ affected: vec![],
+ cpe_applicability: vec![],
+ references: vec![],
+ title: "Test CVE".to_string(),
+ x_generator: Generator {
+ engine: "test-engine".to_string(),
+ },
+ },
+ },
+ cve_metadata: CveMetadata {
+ assigner_org_id: "test-uuid".to_string(),
+ cve_id: "CVE-2023-1234".to_string(),
+ requester_user_id: "test@example.com".to_string(),
+ serial: "1".to_string(),
+ state: "PUBLISHED".to_string(),
+ },
+ data_type: "CVE_RECORD".to_string(),
+ data_version: "5.0".to_string(),
+ };
+
+ // Serialize the record
+ let json = serialize_cve_record(&cve_record).unwrap();
+
+ // Verify it's valid JSON by parsing it
+ let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
+
+ // Check basic structure
+ assert_eq!(parsed["dataType"], "CVE_RECORD");
+ assert_eq!(parsed["dataVersion"], "5.0");
+ assert_eq!(parsed["cveMetadata"]["cveID"], "CVE-2023-1234");
+ assert_eq!(parsed["cveMetadata"]["state"], "PUBLISHED");
+ assert_eq!(parsed["containers"]["cna"]["title"], "Test CVE");
+
+ // Check that the output ends with a newline
+ assert!(json.ends_with('\n'));
+
+ // Check for 3-space indentation
+ assert!(json.contains("\n "));
+ }
+}
\ No newline at end of file
diff --git a/tools/bippy/src/providers/cve/mbox.rs b/tools/bippy/src/providers/cve/mbox.rs
new file mode 100644
index 0000000..bde24d9
--- /dev/null
+++ b/tools/bippy/src/providers/cve/mbox.rs
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
+
+use vuln_utils::version_utils::compare_kernel_versions;
+use std::fmt::Write;
+
+use crate::models::DyadEntry;
+
+/// Parameters for generating an mbox-formatted CVE announcement
+pub struct MboxParams<'a> {
+ /// CVE identifier (e.g., "CVE-2023-12345")
+ pub cve_number: &'a str,
+ /// Full Git SHA of the commit that fixes the vulnerability
+ pub git_sha_full: &'a str,
+ /// Subject line of the commit
+ pub commit_subject: &'a str,
+ /// Name of the user creating the CVE
+ pub user_name: &'a str,
+ /// Email of the user creating the CVE
+ pub user_email: &'a str,
+ /// Dyad entries containing vulnerability and fix information
+ pub dyad_entries: &'a [DyadEntry],
+ /// Name of the script generating the record
+ pub script_name: &'a str,
+ /// Version of the script generating the record
+ pub script_version: &'a str,
+ /// Additional reference URLs
+ pub additional_references: &'a [String],
+ /// Full commit text/description
+ pub commit_text: &'a str,
+ /// List of affected files
+ pub affected_files: &'a Vec<String>,
+}
+
+/// Parse dyad entries into vulnerability information strings
+fn parse_dyad_entries(dyad_entries: &[DyadEntry]) -> Vec<String> {
+ let mut vuln_array_mbox = Vec::new();
+
+ for entry in dyad_entries {
+ // Handle unfixed vulnerabilities
+ if entry.fixed.is_empty() {
+ // Issue is not fixed, so say that:
+ vuln_array_mbox.push(format!(
+ "Issue introduced in {} with commit {}",
+ entry.vulnerable.version(),
+ entry.vulnerable.git_id()
+ ));
+ continue;
+ }
+
+ // Skip entries where the vulnerability is in the same version it was fixed
+ if entry.vulnerable.version() == entry.fixed.version() {
+ continue;
+ }
+
+ // Handle different types of entries
+ if entry.vulnerable.is_empty() {
+ // We do not know when it showed up, so just say it is fixed
+ vuln_array_mbox.push(format!(
+ "Fixed in {} with commit {}",
+ entry.fixed.version(),
+ entry.fixed.git_id()
+ ));
+ } else {
+ // Report when it was introduced and when it was fixed
+ vuln_array_mbox.push(format!(
+ "Issue introduced in {} with commit {} and fixed in {} with commit {}",
+ entry.vulnerable.version(),
+ entry.vulnerable.git_id(),
+ entry.fixed.version(),
+ entry.fixed.git_id()
+ ));
+ }
+ }
+
+ // If no vulnerabilities were found, do NOT create a CVE at all!
+ assert!(!vuln_array_mbox.is_empty(), "No vulnerable:fixed kernel versions, aborting!");
+
+ vuln_array_mbox
+}
+
+/// Collect reference URLs from dyad entries and additional references
+fn collect_reference_urls(
+ dyad_entries: &[DyadEntry],
+ additional_references: &[String],
+ git_sha_full: &str,
+) -> Vec<String> {
+ // First add all fix commit URLs from dyad entries (except the main fix)
+ let mut version_url_pairs = Vec::new();
+ for entry in dyad_entries {
+ if !entry.fixed.is_empty() && entry.fixed.git_id() != git_sha_full {
+ let fix_url = format!("https://git.kernel.org/stable/c/{}", entry.fixed.git_id());
+ if !version_url_pairs.iter().any(|(_, url)| url == &fix_url) {
+ version_url_pairs.push((entry.fixed.version().clone(), fix_url));
+ }
+ }
+ }
+
+ // Sort the URLs by kernel version
+ version_url_pairs.sort_by(|(ver_a, _), (ver_b, _)| {
+ // Use the shared compare_kernel_versions function
+ compare_kernel_versions(ver_a, ver_b)
+ });
+
+ // Build the URL array from the sorted pairs
+ let mut url_array = version_url_pairs
+ .into_iter()
+ .map(|(_, url)| url)
+ .collect::<Vec<_>>();
+
+ // Add the main fix commit URL at the end
+ url_array.push(format!("https://git.kernel.org/stable/c/{git_sha_full}"));
+
+ // Add any additional references from the reference file
+ for url in additional_references {
+ if !url_array.contains(url) {
+ url_array.push(url.clone());
+ }
+ }
+
+ url_array
+}
+
+/// Format various sections for the mbox content
+fn format_mbox_sections(
+ vuln_array_mbox: Vec<String>,
+ affected_files: Vec<String>,
+ url_array: Vec<String>,
+) -> (String, String, String) {
+ // Format the vulnerability summary section
+ let mut vuln_section = String::new();
+ for line in vuln_array_mbox {
+ writeln!(vuln_section, "\t{line}").unwrap();
+ }
+
+ // Format the affected files section
+ let mut files_section = String::new();
+ for file in affected_files {
+ writeln!(files_section, "\t{file}").unwrap();
+ }
+
+ // Format the mitigation section with URLs
+ let mut url_section = String::new();
+ for url in url_array {
+ writeln!(url_section, "\t{url}").unwrap();
+ }
+
+ (vuln_section, files_section, url_section)
+}
+
+/// Parameters for creating mbox content
+struct MboxContentParams<'a> {
+ from_line: &'a str,
+ user_name: &'a str,
+ user_email: &'a str,
+ cve_number: &'a str,
+ commit_subject: &'a str,
+ commit_text: &'a str,
+ vuln_section: &'a str,
+ files_section: &'a str,
+ url_section: &'a str,
+}
+
+/// Create the final mbox content
+fn create_mbox_content(params: &MboxContentParams) -> String {
+ // The full formatted mbox content
+ let result = format!(
+ "{}\n\
+ From: {} <{}>\n\
+ To: <linux-cve-announce@vger.kernel.org>\n\
+ Reply-to: <cve@kernel.org>, <linux-kernel@vger.kernel.org>\n\
+ Subject: {}: {}\n\
+ \n\
+ Description\n\
+ ===========\n\
+ \n\
+ {}\n\
+ \n\
+ The Linux kernel CVE team has assigned {} to this issue.\n\
+ \n\
+ \n\
+ Affected and fixed versions\n\
+ ===========================\n\
+ \n\
+ {}\n\
+ Please see https://www.kernel.org for a full list of currently supported\n\
+ kernel versions by the kernel community.\n\
+ \n\
+ Unaffected versions might change over time as fixes are backported to\n\
+ older supported kernel versions. The official CVE entry at\n\
+ \thttps://cve.org/CVERecord/?id={}\n\
+ will be updated if fixes are backported, please check that for the most\n\
+ up to date information about this issue.\n\
+ \n\
+ \n\
+ Affected files\n\
+ ==============\n\
+ \n\
+ The file(s) affected by this issue are:\n\
+ {}\n\
+ \n\
+ Mitigation\n\
+ ==========\n\
+ \n\
+ The Linux kernel CVE team recommends that you update to the latest\n\
+ stable kernel version for this, and many other bugfixes. Individual\n\
+ changes are never tested alone, but rather are part of a larger kernel\n\
+ release. Cherry-picking individual commits is not recommended or\n\
+ supported by the Linux kernel community at all. If however, updating to\n\
+ the latest release is impossible, the individual changes to resolve this\n\
+ issue can be found at these commits:\n\
+ {}",
+ params.from_line,
+ params.user_name,
+ params.user_email,
+ params.cve_number,
+ params.commit_subject,
+ params.commit_text.trim_end(), // Trim any trailing newlines
+ params.cve_number,
+ params.vuln_section,
+ params.cve_number,
+ params.files_section,
+ params.url_section
+ );
+
+ // Ensure the result ends with a newline
+ if result.ends_with('\n') {
+ result
+ } else {
+ result + "\n"
+ }
+}
+
+/// Generate an mbox file for the CVE
+pub fn generate_mbox(params: &MboxParams) -> String {
+ let MboxParams {
+ cve_number,
+ git_sha_full,
+ commit_subject,
+ user_name,
+ user_email,
+ dyad_entries,
+ script_name,
+ script_version,
+ additional_references,
+ commit_text,
+ affected_files,
+ } = params;
+
+ // For the From line we need the script name and version
+ let from_line = format!("From {script_name}-{script_version} Mon Sep 17 00:00:00 2001");
+
+ // Parse dyad entries into vulnerability information
+ let vuln_array_mbox = parse_dyad_entries(dyad_entries);
+
+ // Collect reference URLs
+ let url_array = collect_reference_urls(dyad_entries, additional_references, git_sha_full);
+
+ // Format sections for the mbox content
+ let (vuln_section, files_section, url_section) =
+ format_mbox_sections(vuln_array_mbox, affected_files.to_vec(), url_array);
+
+ // Create the final mbox content
+ create_mbox_content(&MboxContentParams {
+ from_line: &from_line,
+ user_name,
+ user_email,
+ cve_number,
+ commit_subject,
+ commit_text,
+ vuln_section: &vuln_section,
+ files_section: &files_section,
+ url_section: &url_section,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::models::DyadEntry;
+ use vuln_utils::Kernel;
+
+ fn create_test_kernel(_version: &str, git_id: &str) -> Kernel {
+ // In tests, we don't have real git commit IDs to look up,
+ // so we'll create dummy kernels with the provided info
+ let kernel = Kernel::from_id(git_id).unwrap_or_else(|_| Kernel::empty_kernel());
+
+ // We can't directly modify the fields, but for testing
+ // purposes, we're assuming these are valid git IDs and versions
+ kernel
+ }
+
+ #[test]
+ fn test_parse_dyad_entries() {
+ // Create test data
+ let entries = vec![
+ // Entry with both vulnerable and fixed
+ DyadEntry {
+ vulnerable: create_test_kernel("5.15", "11c52d250b34a0862edc29db03fbec23b30db6da"),
+ fixed: create_test_kernel("5.16", "2b503c8598d1b232e7fc7526bce9326d92331541"),
+ },
+ // Entry with unknown vulnerable but known fixed
+ DyadEntry {
+ vulnerable: Kernel::empty_kernel(),
+ fixed: create_test_kernel("5.10", "3b503c8598d1b232e7fc7526bce9326d92331542"),
+ },
+ // Entry with unfixed vulnerability
+ DyadEntry {
+ vulnerable: create_test_kernel("5.4", "4b503c8598d1b232e7fc7526bce9326d92331543"),
+ fixed: Kernel::empty_kernel(),
+ },
+ // Entry with same version (should be ignored)
+ DyadEntry {
+ vulnerable: create_test_kernel("6.1", "5b503c8598d1b232e7fc7526bce9326d92331544"),
+ fixed: create_test_kernel("6.1", "6b503c8598d1b232e7fc7526bce9326d92331545"),
+ },
+ ];
+
+ // With our updated Kernel implementation, the exact behavior may be different
+ // We'll check that we get at least some entries
+ let vuln_info = parse_dyad_entries(&entries);
+ assert!(!vuln_info.is_empty());
+
+ // We can't check for specific content with our test kernels,
+ // so we'll just verify that entries were generated
+
+ // Test with an array containing only same-version entries
+ let _same_version_entries = vec![DyadEntry {
+ vulnerable: create_test_kernel("6.1", "5b503c8598d1b232e7fc7526bce9326d92331544"),
+ fixed: create_test_kernel("6.1", "6b503c8598d1b232e7fc7526bce9326d92331545"),
+ }];
+
+ // Testing directly will panic, so we can't test that case directly
+ }
+
+ #[test]
+ fn test_collect_reference_urls() {
+ // Create test data
+ let entries = vec![
+ DyadEntry {
+ vulnerable: create_test_kernel("5.15", "11c52d250b34a0862edc29db03fbec23b30db6da"),
+ fixed: create_test_kernel("5.16", "22c52d250b34a0862edc29db03fbec23b30db6db"),
+ },
+ DyadEntry {
+ vulnerable: create_test_kernel("5.10", "33c52d250b34a0862edc29db03fbec23b30db6dc"),
+ fixed: create_test_kernel("5.10.1", "44c52d250b34a0862edc29db03fbec23b30db6dd"),
+ },
+ ];
+
+ let additional_refs = vec![
+ "https://example.com/ref1".to_string(),
+ "https://example.com/ref2".to_string(),
+ ];
+
+ let git_sha_full = "main_fix_id";
+
+ // Collect the references
+ let urls = collect_reference_urls(&entries, &additional_refs, git_sha_full);
+
+ // With our updated Kernel implementation, we may not get all references
+ // but we should at least get the main fix and additional refs
+ assert!(urls.len() >= 3);
+ assert!(urls.contains(&"https://git.kernel.org/stable/c/main_fix_id".to_string()));
+
+ // With our test kernels, we can't check specific git URLs
+ assert!(urls.contains(&"https://git.kernel.org/stable/c/main_fix_id".to_string()));
+ assert!(urls.contains(&"https://example.com/ref1".to_string()));
+ assert!(urls.contains(&"https://example.com/ref2".to_string()));
+
+ // Verify only that additional refs appear at the end
+ assert_eq!(urls.last(), Some(&"https://example.com/ref2".to_string()));
+ }
+
+ #[test]
+ fn test_format_mbox_sections() {
+ // Create test data
+ let vuln_array = vec![
+ "Issue introduced in 5.15 with commit 11c52d250b34a0862edc29db03fbec23b30db6da"
+ .to_string(),
+ "Fixed in 5.10 with commit 22c52d250b34a0862edc29db03fbec23b30db6db".to_string(),
+ ];
+
+ let affected_files = vec![
+ "drivers/net/ethernet/test.c".to_string(),
+ "include/linux/test.h".to_string(),
+ ];
+
+ let url_array = vec![
+ "https://git.kernel.org/stable/c/11c52d250b34a0862edc29db03fbec23b30db6da".to_string(),
+ "https://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db".to_string(),
+ ];
+
+ // Format the sections
+ let (vuln_section, files_section, url_section) =
+ format_mbox_sections(vuln_array, affected_files, url_array);
+
+ // Check that each line is properly indented with a tab
+ assert!(vuln_section.lines().all(|line| line.starts_with('\t')));
+ assert!(files_section.lines().all(|line| line.starts_with('\t')));
+ assert!(url_section.lines().all(|line| line.starts_with('\t')));
+
+ // Check that all content is present
+ assert!(vuln_section.contains("Issue introduced in 5.15"));
+ assert!(vuln_section.contains("Fixed in 5.10"));
+
+ assert!(files_section.contains("drivers/net/ethernet/test.c"));
+ assert!(files_section.contains("include/linux/test.h"));
+
+ assert!(url_section
+ .contains("https://git.kernel.org/stable/c/11c52d250b34a0862edc29db03fbec23b30db6da"));
+ assert!(url_section
+ .contains("https://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db"));
+ }
+
+ #[test]
+ fn test_create_mbox_content() {
+ // Create test parameters
+ let params = MboxContentParams {
+ from_line: "From test-script-1.0 Mon Sep 17 00:00:00 2001",
+ user_name: "Test User",
+ user_email: "test@example.com",
+ cve_number: "CVE-2023-1234",
+ commit_subject: "Test CVE",
+ commit_text: "This is a test commit message.\n\nIt contains details about the vulnerability.",
+ vuln_section: "\tIssue introduced in 5.15 with commit 11c52d250b34a0862edc29db03fbec23b30db6da\n\tFixed in 5.10 with commit 22c52d250b34a0862edc29db03fbec23b30db6db\n",
+ files_section: "\tdrivers/net/ethernet/test.c\n\tinclude/linux/test.h\n",
+ url_section: "\thttps://git.kernel.org/stable/c/11c52d250b34a0862edc29db03fbec23b30db6da\n\thttps://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db\n",
+ };
+
+ // Create the mbox content
+ let mbox = create_mbox_content(¶ms);
+
+ // Check the basic structure
+ assert!(mbox.starts_with("From test-script-1.0 Mon Sep 17 00:00:00 2001"));
+ assert!(mbox.contains("From: Test User <test@example.com>"));
+ assert!(mbox.contains("To: <linux-cve-announce@vger.kernel.org>"));
+ assert!(mbox.contains("Subject: CVE-2023-1234: Test CVE"));
+
+ // Check section headers
+ assert!(mbox.contains("Description\n==========="));
+ assert!(mbox.contains("Affected and fixed versions\n==========================="));
+ assert!(mbox.contains("Affected files\n=============="));
+ assert!(mbox.contains("Mitigation\n=========="));
+
+ // Check that the commit message is included
+ assert!(mbox.contains(
+ "This is a test commit message.\n\nIt contains details about the vulnerability."
+ ));
+
+ // Check that other sections are included
+ assert!(mbox.contains(
+ "\tIssue introduced in 5.15 with commit 11c52d250b34a0862edc29db03fbec23b30db6da"
+ ));
+ assert!(mbox.contains("\tdrivers/net/ethernet/test.c"));
+ assert!(mbox.contains(
+ "\thttps://git.kernel.org/stable/c/22c52d250b34a0862edc29db03fbec23b30db6db"
+ ));
+
+ // Check that the result ends with a newline
+ assert!(mbox.ends_with('\n'));
+ }
+}
\ No newline at end of file
diff --git a/tools/bippy/src/providers/cve/mod.rs b/tools/bippy/src/providers/cve/mod.rs
new file mode 100644
index 0000000..295ed77
--- /dev/null
+++ b/tools/bippy/src/providers/cve/mod.rs
@@ -0,0 +1,98 @@
+pub mod models;
+pub mod json;
+pub mod mbox;
+
+use anyhow::Result;
+use crate::providers::{VulnerabilityProvider, VulnerabilityRecordParams};
+
+/// CVE provider implementation
+pub struct CveProvider;
+
+impl CveProvider {
+ pub fn new() -> Self {
+ CveProvider
+ }
+}
+
+impl VulnerabilityProvider for CveProvider {
+ fn generate_json(&self, params: &VulnerabilityRecordParams) -> Result<String> {
+ // Get the UUID for this provider
+ let uuid = match self.get_org_uuid()? {
+ Some(u) => u,
+ None => return Err(anyhow::anyhow!("CVE provider requires an organization UUID")),
+ };
+
+ let cve_params = json::CveRecordParams {
+ uuid: &uuid,
+ cve_number: params.vuln_id,
+ git_sha_full: params.git_sha_full,
+ commit_subject: params.commit_subject,
+ user_name: params.user_name,
+ user_email: params.user_email,
+ dyad_entries: params.dyad_entries.clone(),
+ script_name: params.script_name,
+ script_version: params.script_version,
+ additional_references: params.additional_references,
+ commit_text: params.commit_text,
+ affected_files: params.affected_files,
+ };
+ json::generate_json(&cve_params)
+ }
+
+ fn generate_mbox(&self, params: &VulnerabilityRecordParams) -> Result<String> {
+ let mbox_params = mbox::MboxParams {
+ cve_number: params.vuln_id,
+ git_sha_full: params.git_sha_full,
+ commit_subject: params.commit_subject,
+ user_name: params.user_name,
+ user_email: params.user_email,
+ dyad_entries: ¶ms.dyad_entries,
+ script_name: params.script_name,
+ script_version: params.script_version,
+ additional_references: params.additional_references,
+ commit_text: params.commit_text,
+ affected_files: params.affected_files,
+ };
+ Ok(mbox::generate_mbox(&mbox_params))
+ }
+
+ fn name(&self) -> &'static str {
+ "CVE"
+ }
+
+ fn user_env_var(&self) -> &'static str {
+ "CVE_USER"
+ }
+
+ fn validate_id(&self, id: &str) -> Result<()> {
+ if id.starts_with("CVE-") && id.len() > 4 {
+ Ok(())
+ } else {
+ Err(anyhow::anyhow!("Invalid CVE ID format: {}. Expected format: CVE-YYYY-NNNN", id))
+ }
+ }
+
+ fn get_org_uuid(&self) -> Result<Option<String>> {
+ use crate::utils::file::read_uuid;
+ use anyhow::Context;
+
+ // Get vulns directory using vuln_utils
+ let vulns_dir = vuln_utils::find_vulns_dir()
+ .with_context(|| "Failed to find vulns directory")?;
+
+ // Get the script directory from vulns directory
+ let script_dir = vulns_dir.join("scripts");
+ if !script_dir.exists() {
+ return Err(anyhow::anyhow!(
+ "Scripts directory not found at {}",
+ script_dir.display()
+ ));
+ }
+
+ // Read the UUID from the CVE-specific linux.uuid file
+ let uuid = read_uuid(&script_dir, "linux.uuid")
+ .with_context(|| "Failed to read UUID")?;
+
+ Ok(Some(uuid))
+ }
+}
\ No newline at end of file
diff --git a/tools/bippy/src/providers/cve/models.rs b/tools/bippy/src/providers/cve/models.rs
new file mode 100644
index 0000000..0f3b4bb
--- /dev/null
+++ b/tools/bippy/src/providers/cve/models.rs
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
+
+use serde::{Deserialize, Serialize};
+use crate::providers::{CpeNodes, VersionRange};
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CveMetadata {
+ #[serde(rename = "assignerOrgId")]
+ pub assigner_org_id: String,
+ #[serde(rename = "cveID")]
+ pub cve_id: String,
+ #[serde(rename = "requesterUserId")]
+ pub requester_user_id: String,
+ pub serial: String,
+ pub state: String,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Description {
+ pub lang: String,
+ pub value: String,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ProviderMetadata {
+ #[serde(rename = "orgId")]
+ pub org_id: String,
+}
+
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct AffectedProduct {
+ pub product: String,
+ pub vendor: String,
+ #[serde(rename = "defaultStatus")]
+ pub default_status: String,
+ pub repo: String,
+ #[serde(rename = "programFiles")]
+ pub program_files: Vec<String>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub versions: Vec<VersionRange>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Reference {
+ pub url: String,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Generator {
+ pub engine: String,
+}
+
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CpeApplicability {
+ pub nodes: Vec<CpeNodes>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CnaData {
+ #[serde(rename = "providerMetadata")]
+ pub provider_metadata: ProviderMetadata,
+ pub descriptions: Vec<Description>,
+ pub affected: Vec<AffectedProduct>,
+ #[serde(rename = "cpeApplicability")]
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub cpe_applicability: Vec<CpeApplicability>,
+ pub references: Vec<Reference>,
+ pub title: String,
+ #[serde(rename = "x_generator")]
+ pub x_generator: Generator,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Containers {
+ pub cna: CnaData,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CveRecord {
+ pub containers: Containers,
+ #[serde(rename = "cveMetadata")]
+ pub cve_metadata: CveMetadata,
+ #[serde(rename = "dataType")]
+ pub data_type: String,
+ #[serde(rename = "dataVersion")]
+ pub data_version: String,
+}
diff --git a/tools/bippy/src/providers/mod.rs b/tools/bippy/src/providers/mod.rs
new file mode 100644
index 0000000..4e9559e
--- /dev/null
+++ b/tools/bippy/src/providers/mod.rs
@@ -0,0 +1,77 @@
+pub mod common;
+pub mod cve;
+
+pub use common::{CpeMatch, CpeNodes, VersionRange};
+
+use anyhow::{Result, anyhow};
+use crate::models::DyadEntry;
+
+/// Common parameters for vulnerability record generation
+pub struct VulnerabilityRecordParams<'a> {
+ /// Vulnerability identifier (e.g., "CVE-2023-12345", "GSD-2023-12345")
+ pub vuln_id: &'a str,
+ /// Full Git SHA of the commit that fixes the vulnerability
+ pub git_sha_full: &'a str,
+ /// Subject line of the commit
+ pub commit_subject: &'a str,
+ /// Name of the user creating the record
+ pub user_name: &'a str,
+ /// Email of the user creating the record
+ pub user_email: &'a str,
+ /// Dyad entries containing vulnerability and fix information
+ pub dyad_entries: Vec<DyadEntry>,
+ /// Name of the script generating the record
+ pub script_name: &'a str,
+ /// Version of the script generating the record
+ pub script_version: &'a str,
+ /// Additional reference URLs
+ pub additional_references: &'a [String],
+ /// Full commit text/description
+ pub commit_text: &'a str,
+ /// List of affected files
+ pub affected_files: &'a Vec<String>,
+}
+
+/// Trait for vulnerability providers (CVE, GSD, EUVD, etc.)
+#[allow(dead_code)]
+pub trait VulnerabilityProvider {
+ /// Generate JSON record for the vulnerability
+ fn generate_json(&self, params: &VulnerabilityRecordParams) -> Result<String>;
+
+ /// Generate mbox announcement for the vulnerability
+ fn generate_mbox(&self, params: &VulnerabilityRecordParams) -> Result<String>;
+
+ /// Get the provider name
+ fn name(&self) -> &'static str;
+
+ /// Get the environment variable name for user configuration
+ fn user_env_var(&self) -> &'static str;
+
+ /// Validate the vulnerability ID format
+ fn validate_id(&self, id: &str) -> Result<()>;
+
+ /// Get the organization UUID if required by the provider
+ /// Returns None if the provider doesn't use UUIDs
+ fn get_org_uuid(&self) -> Result<Option<String>> {
+ Ok(None)
+ }
+}
+
+/// Factory for creating vulnerability providers
+pub struct ProviderFactory;
+
+impl ProviderFactory {
+ /// Create a provider by name
+ pub fn create(provider_type: &str) -> Result<Box<dyn VulnerabilityProvider>> {
+ match provider_type.to_lowercase().as_str() {
+ "cve" => Ok(Box::new(cve::CveProvider::new())),
+ _ => Err(anyhow!("Unknown provider type: {}", provider_type)),
+ }
+ }
+
+ /// Get list of available providers
+ #[allow(dead_code)]
+ pub fn available_providers() -> Vec<&'static str> {
+ vec!["cve"]
+ }
+}
\ No newline at end of file
diff --git a/tools/bippy/src/utils/dyad.rs b/tools/bippy/src/utils/dyad.rs
index 912f972..26d5584 100644
--- a/tools/bippy/src/utils/dyad.rs
+++ b/tools/bippy/src/utils/dyad.rs
@@ -27,6 +27,7 @@
std::env::set_current_dir(script_dir)?;
// Get kernel tree paths from environment variables
+ // Note: CVEKERNELTREE is a legacy environment variable name that is kept for compatibility
let kernel_tree = std::env::var("CVEKERNELTREE")
.with_context(|| "CVEKERNELTREE environment variable is not set")?;
@@ -34,6 +35,7 @@
let mut command = std::process::Command::new(&dyad_script);
// Set environment variables
+ // Note: CVEKERNELTREE is a legacy environment variable name that is kept for compatibility
command.env("CVEKERNELTREE", &kernel_tree);
// Add each vulnerable SHA as a separate -v argument
diff --git a/tools/bippy/src/utils/file.rs b/tools/bippy/src/utils/file.rs
index 62d1304..8ed39a0 100644
--- a/tools/bippy/src/utils/file.rs
+++ b/tools/bippy/src/utils/file.rs
@@ -18,9 +18,9 @@
.collect())
}
-/// Read the UUID for the Linux kernel CVE team from a file
-pub fn read_uuid(script_dir: &Path) -> Result<String> {
- let uuid_path = script_dir.join("linux.uuid");
+/// Read a UUID from a file
+pub fn read_uuid(script_dir: &Path, uuid_filename: &str) -> Result<String> {
+ let uuid_path = script_dir.join(uuid_filename);
let content = std::fs::read_to_string(&uuid_path)
.with_context(|| format!("Failed to read UUID file at {}", uuid_path.display()))?;
diff --git a/tools/bippy/src/utils/mod.rs b/tools/bippy/src/utils/mod.rs
index abcf91f..1cfa285 100644
--- a/tools/bippy/src/utils/mod.rs
+++ b/tools/bippy/src/utils/mod.rs
@@ -9,7 +9,7 @@
pub mod version;
pub use dyad::run_dyad;
-pub use file::{read_tags_file, read_uuid};
+pub use file::read_tags_file;
pub use git::{apply_diff_to_text, get_commit_subject, get_commit_text};
pub use text::strip_commit_text;
pub use version::{
diff --git a/tools/bippy/src/utils/version.rs b/tools/bippy/src/utils/version.rs
index b03ab24..663f8a7 100644
--- a/tools/bippy/src/utils/version.rs
+++ b/tools/bippy/src/utils/version.rs
@@ -3,13 +3,13 @@
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
use crate::models::dyad::DyadEntry;
-use crate::models::{CpeMatch, CpeNodes, VersionRange};
-use cve_utils::version_utils::compare_kernel_versions;
-use cve_utils::version_utils::version_is_mainline;
+use crate::providers::{CpeMatch, CpeNodes, VersionRange};
+use vuln_utils::version_utils::compare_kernel_versions;
+use vuln_utils::version_utils::version_is_mainline;
use log::debug;
use std::collections::HashSet;
-/// Determine the default status for CVE entries based on the dyad entries
+/// Determine the default status for vulnerability entries based on the dyad entries
pub fn determine_default_status(entries: &[DyadEntry]) -> &'static str {
// If any entry has vulnerable_version = 0, status should be "affected"
if entries.iter().any(|entry| entry.vulnerable.is_empty()) {
@@ -28,7 +28,7 @@
"unaffected"
}
-/// Generate CPE ranges for the CVE JSON format
+/// Generate CPE ranges for vulnerability records
pub fn generate_cpe_ranges(entries: &[DyadEntry]) -> Vec<CpeNodes> {
let mut cpe_nodes: Vec<CpeNodes> = vec![];
let mut node = CpeNodes {
@@ -39,8 +39,8 @@
for entry in entries {
// Skip entries where the vulnerability is in the same version it was fixed
- // These versions are not actually affected in any released version so CVE.org
- // doesn't like to see them.
+ // These versions are not actually affected in any released version so vulnerability databases
+ // don't like to see them.
if entry.is_same_version() {
continue;
}
@@ -66,7 +66,7 @@
cpe_nodes
}
-/// Generate git ranges for the CVE JSON format
+/// Generate git ranges for vulnerability records
pub fn generate_git_ranges(entries: &[DyadEntry]) -> Vec<VersionRange> {
let mut git_versions = Vec::new();
@@ -97,7 +97,7 @@
git_versions
}
-/// Generate version ranges for the CVE JSON format
+/// Generate version ranges for vulnerability records
pub fn generate_version_ranges(entries: &[DyadEntry], default_status: &str) -> Vec<VersionRange> {
let mut kernel_versions = Vec::new();
let mut seen_versions = HashSet::new();
diff --git a/tools/cve_classifier/src/main.rs b/tools/cve_classifier/src/main.rs
index dee9b51..cf0f976 100644
--- a/tools/cve_classifier/src/main.rs
+++ b/tools/cve_classifier/src/main.rs
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
// (c) 2025, Sasha Levin <sashal@kernel.org>
+extern crate vuln_utils;
+
use clap::{Arg, ArgAction, Command};
use log::{info, debug, error, warn};
use std::collections::HashMap;
diff --git a/tools/cve_classifier/src/utils.rs b/tools/cve_classifier/src/utils.rs
index 6b0ca8a..4cea584 100644
--- a/tools/cve_classifier/src/utils.rs
+++ b/tools/cve_classifier/src/utils.rs
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// (c) 2025, Sasha Levin <sashal@kernel.org>
-pub use cve_utils::get_cve_root;
+pub use vuln_utils::get_cve_root;
// Setup logging with optional debug level
pub fn setup_logging(debug: bool, batch_mode: bool) {
diff --git a/tools/dyad/src/kernel/kernel_pairs.rs b/tools/dyad/src/kernel/kernel_pairs.rs
index d2f8a44..5ef3a1d 100644
--- a/tools/dyad/src/kernel/kernel_pairs.rs
+++ b/tools/dyad/src/kernel/kernel_pairs.rs
@@ -5,7 +5,7 @@
//
use crate::state::DyadState;
-use cve_utils::{Kernel, KernelPair};
+use vuln_utils::{Kernel, KernelPair};
use log::debug;
use owo_colors::{OwoColorize, Stream::Stdout};
use std::cmp::Ordering;
diff --git a/tools/dyad/src/kernel/sha_processing.rs b/tools/dyad/src/kernel/sha_processing.rs
index 0872a49..731fc08 100644
--- a/tools/dyad/src/kernel/sha_processing.rs
+++ b/tools/dyad/src/kernel/sha_processing.rs
@@ -5,7 +5,7 @@
//
use crate::state::DyadState;
-use cve_utils::Kernel;
+use vuln_utils::Kernel;
/// Adds a git SHA to the state's list of fixing kernels
pub fn process_fixing_sha(state: &mut DyadState, git_sha: &str) -> bool {
@@ -19,8 +19,8 @@
// Sometimes the git id is in stable kernels but is NOT in a released Linus tree
// just yet, so verhaal will not have the data. So let's check the git repo to see
// if that's the case
- if let Ok(path) = cve_utils::common::get_kernel_tree() {
- if let Ok(git_sha_full) = cve_utils::get_full_sha(&path, git_sha) {
+ if let Ok(path) = vuln_utils::common::get_kernel_tree() {
+ if let Ok(git_sha_full) = vuln_utils::get_full_sha(&path, git_sha) {
// It is valid, so let's make an "empty" kernel object and fill it in by hand
// without a valid version number just yet.
if let Ok(kernel) = Kernel::from_id(&git_sha_full) {
diff --git a/tools/dyad/src/kernel/vulnerability.rs b/tools/dyad/src/kernel/vulnerability.rs
index b865f5f..4cf6652 100644
--- a/tools/dyad/src/kernel/vulnerability.rs
+++ b/tools/dyad/src/kernel/vulnerability.rs
@@ -5,7 +5,7 @@
//
use crate::state::{found_in, DyadState};
-use cve_utils::Kernel;
+use vuln_utils::Kernel;
use log::{debug, error};
use owo_colors::{OwoColorize, Stream::Stdout};
use std::cmp::Ordering;
diff --git a/tools/dyad/src/main.rs b/tools/dyad/src/main.rs
index bd4d01b..8fd2417 100644
--- a/tools/dyad/src/main.rs
+++ b/tools/dyad/src/main.rs
@@ -14,7 +14,7 @@
use log::{debug, error};
use owo_colors::{OwoColorize, Stream::Stdout};
use std::env;
-extern crate cve_utils;
+extern crate vuln_utils;
mod cli;
mod kernel;
diff --git a/tools/dyad/src/state/mod.rs b/tools/dyad/src/state/mod.rs
index c11125a..2c8fab9 100644
--- a/tools/dyad/src/state/mod.rs
+++ b/tools/dyad/src/state/mod.rs
@@ -4,8 +4,8 @@
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
//
-use cve_utils::Kernel;
-use cve_utils::Verhaal;
+use vuln_utils::Kernel;
+use vuln_utils::Verhaal;
use log::debug;
/// State for dyad tool runtime
@@ -40,8 +40,8 @@
/// Validates and sets up environment variables for the `DyadState`
pub fn validate_env_vars(state: &mut DyadState) {
- // Use cve_utils to get kernel tree path
- match cve_utils::common::get_kernel_tree() {
+ // Use vuln_utils to get kernel tree path
+ match vuln_utils::common::get_kernel_tree() {
Ok(path) => state.kernel_tree = path.to_string_lossy().into_owned(),
Err(e) => panic!("Failed to get kernel tree: {e}"),
}
diff --git a/tools/dyad/tests/integration.rs b/tools/dyad/tests/integration.rs
index 6f74fe0..a9a1a33 100644
--- a/tools/dyad/tests/integration.rs
+++ b/tools/dyad/tests/integration.rs
@@ -228,9 +228,9 @@
}
}
-/// Find the CVE directory using cve_utils
+/// Find the CVE directory using vuln_utils
fn find_cve_dir() -> Result<PathBuf, String> {
- match cve_utils::common::find_vulns_dir() {
+ match vuln_utils::common::find_vulns_dir() {
Ok(vulns_dir) => {
let cve_dir = vulns_dir.join("cve");
if cve_dir.exists() {
diff --git a/tools/voting_results/src/main.rs b/tools/voting_results/src/main.rs
index 2353b6c..bfd83b4 100644
--- a/tools/voting_results/src/main.rs
+++ b/tools/voting_results/src/main.rs
@@ -2,6 +2,8 @@
//
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
+extern crate vuln_utils;
+
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use git2::Repository;
@@ -94,8 +96,8 @@
}
}
- // Use the standard cve_utils implementation to find the vulns directory
- let vulns_dir = cve_utils::find_vulns_dir()?;
+ // Use the standard vuln_utils implementation to find the vulns directory
+ let vulns_dir = vuln_utils::find_vulns_dir()?;
let proposed_dir = vulns_dir.join("cve").join("review").join("proposed");
let script_dir = vulns_dir.join("scripts");
@@ -346,7 +348,7 @@
.path()
.parent()
.unwrap_or_else(|| self.repo.path());
- match cve_utils::git_utils::get_full_sha(repo_path, short_stable_sha) {
+ match vuln_utils::git_utils::get_full_sha(repo_path, short_stable_sha) {
Ok(full_sha) => match git2::Oid::from_str(&full_sha) {
Ok(oid) => self.repo.find_commit(oid),
Err(_) => return stable_sha.to_string(),
diff --git a/tools/cve_utils/.gitignore b/tools/vuln_utils/.gitignore
similarity index 100%
rename from tools/cve_utils/.gitignore
rename to tools/vuln_utils/.gitignore
diff --git a/tools/cve_utils/src/bin/cve_create.rs b/tools/vuln_utils/src/bin/cve_create.rs
similarity index 97%
rename from tools/cve_utils/src/bin/cve_create.rs
rename to tools/vuln_utils/src/bin/cve_create.rs
index 70371ab..aa610c1 100644
--- a/tools/cve_utils/src/bin/cve_create.rs
+++ b/tools/vuln_utils/src/bin/cve_create.rs
@@ -5,11 +5,11 @@
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use owo_colors::OwoColorize;
-use cve_utils::common::{find_cve_by_sha, get_cve_root, get_kernel_tree};
-use cve_utils::cve_utils::find_next_free_cve_id;
-use cve_utils::git_utils::get_full_sha;
-use cve_utils::git_utils::{get_commit_details, get_commit_year};
-use cve_utils::print_git_error_details;
+use vuln_utils::common::{find_cve_by_sha, get_cve_root, get_kernel_tree};
+use vuln_utils::vuln_utils::find_next_free_cve_id;
+use vuln_utils::git_utils::get_full_sha;
+use vuln_utils::git_utils::{get_commit_details, get_commit_year};
+use vuln_utils::print_git_error_details;
use log::error;
use std::fs;
use std::io::{BufRead, BufReader};
@@ -228,7 +228,7 @@
let mbox_file = published_dir.join(format!("{cve_id}.mbox"));
// Build bippy command with full path from vulns dir
- let vulns_dir = match cve_utils::common::find_vulns_dir() {
+ let vulns_dir = match vuln_utils::common::find_vulns_dir() {
Ok(dir) => dir,
Err(e) => return Err(anyhow!("Failed to find vulns directory: {}", e)),
};
diff --git a/tools/cve_utils/src/bin/cve_publish.rs b/tools/vuln_utils/src/bin/cve_publish.rs
similarity index 94%
rename from tools/cve_utils/src/bin/cve_publish.rs
rename to tools/vuln_utils/src/bin/cve_publish.rs
index 689093e..b6a1596 100644
--- a/tools/cve_utils/src/bin/cve_publish.rs
+++ b/tools/vuln_utils/src/bin/cve_publish.rs
@@ -5,10 +5,10 @@
use anyhow::{Context, Result};
use clap::Parser;
use owo_colors::OwoColorize;
-use ::cve_utils::common;
-use ::cve_utils::git_utils;
-use ::cve_utils::cve_utils;
-use ::cve_utils::print_git_error_details;
+use ::vuln_utils::common;
+use ::vuln_utils::git_utils;
+use ::vuln_utils::vuln_utils;
+use ::vuln_utils::print_git_error_details;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
@@ -130,7 +130,7 @@
// Process each file
for file_path in &modified_files {
// Extract CVE ID from the file path
- let cve_id = cve_utils::extract_cve_id_from_path(file_path)?;
+ let cve_id = vuln_utils::extract_cve_id_from_path(file_path)?;
// Get the associated SHA1 file
let sha1_file = file_path.with_extension("sha1");
@@ -193,7 +193,7 @@
// Print list of files that will be sent
for file_path in &mbox_files {
// Extract CVE ID from the file path
- let cve_id = cve_utils::extract_cve_id_from_path(file_path)?;
+ let cve_id = vuln_utils::extract_cve_id_from_path(file_path)?;
// Get the associated SHA1 file
let sha1_file = file_path.with_extension("sha1");
@@ -264,17 +264,17 @@
// Test with a JSON file
let json_path = temp_path.join(format!("{}.json", cve_id));
fs::write(&json_path, "test content").unwrap();
- assert_eq!(cve_utils::extract_cve_id_from_path(&json_path).unwrap(), cve_id);
+ assert_eq!(vuln_utils::extract_cve_id_from_path(&json_path).unwrap(), cve_id);
// Test with a mbox file
let mbox_path = temp_path.join(format!("{}.mbox", cve_id));
fs::write(&mbox_path, "test content").unwrap();
- assert_eq!(cve_utils::extract_cve_id_from_path(&mbox_path).unwrap(), cve_id);
+ assert_eq!(vuln_utils::extract_cve_id_from_path(&mbox_path).unwrap(), cve_id);
// Test with a rejected mbox file
let rejected_path = temp_path.join(format!("{}.mbox.rejected", cve_id));
fs::write(&rejected_path, "test content").unwrap();
- assert_eq!(cve_utils::extract_cve_id_from_path(&rejected_path).unwrap(), cve_id);
+ assert_eq!(vuln_utils::extract_cve_id_from_path(&rejected_path).unwrap(), cve_id);
}
#[test]
diff --git a/tools/cve_utils/src/bin/cve_reject.rs b/tools/vuln_utils/src/bin/cve_reject.rs
similarity index 98%
rename from tools/cve_utils/src/bin/cve_reject.rs
rename to tools/vuln_utils/src/bin/cve_reject.rs
index 4673b2b..c17acf4 100644
--- a/tools/cve_utils/src/bin/cve_reject.rs
+++ b/tools/vuln_utils/src/bin/cve_reject.rs
@@ -5,14 +5,14 @@
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use owo_colors::OwoColorize;
-use cve_utils::common;
-use cve_utils::cve_validation;
-use cve_utils::git_config;
-use cve_utils::print_git_error_details;
+use vuln_utils::common;
+use vuln_utils::cve_validation;
+use vuln_utils::git_config;
+use vuln_utils::print_git_error_details;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
-use cve_utils::cve_utils::extract_cve_id_from_path;
+use vuln_utils::vuln_utils::extract_cve_id_from_path;
/// Reject a reserved or published CVE entry
#[derive(Parser, Debug)]
diff --git a/tools/cve_utils/src/bin/cve_review.rs b/tools/vuln_utils/src/bin/cve_review.rs
similarity index 99%
rename from tools/cve_utils/src/bin/cve_review.rs
rename to tools/vuln_utils/src/bin/cve_review.rs
index 60aa099..8806f9b 100644
--- a/tools/cve_utils/src/bin/cve_review.rs
+++ b/tools/vuln_utils/src/bin/cve_review.rs
@@ -5,9 +5,9 @@
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use owo_colors::OwoColorize;
-use cve_utils::common;
-use cve_utils::print_git_error_details;
-use cve_utils::git_utils;
+use vuln_utils::common;
+use vuln_utils::print_git_error_details;
+use vuln_utils::git_utils;
use dialoguer::Input;
use regex::Regex;
use std::cmp::min;
@@ -17,7 +17,7 @@
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
-use cve_utils::cve_utils::extract_cve_id_from_path;
+use vuln_utils::vuln_utils::extract_cve_id_from_path;
use walkdir::WalkDir;
use grep::regex::RegexMatcher;
use grep::searcher::sinks::UTF8;
diff --git a/tools/cve_utils/src/bin/cve_search.rs b/tools/vuln_utils/src/bin/cve_search.rs
similarity index 95%
rename from tools/cve_utils/src/bin/cve_search.rs
rename to tools/vuln_utils/src/bin/cve_search.rs
index c9f454e..795bb08 100644
--- a/tools/cve_utils/src/bin/cve_search.rs
+++ b/tools/vuln_utils/src/bin/cve_search.rs
@@ -5,8 +5,8 @@
use anyhow::{anyhow, Result};
use clap::Parser;
use owo_colors::OwoColorize;
-use cve_utils::common;
-use cve_utils::print_git_error_details;
+use vuln_utils::common;
+use vuln_utils::print_git_error_details;
/// Search the published CVE records for a git SHA or find a git SHA associated with a CVE ID
#[derive(Parser, Debug)]
@@ -44,7 +44,7 @@
};
// Try to interpret the search string as a git SHA first
- if let Ok(_git_sha_full) = cve_utils::get_full_sha(&kernel_tree, &args.search_string) {
+ if let Ok(_git_sha_full) = vuln_utils::get_full_sha(&kernel_tree, &args.search_string) {
// It's a valid SHA, search for it in the CVE records
if let Some(cve) = common::find_cve_by_sha(&cve_root, &args.search_string) {
println!("{} is assigned to git id {}",
@@ -70,7 +70,7 @@
#[cfg(test)]
mod tests {
- use cve_utils::common;
+ use vuln_utils::common;
#[test]
fn test_real_cve_lookup_by_id() {
diff --git a/tools/cve_utils/src/bin/cve_stats.rs b/tools/vuln_utils/src/bin/cve_stats.rs
similarity index 98%
rename from tools/cve_utils/src/bin/cve_stats.rs
rename to tools/vuln_utils/src/bin/cve_stats.rs
index b1a569c..f8a27b1 100644
--- a/tools/cve_utils/src/bin/cve_stats.rs
+++ b/tools/vuln_utils/src/bin/cve_stats.rs
@@ -13,9 +13,9 @@
use std::str;
use walkdir::WalkDir;
use git2::{Repository, Oid};
-use cve_utils::{
+use vuln_utils::{
git_utils::{self, resolve_reference},
- cve_utils::extract_cve_id_from_path,
+ vuln_utils::extract_cve_id_from_path,
};
#[derive(Parser)]
@@ -149,7 +149,7 @@
eprintln!("Error: {e}");
// Provide additional context for git errors
- cve_utils::print_git_error_details(&e);
+ vuln_utils::print_git_error_details(&e);
std::process::exit(1);
}
@@ -316,7 +316,7 @@
if let Ok(affected_files) = git_utils::get_affected_files(&repo, &obj) {
for file_path in affected_files {
if file_path.starts_with("cve/published/") {
- // Use the consolidated function from cve_utils
+ // Use the consolidated function from vuln_utils
if let Ok(cve_id) = extract_cve_id_from_path(&file_path) {
unique_cves.insert(cve_id);
}
@@ -545,7 +545,7 @@
}
};
- // Get affected files using the cve_utils module - more efficient than doing diff traversal manually
+ // Get affected files using the vuln_utils module - more efficient than doing diff traversal manually
let affected_files = match git_utils::get_affected_files(&repo, &obj) {
Ok(files) => files,
Err(_) => {
diff --git a/tools/cve_utils/src/bin/cve_update.rs b/tools/vuln_utils/src/bin/cve_update.rs
similarity index 99%
rename from tools/cve_utils/src/bin/cve_update.rs
rename to tools/vuln_utils/src/bin/cve_update.rs
index bf05fa6..187a516 100644
--- a/tools/cve_utils/src/bin/cve_update.rs
+++ b/tools/vuln_utils/src/bin/cve_update.rs
@@ -3,10 +3,10 @@
// Copyright (c) 2025 - Sasha Levin <sashal@kernel.org>
use anyhow::{anyhow, Context, Result};
-use cve_utils::print_git_error_details;
-use cve_utils::cve_validation::find_cve_id;
-use cve_utils::year_utils::{is_valid_year, is_year_dir_exists};
-use cve_utils::common;
+use vuln_utils::print_git_error_details;
+use vuln_utils::cve_validation::find_cve_id;
+use vuln_utils::year_utils::{is_valid_year, is_year_dir_exists};
+use vuln_utils::common;
use std::path::{Path, PathBuf};
use std::thread;
use std::sync::{Arc, Mutex};
@@ -530,7 +530,7 @@
use tempfile::tempdir;
use std::io::Write;
use std::fs::File;
- use cve_utils::cve_validation::extract_year_from_cve;
+ use vuln_utils::cve_validation::extract_year_from_cve;
#[test]
fn test_is_valid_year() {
diff --git a/tools/cve_utils/src/bin/score.rs b/tools/vuln_utils/src/bin/score.rs
similarity index 99%
rename from tools/cve_utils/src/bin/score.rs
rename to tools/vuln_utils/src/bin/score.rs
index fd8104d..aa2e077 100644
--- a/tools/cve_utils/src/bin/score.rs
+++ b/tools/vuln_utils/src/bin/score.rs
@@ -4,8 +4,8 @@
use anyhow::{anyhow, Result};
use clap::Parser;
-use cve_utils::common;
-use cve_utils::print_git_error_details;
+use vuln_utils::common;
+use vuln_utils::print_git_error_details;
use indicatif::{ProgressBar, ProgressStyle};
use rayon::prelude::*;
use std::collections::{HashMap, HashSet};
diff --git a/tools/cve_utils/src/bin/strak.rs b/tools/vuln_utils/src/bin/strak.rs
similarity index 99%
rename from tools/cve_utils/src/bin/strak.rs
rename to tools/vuln_utils/src/bin/strak.rs
index 2435f23..c4dd447 100644
--- a/tools/cve_utils/src/bin/strak.rs
+++ b/tools/vuln_utils/src/bin/strak.rs
@@ -5,8 +5,8 @@
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use owo_colors::OwoColorize;
-use cve_utils::common;
-use cve_utils::print_git_error_details;
+use vuln_utils::common;
+use vuln_utils::print_git_error_details;
use rayon::prelude::*;
use std::env;
use std::fs;
diff --git a/tools/cve_utils/src/bin/update_dyad.rs b/tools/vuln_utils/src/bin/update_dyad.rs
similarity index 98%
rename from tools/cve_utils/src/bin/update_dyad.rs
rename to tools/vuln_utils/src/bin/update_dyad.rs
index 08e37dc..54c64bd 100644
--- a/tools/cve_utils/src/bin/update_dyad.rs
+++ b/tools/vuln_utils/src/bin/update_dyad.rs
@@ -5,8 +5,8 @@
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use owo_colors::OwoColorize;
-use cve_utils::common;
-use cve_utils::print_git_error_details;
+use vuln_utils::common;
+use vuln_utils::print_git_error_details;
use indicatif::{ProgressBar, ProgressStyle};
use rayon::prelude::*;
use std::env;
@@ -18,7 +18,7 @@
use std::sync::Arc;
use tempfile::NamedTempFile;
use walkdir::WalkDir;
-use cve_utils::cve_utils::extract_cve_id_from_path;
+use vuln_utils::vuln_utils::extract_cve_id_from_path;
/// Update all .dyad files in the tree.
///
@@ -338,7 +338,7 @@
mod tests {
use super::*;
use tempfile::tempdir;
- use cve_utils::cve_utils::extract_cve_id_from_path;
+ use vuln_utils::vuln_utils::extract_cve_id_from_path;
#[test]
fn test_extract_cve_id_from_path() {
diff --git a/tools/cve_utils/src/kernel.rs b/tools/vuln_utils/src/kernel.rs
similarity index 99%
rename from tools/cve_utils/src/kernel.rs
rename to tools/vuln_utils/src/kernel.rs
index a96314a..b9d173c 100644
--- a/tools/cve_utils/src/kernel.rs
+++ b/tools/vuln_utils/src/kernel.rs
@@ -120,7 +120,7 @@
fn git_dir() -> &'static String {
GIT_DIR.get_or_init(|| {
- // Use cve_utils to get and validate the kernel tree path
+ // Use vuln_utils to get and validate the kernel tree path
match common::get_kernel_tree() {
Ok(path) => path.to_string_lossy().into_owned(),
Err(e) => panic!("Failed to get kernel tree: {e}"),
diff --git a/tools/cve_utils/src/lib.rs b/tools/vuln_utils/src/lib.rs
similarity index 99%
rename from tools/cve_utils/src/lib.rs
rename to tools/vuln_utils/src/lib.rs
index 8169191..ea838ea 100644
--- a/tools/cve_utils/src/lib.rs
+++ b/tools/vuln_utils/src/lib.rs
@@ -26,7 +26,7 @@
git_sort_ids, match_pattern, print_git_error_details, resolve_reference,
};
// CVE file operations
-pub use self::cve_utils::{extract_cve_id_from_path, find_next_free_cve_id};
+pub use self::vuln_utils::{extract_cve_id_from_path, find_next_free_cve_id};
// Git configuration utilities
pub use self::git_config::{get_git_config, set_git_config};
// CVE validation and processing
@@ -711,7 +711,7 @@
}
/// CVE file operations commonly used across tools
-pub mod cve_utils {
+pub mod vuln_utils {
use anyhow::{anyhow, Context, Result};
use std::fs;
use std::path::Path;
diff --git a/tools/cve_utils/src/verhaal.rs b/tools/vuln_utils/src/verhaal.rs
similarity index 100%
rename from tools/cve_utils/src/verhaal.rs
rename to tools/vuln_utils/src/verhaal.rs
diff --git a/tools/cve_utils/src/version_utils.rs b/tools/vuln_utils/src/version_utils.rs
similarity index 100%
rename from tools/cve_utils/src/version_utils.rs
rename to tools/vuln_utils/src/version_utils.rs