mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
Merge pull request #1482 from midasmatterhorn/fix/font-offset-overflow
pdf: checked_add for CFF Private DICT and Type 1 RD offsets from the file
This commit is contained in:
+65
-2
@@ -394,11 +394,16 @@ impl Cff {
|
||||
let private = |pd: &[(u16, Vec<f64>)]| -> (Vec<std::ops::Range<usize>>, (f64, f64)) {
|
||||
let Some(pv) = get(pd, 18).filter(|v| v.len() == 2) else { return (vec![], (0.0, 0.0)) };
|
||||
let (size, off) = (pv[0] as usize, pv[1] as usize);
|
||||
let Some(bytes) = d.get(off..off + size) else { return (vec![], (0.0, 0.0)) };
|
||||
let Some(bytes) = off.checked_add(size).and_then(|end| d.get(off..end)) else { return (vec![], (0.0, 0.0)) };
|
||||
let pr = dict(bytes);
|
||||
let nominal = get(&pr, 21).and_then(|v| v.first().copied()).unwrap_or(0.0);
|
||||
let default = get(&pr, 20).and_then(|v| v.first().copied()).unwrap_or(0.0);
|
||||
let subrs = get(&pr, 19).and_then(|v| v.first().copied()).and_then(|s| index(d, off + s as usize)).map(|x| x.0).unwrap_or_default();
|
||||
let subrs = get(&pr, 19)
|
||||
.and_then(|v| v.first().copied())
|
||||
.and_then(|s| off.checked_add(s as usize))
|
||||
.and_then(|p| index(d, p))
|
||||
.map(|x| x.0)
|
||||
.unwrap_or_default();
|
||||
(subrs, (default, nominal))
|
||||
};
|
||||
let mut lsubrs = vec![];
|
||||
@@ -1082,4 +1087,62 @@ mod tests {
|
||||
let (p, _) = cff.outline(cff.gid_by_name("A").unwrap()).unwrap();
|
||||
assert!(p.elements().is_empty());
|
||||
}
|
||||
|
||||
/// A CFF whose top DICT points its Private DICT at offsets near `usize::MAX`: `size` and `off`
|
||||
/// come from the file, and reading the Private DICT must not overflow `off + size`.
|
||||
#[test]
|
||||
fn huge_private_dict_offsets_do_not_overflow() {
|
||||
fn idx(items: &[Vec<u8>]) -> Vec<u8> {
|
||||
let mut out = (items.len() as u16).to_be_bytes().to_vec();
|
||||
if items.is_empty() {
|
||||
return out;
|
||||
}
|
||||
out.push(4);
|
||||
let mut off = 1u32;
|
||||
out.extend_from_slice(&off.to_be_bytes());
|
||||
for it in items {
|
||||
off += it.len() as u32;
|
||||
out.extend_from_slice(&off.to_be_bytes());
|
||||
}
|
||||
for it in items {
|
||||
out.extend_from_slice(it);
|
||||
}
|
||||
out
|
||||
}
|
||||
let int = |v: i32| {
|
||||
let mut o = vec![29];
|
||||
o.extend_from_slice(&v.to_be_bytes());
|
||||
o
|
||||
};
|
||||
// The BCD real "1E300": a size and an offset that both saturate at `usize::MAX`.
|
||||
let huge = vec![0x1E, 0x1B, 0x30, 0x0F];
|
||||
let header = [1u8, 0, 4, 4];
|
||||
let names = idx(&[b"Test".to_vec()]);
|
||||
let strings = idx(&[]);
|
||||
let gsubrs = idx(&[]);
|
||||
let charset = vec![0u8];
|
||||
let cs = idx(&[vec![14]]); // one `.notdef` charstring: endchar
|
||||
let top_len = 5 + 1 + 5 + 1 + huge.len() * 2 + 1;
|
||||
let top_index_len = 2 + 1 + 8 + top_len;
|
||||
let base = header.len() + names.len() + top_index_len + strings.len() + gsubrs.len();
|
||||
let cs_off = base + charset.len();
|
||||
let mut top = vec![];
|
||||
top.extend(int(0));
|
||||
top.push(15); // charset: ISOAdobe
|
||||
top.extend(int(cs_off as i32));
|
||||
top.push(17); // CharStrings
|
||||
top.extend(&huge);
|
||||
top.extend(&huge);
|
||||
top.push(18); // Private: [size, offset]
|
||||
assert_eq!(top.len(), top_len);
|
||||
let mut out = header.to_vec();
|
||||
out.extend(names);
|
||||
out.extend(idx(&[top]));
|
||||
out.extend(strings);
|
||||
out.extend(gsubrs);
|
||||
out.extend(charset);
|
||||
out.extend(cs);
|
||||
let cff = Cff::parse(&out).expect("the damaged Private DICT is skipped, the font parses");
|
||||
assert_eq!(cff.glyph_count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -143,7 +143,7 @@ impl Type1 {
|
||||
q += 1;
|
||||
}
|
||||
q += 1;
|
||||
let bytes = priv_.get(q..q + n)?;
|
||||
let bytes = priv_.get(q..q.checked_add(n)?)?;
|
||||
Some((cs(bytes), q + n))
|
||||
};
|
||||
if let Some(p) = find(&priv_, b"/Subrs", 0) {
|
||||
@@ -532,4 +532,17 @@ mod tests {
|
||||
assert!((p.area().abs() - 250_000.0).abs() < 1e-6);
|
||||
assert_eq!(p.bounding_box(), kurbo::Rect::new(50.0, 0.0, 550.0, 500.0));
|
||||
}
|
||||
|
||||
/// A `/Subrs` entry whose `RD` length is `usize::MAX`: the bytes are not in the file, and
|
||||
/// `q + n` must not overflow.
|
||||
#[test]
|
||||
fn huge_rd_lengths_do_not_overflow() {
|
||||
let mut out = b"%!PS-AdobeFont-1.0: Test 001\n/FontMatrix [0.001 0 0 0.001 0 0] readonly def\ncurrentfile eexec\n".to_vec();
|
||||
let mut priv_ = b"dup /Private 8 dict dup begin /lenIV -1 def\n/Subrs 1 array\ndup 0 18446744073709551615 RD\n".to_vec();
|
||||
priv_.extend_from_slice(b"end\n/CharStrings 1 dict dup begin\n/.notdef 4 RD \x8b\x8b\r\x0e ND\nend\nend\nmark currentfile closefile\n");
|
||||
out.extend(encrypt(&priv_, EEXEC_R, 4));
|
||||
out.extend_from_slice(b"\n0000000000000000\ncleartomark\n");
|
||||
let t = Type1::parse(&out).expect("the damaged Subrs entry is skipped");
|
||||
assert!(t.has_glyph(".notdef"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user