diff --git a/src/assail/mod.rs b/src/assail/mod.rs index 0f985d1..ee63268 100644 --- a/src/assail/mod.rs +++ b/src/assail/mod.rs @@ -225,11 +225,25 @@ pub fn load_user_classifications(project_root: &Path) -> Vec .join("assail-classifications.a2ml"), project_root.join(".panic-attack-classifications.a2ml"), ]; + // User-classification a2ml files are hand-edited audit registries. A + // legitimate one rarely exceeds a few dozen KiB. Capping at 4 MiB + // stops a malicious or accidental input from exhausting memory during + // a multi-thousand-repo mass-panic sweep. + use std::io::Read; + const CLASSIFICATIONS_FILE_READ_LIMIT: u64 = 4 * 1024 * 1024; + let mut content = String::new(); for p in &candidate_paths { - if let Ok(c) = fs::read_to_string(p) { - content = c; - break; + if let Ok(mut f) = fs::File::open(p) { + let mut buf = String::new(); + if (&mut f) + .take(CLASSIFICATIONS_FILE_READ_LIMIT) + .read_to_string(&mut buf) + .is_ok() + { + content = buf; + break; + } } } if content.is_empty() { diff --git a/src/assemblyline.rs b/src/assemblyline.rs index 9749ef4..e2481c9 100644 --- a/src/assemblyline.rs +++ b/src/assemblyline.rs @@ -115,9 +115,20 @@ impl FingerprintCache { Ok(()) } - /// Load fingerprint cache from a standalone cache JSON file + /// Load fingerprint cache from a standalone cache JSON file. + /// + /// Bounded at 256 MiB — fingerprint caches grow with the size of the + /// estate being scanned, but even an aggressive multi-thousand-repo + /// rollup should land well under this cap. A larger file is almost + /// certainly corrupted or hostile. pub fn load_cache_file(path: &Path) -> Result { - let content = fs::read_to_string(path)?; + use std::io::Read; + const CACHE_FILE_READ_LIMIT: u64 = 256 * 1024 * 1024; + + let mut content = String::new(); + fs::File::open(path)? + .take(CACHE_FILE_READ_LIMIT) + .read_to_string(&mut content)?; let cache: Self = serde_json::from_str(&content)?; Ok(cache) } diff --git a/src/attestation/mod.rs b/src/attestation/mod.rs index 075b902..154ec9a 100644 --- a/src/attestation/mod.rs +++ b/src/attestation/mod.rs @@ -67,8 +67,19 @@ pub enum VerifyResult { /// with a list of failure reasons otherwise. pub fn verify_attestation_file(path: &std::path::Path) -> anyhow::Result { use sha2::{Digest, Sha256}; - - let content = std::fs::read_to_string(path) + use std::io::Read; + + // Attestation envelopes are JSON. They embed three hashes plus a small + // signature; legitimate envelopes are well under 1 MiB. Capping at + // 16 MiB stops a malicious or accidental input from exhausting memory + // before verification can even begin. + const ATTESTATION_FILE_READ_LIMIT: u64 = 16 * 1024 * 1024; + + let mut content = String::new(); + std::fs::File::open(path) + .map_err(|e| anyhow::anyhow!("opening {}: {}", path.display(), e))? + .take(ATTESTATION_FILE_READ_LIMIT) + .read_to_string(&mut content) .map_err(|e| anyhow::anyhow!("reading {}: {}", path.display(), e))?; let envelope: A2mlEnvelope = serde_json::from_str(&content)