Repository navigation
[ot] Reject out-of-range GSUB/GPOS lookup types instead of truncating - #484
Conversation
|
Thanks for this. Would it be possible to add the tests to HarfBuzz and regenerate the HarfRust tests from that, instead of adding them to custom? |
Two fuzzed fonts whose GSUB has lookups with types like 3586 (0x0E02) and 7432 (0x1D08). HarfBuzz's sanitizer rejects the table, so the text shapes to .notdef glyphs with no lookups applied. HarfRust truncated the type to u8 and ran them as types 2 and 8 (MultipleSubst and ReverseChainSingleSubst): a single "A" became 2048 glyphs (harfbuzz/harfrust#484). Tested with `meson test -C build` (harfbuzz suites: 252 ok, 8 skipped). Assisted-by: Claude
f7ddb59 to
83c1651
Compare
|
Done: the two fonts and their tests are now in HarfBuzz (harfbuzz/harfbuzz#6319,
I kept only the new tests in Also rebased on main: the CHANGELOG entry moved to |
83c1651 to
3225f20
Compare
`LookupInfo::new` passed the 16-bit `lookupType` to `SubtableInfo::new` as `lookup_type as u8`, and the extension path did the same with `extension_lookup_type()`. A malformed font with e.g. lookupType 3586 (0x0E02) was therefore dispatched as type 2 (MultipleSubst) and 7432 (0x1D08) as type 8 (ReverseChainSingleSubst), running those lookups over whatever bytes sit at the (equally malformed) subtable offsets. HarfBuzz's sanitizer rejects such a table outright. HarfRust instead expanded a single 'A' into 2048 glyphs (bounded only by MAX_LEN_MIN), at ~8 ms per call vs ~2 µs in HarfBuzz, and 23 characters into ~39 000 glyphs in 60-900 ms. Anything shaping untrusted fonts could be made to burn ~10 ms-1 s per shape() call. Keep the lookup type as u16 all the way to the dispatch `match`; its existing `_ => return None` arm now rejects unknown types, and the two `as u8` truncations go away. The two fuzzer-generated fonts and their tests come from HarfBuzz (harfbuzz/harfbuzz#6319, in-house context-matching.tests); context_matching_009 to 012 are generated from there. All four fail on main and pass with this change. Found with libFuzzer + perf.
3225f20 to
e29277b
Compare
Problem
LookupInfo::newpasses the 16-bitlookupTypetoSubtableInfo::newaslookup_type as u8,and the extension path does the same with
extension_lookup_type() as u8. A malformed font withlookupType 3586 (
0x0E02) is dispatched as type 2 (MultipleSubst), and 7432 (0x1D08) as type 8(ReverseChainSingleSubst). Those lookups then run over whatever bytes are at the subtable offsets.
HarfBuzz's sanitizer rejects such a GSUB table. HarfRust applies the aliased lookups instead:
"A""Hello, Wörld! fi fl ffi"Only
MAX_LEN_MINbounds the output, so a short string costs ~10 ms to 1 s pershape()callfor anything that shapes untrusted fonts. perf: 76% in
ReverseChainSingleSubstFormat1::apply.Change
The lookup type stays
u16all the way to the dispatchmatch, so its existing_ => return Nonearm rejects unknown types. Bothas u8truncations are gone.Tests
Two fuzzer-generated fonts, added to HarfBuzz in harfbuzz/harfbuzz#6319 (in-house
context-matching.tests). Their tests here,context_matching_009..012inin_house.rs,are generated from that HarfBuzz branch with
gen-shaping-tests.py. All four fail on main andpass with this change. The tests (default and
icu) pass, as docargo fmt --checkand the1.85, libm and thumbv7em builds. A current clippy reports the same 8 errors on main as on
this branch, all in files this PR doesn't touch.
Found with libFuzzer, then perf and a HarfBuzz trace. This PR was produced by AI agents
(Claude), from the fuzzing and the fix through to this description.