Panic: u16 overflow of the stem-hint count in charstring.rs when a glyph declares more than 65535 hints

Open
#33 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
67/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
backend

Research direction

Start in subsetter-0.2.6/src/cff/charstring.rs, at Decompiler::count_hints and the reported hint_count addition. Read how its errors are returned, then add a regression test using the supplied CFF reproduction or a focused charstring test. Done means excessive stem hints return an error instead of panicking or wrapping; run cargo test.

Written by the indexing model from the issue text.

Description

subset panics on a CFF glyph that declares more than 65535 stem hints, in any build with overflow checks (debug builds, cargo test, fuzzing):

thread 'main' panicked at subsetter-0.2.6/src/cff/charstring.rs:171:9:
attempt to add with overflow

Decompiler::count_hints adds the hints of each stem operator to hint_count, a u16 (self.hint_count += elements.len() as u16 / 2). A glyph whose charstring, through subroutines, runs hstem with 48 operands 2731 times or more overflows it. Without overflow checks the count wraps silently, and the size of a following hintmask would be computed from the wrapped count. This happens with 0.2.6 and with main (b0adcc1).

To reproduce with subsetter 0.2.6 (default-features = false). The font is built for this report:

fn main() {
    // A minimal OpenType CFF font, 355 bytes, whose one glyph declares 72,000 stem
    // hints: global subroutine 0 is 48 operands and hstem (24 hints), subroutine 1 calls
    // it 60 times, subroutine 2 calls subroutine 1 50 times, and the charstring calls
    // subroutine 2 once, then endchar.
    let hex = "4f54544f000100100000000043464620000000000000001c000001470100040100010101024100010101121d0000013d111d000000021d000001451200000003020001003300ac01118b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b8b010b201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d201d0b211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d211d0b0001010104221d0e8b14";
    let data: Vec<u8> = (0..hex.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
        .collect();
    let remapper = subsetter::GlyphRemapper::new();
    println!("{:?}", subsetter::subset(&data, 0, &remapper).map(|font| font.len()));
}

cargo run panics as above; cargo run --release prints Ok(147168).

It was found while reviewing how officewright guards against subsetter panics; until it is fixed, our call to subset turns the panic into an error.

A possible fix: add with checked_add and return CFFError on overflow. The Type 2 charstring format also limits a glyph to 96 stem hints (Technical Note #5177, Appendix B), so refusing more than that would fix it too.

Dominant language
Rust
Stars
61
Forks
16
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from typst/subsetter

All issues in typst/subsetter

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.