Skip to content

Replace compression-ratio heuristic with absolute uncompressed-size bound - #204

Draft
rootkiller6788 wants to merge 1 commit into
google:masterfrom
rootkiller6788:fix-compression-ratio-false-positive
Draft

Replace compression-ratio heuristic with absolute uncompressed-size bound#204
rootkiller6788 wants to merge 1 commit into
google:masterfrom
rootkiller6788:fix-compression-ratio-false-positive

Conversation

@rootkiller6788

Copy link
Copy Markdown

Fixes #184

Problem

woff2_decompress (and the decoder used by browsers) rejects valid fonts with:

Implausible compression ratio 1952.7

Issue #184 shows a real use case: a font that covers Unicode planes 0, 1 and 15 with a pool of N identical empty glyphs (cmap format 12). Such a font is valid per the spec, and its WOFF2 compresses extremely well because the glyph data is repeated. At N=65534 it compresses from ~533KB to 277 bytes (~1924x), and the ratio check rejects it.

I reproduced this locally: a 8000-glyph font compressing from 65457 to 248 bytes (~264x) was rejected with "Implausible compression ratio 202.0"; with the fix it round-trips byte-for-byte.

Why the ratio check is the wrong guard

The check (added in 2016 as a decompression-bomb guard for the std::vector<uint8_t> uncompressed_buf(hdr.uncompressed_size) allocation) rejects when uncompressed_size / file_size > 100. Two problems:

  1. It is bypassable. file_size is just as attacker-controlled as uncompressed_size; an attacker can pad the file so the ratio stays under 100 while claiming gigabytes. It therefore never actually bounded the allocation.
  2. It produces false positives. Legitimately highly-compressible fonts (repeated empty glyphs, subset fonts) can exceed any fixed ratio.

The fix

Bound the quantity that actually sizes the allocation directly: reject hdr.uncompressed_size > kDefaultMaxSize (128MB), the same limit the reconstructed output is already subject to via WOFF2StringOut/WOFF2MemoryOut.

A font that can decode successfully reconstructs to at most kDefaultMaxSize, and a valid font's transformed table data is never larger than its reconstructed output, so this bound cannot reject a decodable font. A claim above the limit is rejected before any allocation happens.

Verification

  • Crafted the issue's scenario (N=65534, planes 0/1/15): previously rejected at ratio ~1924; now decodes to a byte-identical TTF (all 10 tables match via fontTools).
  • Crafted a 8000-glyph variant (ratio ~202): previously rejected; now decodes byte-identically.
  • Crafted a decompression bomb claiming 129MB uncompressed in a 68-byte file: rejected by the new bound before allocation ("Implausible uncompressed size 135266304").
  • A 100MB claim (under the bound) proceeds to brotli, which fails naturally on garbage input.
  • Fuzzed 120 random/mutated inputs through the decoder: 0 crashes.

…ound

The decoder rejected valid fonts whose claimed uncompressed size was more
than 100x their on-disk size ("Implausible compression ratio"). Legitimate
highly-compressible fonts, e.g. subset fonts with many repeated empty glyphs
used to cover the whole Unicode range (issue google#184), can exceed any fixed
ratio: a 65534-glyph font compresses from ~533KB to 277 bytes (~1924x).

The ratio check was introduced as a decompression-bomb guard, but it is
both bypassable and wrong. The compressed size is equally attacker-controlled
(an attacker can pad the file to make any ratio pass), so it never bounded
the real allocation anyway; and the quantity that actually needs bounding is
the intermediate buffer sized by hdr.uncompressed_size. Bound that against
kDefaultMaxSize, the same limit the reconstructed output is already subject
to: a font that can decode successfully never needs an uncompressed buffer
larger than its output, so this cannot reject a decodable font, while a
claim above the limit is rejected before any allocation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Implausible compression ratio" excludes some valid fonts

1 participant