From bc05e42dafc4e5f43f3a9741c3f25e2f8f593424 Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Fri, 21 Aug 2026 20:33:52 +0800 Subject: [PATCH] Replace compression-ratio heuristic with absolute uncompressed-size bound 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 #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. --- src/woff2_dec.cc | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/woff2_dec.cc b/src/woff2_dec.cc index efb579b..8475c3c 100644 --- a/src/woff2_dec.cc +++ b/src/woff2_dec.cc @@ -62,10 +62,6 @@ const size_t kCompositeGlyphBegin = 10; // Largest glyph ever observed was 72k bytes const size_t kDefaultGlyphBuf = 5120; -// Over 14k test fonts the max compression ratio seen to date was ~20. -// >100 suggests you wrote a bad uncompressed size. -const float kMaxPlausibleCompressionRatio = 100.0; - // metadata for a TTC font entry struct TtcFont { uint32_t flavor; @@ -1365,10 +1361,23 @@ bool ConvertWOFF2ToTTF(const uint8_t* data, size_t length, return FONT_COMPRESSION_FAILURE(); } - const float compression_ratio = (float) hdr.uncompressed_size / length; - if (compression_ratio > kMaxPlausibleCompressionRatio) { + // hdr.uncompressed_size is attacker-controlled: it is the sum of the table + // directory's transform lengths and it sizes the decompression buffer below. + // Bound it before allocating. 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 a larger + // uncompressed size can only be a decompression-bomb claim. + // + // This used to be a compression-ratio heuristic (reject when uncompressed + // size / file size exceeded a fixed ratio), but the file size is just as + // attacker-controlled so the ratio was bypassable, and legitimately + // highly-compressible fonts (e.g. subset fonts with many repeated empty + // glyphs) can exceed any fixed ratio. The absolute bound is what actually + // limits the allocation. + if (PREDICT_FALSE(hdr.uncompressed_size > kDefaultMaxSize)) { #ifdef FONT_COMPRESSION_BIN - fprintf(stderr, "Implausible compression ratio %.01f\n", compression_ratio); + fprintf(stderr, "Implausible uncompressed size %u\n", + hdr.uncompressed_size); #endif return FONT_COMPRESSION_FAILURE(); }