Skip to content

Fix unbounded ReadGlyph allocation (issue #191)#192

Open
parasol-aser wants to merge 2 commits intogoogle:masterfrom
parasol-aser:fix/issue-191-readglyph-unbounded-allocation
Open

Fix unbounded ReadGlyph allocation (issue #191)#192
parasol-aser wants to merge 2 commits intogoogle:masterfrom
parasol-aser:fix/issue-191-readglyph-unbounded-allocation

Conversation

@parasol-aser
Copy link
Copy Markdown

@parasol-aser parasol-aser commented Apr 13, 2026

Summary

Fixes #191: ReadGlyph can allocate an oversized contour when a malformed simple glyph has non-monotonic endPtsOfContours values. The subtraction that computes num_points can wrap before std::vector<Glyph::Point>::resize.

Fix

src/glyph.cc now rejects simple glyphs where a later endPtsOfContours entry is smaller than the previous entry:

if (i > 0 && point_index < last_point_index) {
  return FONT_COMPRESSION_FAILURE();
}

This is the spec-level invariant needed for the reported allocation bug. I removed the earlier remaining-byte cap because it was based on a wrong assumption: simple-glyph flags are RLE-encoded, so logical point count is not bounded by one stored flag byte per point. StoreEndPtsOfContours also confirms that the endpoint value itself may reach uint16_t::max().

Changes

  • src/glyph.cc — reject decreasing endPtsOfContours before computing num_points.
  • src/convert_ttf2woff2_fuzzer.cc — add an encoder fuzz entry point.
  • CMakeLists.txt — register the encoder fuzzer and opt-in regression tests.
  • test/test_read_glyph.cc — cover non-monotonic endpoints and RLE-encoded flags.
  • test/test_convert_ttf2woff2.cc / test/generate_fixtures.py — cover valid baseline input and malformed non-monotonic TTF fixtures.

Test plan

  • cmake -S . -B build -G Ninja
  • cmake --build build
  • ctest --test-dir build --output-on-failure
  • ./build/test_read_glyph

Reviewer follow-up still useful: run a longer encoder fuzzing session against a normal sfnt corpus and compare woff2_compress output on a known-good corpus.

In ReadGlyph's simple-glyph branch, endPtsOfContours is treated as a
uint16_t without enforcing monotonicity. A crafted sfnt with
point_index < last_point_index wraps the subtraction and drives
std::vector<Point>::resize to ~65535 entries per contour, producing
multi-GB allocations and OOM DoS in ConvertTTFToWOFF2.

Enforce the spec's monotonic-non-decreasing requirement on
endPtsOfContours, and bound num_points by the remaining glyph buffer
so allocations stay proportional to input size.

Add a libFuzzer harness (convert_ttf2woff2_fuzzer) mirroring the
reporter's setup, unit tests for ReadGlyph's per-contour guards, and
an end-to-end test over fontTools-generated TTF fixtures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@google-cla
Copy link
Copy Markdown

google-cla Bot commented Apr 13, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

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.

[Fuzzing] DoS: Unbounded ReadGlyph allocation via uint16_t wraparound in ConvertTTFToWOFF2

1 participant