Repository navigation
Conversation
|
@khaledhosny can you review this please? |
Implements a sanitizer for the VARC table from the boring-expansion-spec, which encodes variable-composite glyphs combinable with glyf/gvar or CFF2. The parser fully validates: - the table header and its five sub-table offsets; - the MultiItemVariationStore (sparse regions, item variation data, delta-set indices), collecting per-outer delta-set counts; - the ConditionList and all five Condition formats (recursively, with a depth limit); - the axisIndicesList, decoding each TupleValues entry to determine the axis-value count each referring component must supply; - every VarCompositeGlyph record: uint32var flags, 16/24-bit gid, condition index, axis indices/values, variation indices and transform fields, plus reserved uint32var fields; - that the coverage table's glyph count matches the glyphRecords count, since the former indexes the latter. Cross-references (gid, condition index, axisIndices index, VarIdx, region/axis indices) are bounds-checked; VarIdx 0xFFFFFFFF is accepted as the NO_VARIATION sentinel used by HarfBuzz and fonttools. Like CFF/COLR, the validated table is re-emitted verbatim. Adds three VARC good-font tests (glyf-flavored, one exercising all five condition formats). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Hi, I’m Codex, an AI coding agent from OpenAI. I reviewed this PR for sanitizer blockers and correctness bugs. I would block merging on the first four findings:
The full local 658-test suite and all GitHub checks pass, but the PR adds only good-font fixtures, so none of these rejection paths are covered. |
The delta sets of a MultiItemVariationData were only validated as objects of the enclosing CFF2 Index; their TupleValues contents were never decoded, so a run declaring more bytes than its object holds was accepted and passed through. Decode every delta set within its own bounds, require its value count to be a multiple of the region count, and record the quotient (the tuple length) per delta set. Every VarIdx use is now checked against the tuple length its consumer reads: 1 for a ConditionValue, the axis count of the referenced axisIndices entry for axisValuesVarIndex, and the number of present transform fields for transformVarIndex. The all-conditions test font had its ConditionValue pointing at a delta set of tuple length 3; regenerate it with a length-1 delta set. Adds bad fonts with an overshooting delta-set run, a delta set whose value count is not a multiple of the region count, and a ConditionValue referring to a delta set of the wrong tuple length. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChZAjXEo18sgTrMqfVsqBU
ConditionAnd/ConditionOr/ConditionNegate were validated by recursing into every child, bounded only by nesting depth. Since a child may be shared by several parents, a small table could demand exponential work (two references per level, 30 levels or so) and effectively hang the sanitizer. Track, per byte offset into the ConditionList, whether the condition there has already been validated, and skip repeats. The work is now linear in the size of the list. Adds a good font whose ConditionList is a 60-level chain of ConditionAnd tables, each referring to the next one twice. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChZAjXEo18sgTrMqfVsqBU
The axisIndicesList entries were decoded only to count their values; the values themselves were discarded, so an axis index beyond the fvar axis count was retained. Component axis values were likewise skipped over without inspection. Have ParseTupleValues hand back the decoded values, require every axis index to name an fvar axis, and require every component axis value to be a normalized coordinate in [-1, 1]. Adds bad fonts with an axis index of 127 in an eight-axis font and a component axis value just below -1.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChZAjXEo18sgTrMqfVsqBU
The coverage glyph count was compared with the glyphRecords count by passing the latter as ParseCoverageTable's expected count, where 0 means "don't check". A table with zero glyph records (or more than 0xFFFF) therefore skipped the comparison altogether, and a coverage table listing glyphs with no records behind them was accepted. Reject an empty glyphRecords Index and one with more entries than a coverage table can express, so the comparison always takes place. Adds a bad font with zero glyph records and four covered glyphs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChZAjXEo18sgTrMqfVsqBU
…0xF0 In the five-byte form of uint32var the low nibble of the lead byte holds bits 32 and up. Those cannot be represented in a uint32, so only a lead byte of exactly 0xF0 is valid; the reader nevertheless accepted 0xF1-0xFF and silently dropped the high bits, reading "F1 00 00 00 00" (2^32) as zero. Adds a bad font whose first component's flags are encoded that way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChZAjXEo18sgTrMqfVsqBU
Condition format 1 was checked only for a valid axis index; its filter range was read and ignored, so a condition spanning the raw int16 range was accepted. The values are normalized F2Dot14 coordinates and must lie within [-1, 1], as the GSUB/GPOS condition validator already enforces. Like that validator, warn if the range is misordered. Adds a bad font with a filterRangeMinValue of -2.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChZAjXEo18sgTrMqfVsqBU
|
I did a review with codex and addressed the findings with Claude. Should be in better shape now, but more LLM review might be justified. |
Duplicate directory entries can cause a dropped table to be parsed again from a different location, violating the per-font table uniqueness assertion. Reject duplicate tags before parsing any tables and cover both retained and dropped gasp entries.
Add sanitization for OFF 2026 variable composites and extended glyph repertoires. Fonts using these formats can retain their outlines, mappings, layout and variation data through sanitization, including fonts with both legacy and uppercase tables. CFSH is excluded.
Add regression tests for extended glyph IDs, offsets beyond 64 KiB, mixed field widths, malformed/truncated data, shared condition graphs, depth limits, and uppercase-only and hybrid font round trips. Update four VARC corpus fonts to the current variation-store encoding.
Validation: all 685 Meson tests complete successfully in both the normal and AddressSanitizer/UBSan builds: 562 passes and 123 expected rejections. The CIFuzz duplicate-tag reproducer is rejected cleanly. A separate five-minute libFuzzer/ASan run replayed the public OSS-Fuzz corpus and completed 638,152 input executions without a crash.
Also tested NotoSans-beyond64k.ttf with 104,404 glyphs. FreeType confirms identical outlines and metrics for every glyph; HarfBuzz shaping matches for 20 multilingual samples. GDEF, GPOS, GSUB and cmap remain byte-identical, and a second sanitization produces identical output.