From be9a879e5111ef2ad133b442a1d379d85bac9086 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Fri, 9 Oct 2026 21:11:39 +0700 Subject: [PATCH] Fonts: cap the total a suitcase fork can make the scan copy; no fork look-up off macOS References can share data, so a damaged fork could make the scan copy one large resource up to 256 times: cap the total copied like the fork itself. Off macOS no file has a fork, so the scan doesn't look at each font file twice. Co-Authored-By: Claude Opus 5.5 --- crates/text/src/suitcase.rs | 12 ++++++++---- crates/text/src/tests_sysfonts.rs | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/text/src/suitcase.rs b/crates/text/src/suitcase.rs index 78c93c5e6..0de80f2a5 100644 --- a/crates/text/src/suitcase.rs +++ b/crates/text/src/suitcase.rs @@ -35,6 +35,7 @@ fn collect<'a>(fork: &'a [u8], fonts: &mut Vec<&'a [u8]>) -> Option<()> { let types = be16(map, 24)?; // Counts are stored less one, so a list of none is 0xFFFF. let n_types = (be16(map, types)? + 1) & 0xFFFF; + let mut total = 0usize; for t in 0..n_types.min(MAX_TYPES) { let at = types + 2 + t * 8; if map.get(at..at + 4)? != b"sfnt" { @@ -44,11 +45,13 @@ fn collect<'a>(fork: &'a [u8], fonts: &mut Vec<&'a [u8]>) -> Option<()> { // Each reference: its data's offset is the low three bytes of the word 4 bytes in. let refs = types + be16(map, at + 6)?; for r in 0..count { - if fonts.len() >= MAX_FONTS { - return Some(()); - } let start = data.checked_add(be32(map, refs + r * 12 + 4)? & 0x00FF_FFFF)?; let len = be32(fork, start)?; + // References can share data: cap the total too, as every font is copied out. + total = total.checked_add(len)?; + if fonts.len() >= MAX_FONTS || total as u64 > MAX_FORK { + return Some(()); + } fonts.push(fork.get(start + 4..start.checked_add(4)?.checked_add(len)?)?); } } @@ -65,7 +68,8 @@ pub(crate) fn read_fonts(path: &Path) -> Vec> { /// Whether the file at `path` is a suitcase font: nothing in its data fork and something in its /// resource fork. pub(crate) fn is_suitcase(path: &Path) -> bool { - std::fs::metadata(path).is_ok_and(|m| m.is_file() && m.len() == 0) && fork_len(path) > 0 + // Off macOS no file has a fork: don't look at every font file twice. + cfg!(target_os = "macos") && std::fs::metadata(path).is_ok_and(|m| m.is_file() && m.len() == 0) && fork_len(path) > 0 } #[cfg(target_os = "macos")] diff --git a/crates/text/src/tests_sysfonts.rs b/crates/text/src/tests_sysfonts.rs index c1c2c5b1e..262884425 100644 --- a/crates/text/src/tests_sysfonts.rs +++ b/crates/text/src/tests_sysfonts.rs @@ -274,6 +274,8 @@ fn a_resource_fork_gives_up_its_font_files() { let _ = crate::suitcase::sfnt_resources(&fork[..cut]); } assert!(crate::suitcase::sfnt_resources(&[0xFF; 600]).is_empty()); + // A fork can't make the scan read more than so many fonts. + assert_eq!(crate::suitcase::sfnt_resources(&crate::suitcase::fork_of(&vec![b"font".to_vec(); 300])).len(), 256); } /// Office's fonts: a file with nothing in it but a resource fork holding the fonts, named