font-catalog: cap the font file read where the file has grown since its size was checked (#873)

This commit is contained in:
Dylan M. Taylor
2026-11-11 19:20:26 -05:00
committed by GitHub
parent 4559fe4171
commit 23a63f5c76
2 changed files with 26 additions and 1 deletions
+14 -1
View File
@@ -22,6 +22,19 @@ const MAX_SCAN_READ: u64 = 256 << 20;
/// The largest font file loaded.
const MAX_LOAD: u64 = 512 << 20;
/// The bytes of the file at `path`, at most `max`: the size was checked before, but a file can
/// grow between that check and the read, so the read itself stops one byte past the limit.
pub(crate) fn read_capped(path: &Path, max: u64) -> Result<Vec<u8>, FontFileError> {
use std::io::Read as _;
let mut bytes = Vec::new();
std::fs::File::open(path)?.take(max.saturating_add(1)).read_to_end(&mut bytes)?;
let size = bytes.len() as u64;
if size > max {
return Err(FontFileError::TooLarge { size, max });
}
Ok(bytes)
}
/// Why a font file or font data wasn't added or loaded.
#[derive(Debug, thiserror::Error)]
pub enum FontFileError {
@@ -544,7 +557,7 @@ impl Catalog {
if size > MAX_LOAD {
return Err(FontFileError::TooLarge { size, max: MAX_LOAD });
}
let bytes: Arc<[u8]> = std::fs::read(path)?.into();
let bytes: Arc<[u8]> = read_capped(path, MAX_LOAD)?.into();
let mut files = lock(&self.files);
files.retain(|_, w| w.strong_count() > 0);
files.insert(path.to_path_buf(), Arc::downgrade(&bytes));
+12
View File
@@ -556,3 +556,15 @@ fn repeated_name_records_are_bounded_before_pairing() {
assert!(!face.keys.legacy.is_empty());
assert_eq!(face.keys.legacy.len(), 256, "the pair budget counts distinct pairs");
}
/// A read stops at its cap, whatever the file has grown to since its size was checked.
#[test]
fn a_read_is_capped_where_the_file_has_grown() {
use crate::catalog::read_capped;
let path = std::env::temp_dir().join(format!("craft-font-catalog-cap-{}", std::process::id()));
std::fs::write(&path, b"0123456789").unwrap();
assert_eq!(read_capped(&path, 10).unwrap(), b"0123456789");
assert!(matches!(read_capped(&path, 9), Err(FontFileError::TooLarge { size: 10, max: 9 })));
assert!(matches!(read_capped(&path.with_extension("missing"), 9), Err(FontFileError::Io(_))));
let _ = std::fs::remove_file(&path);
}