Filing this as an ordinary bug, not a security finding — there's no crash in release builds and
no memory-safety angle (the crate is safe Rust throughout, and the wrapped value stays within
NormalizedCoordinate's own valid range regardless), just a rendering-correctness issue for an
unusual avar table.
avar.rs's map_value computes value - record.from_coordinate + record.to_coordinate in plain
i16 at three call sites (the single-segment fast path, and both endpoints of the multi-segment
search) — unlike the interpolation branch a few lines below, which correctly promotes to i32
before the equivalent arithmetic. An AxisValueMap with an extreme (from_coordinate, to_coordinate) pair overflows i16 range at those three sites.
What actually happens:
- In a debug build, this panics (
attempt to subtract with overflow).
- In a release build (
overflow-checks = false, the default — i.e. what you'd actually ship),
it does not panic; it silently wraps to whatever the i16 wraparound produces and returns that as
the mapped coordinate. Face::set_variation already discards avar::map_coordinate's
Option (let _ = ...), so this doesn't propagate as an error either way — the practical effect
is that a variable font with a pathological avar table maps to a wrong-but-still-in-range
NormalizedCoordinate for that one axis, producing a visually incorrect glyph shape rather than
any crash or undefined behavior.
I'm flagging the debug-panic facet mostly for completeness/consistency with the crate's own
already-open #178/#205 (both debug_assert-adjacent, both non-release-exploitable on their own) —
not claiming it's separately severe.
Reproduction (ttf-parser 0.25.1, and current main @ 6e75b3c)
fvar axis wght range 0.0..1000.0; avar single segment map
{from_coordinate: -32768, to_coordinate: 32767}; set_variation(wght, 1000.0) normalizes to
16384:
debug: thread 'main' panicked at src/tables/avar.rs:147:21: attempt to subtract with overflow
release: set_variation returned Some(()), coordinates = [NormalizedCoordinate(16383)]
(mathematically 16384 - (-32768) + 32767 = 81919, which doesn't fit in i16 --
the result is whatever 81919 wraps to mod 2^16, reinterpreted as i16)
Fix
PR incoming: promotes all three sites to i32, matching the existing sibling interpolation
branch, and rejects (returns the coordinate unmapped) rather than wraps when the result doesn't
fit back in i16.
Discovered by the rust-in-peace security pipeline.
Filing this as an ordinary bug, not a security finding — there's no crash in release builds and
no memory-safety angle (the crate is safe Rust throughout, and the wrapped value stays within
NormalizedCoordinate's own valid range regardless), just a rendering-correctness issue for anunusual
avartable.avar.rs'smap_valuecomputesvalue - record.from_coordinate + record.to_coordinatein plaini16at three call sites (the single-segment fast path, and both endpoints of the multi-segmentsearch) — unlike the interpolation branch a few lines below, which correctly promotes to
i32before the equivalent arithmetic. An
AxisValueMapwith an extreme(from_coordinate, to_coordinate)pair overflows i16 range at those three sites.What actually happens:
attempt to subtract with overflow).overflow-checks = false, the default — i.e. what you'd actually ship),it does not panic; it silently wraps to whatever the i16 wraparound produces and returns that as
the mapped coordinate.
Face::set_variationalready discardsavar::map_coordinate'sOption(let _ = ...), so this doesn't propagate as an error either way — the practical effectis that a variable font with a pathological
avartable maps to a wrong-but-still-in-rangeNormalizedCoordinatefor that one axis, producing a visually incorrect glyph shape rather thanany crash or undefined behavior.
I'm flagging the debug-panic facet mostly for completeness/consistency with the crate's own
already-open #178/#205 (both
debug_assert-adjacent, both non-release-exploitable on their own) —not claiming it's separately severe.
Reproduction (ttf-parser 0.25.1, and current main @ 6e75b3c)
fvaraxiswghtrange0.0..1000.0;avarsingle segment map{from_coordinate: -32768, to_coordinate: 32767};set_variation(wght, 1000.0)normalizes to16384:Fix
PR incoming: promotes all three sites to
i32, matching the existing sibling interpolationbranch, and rejects (returns the coordinate unmapped) rather than wraps when the result doesn't
fit back in
i16.Discovered by the rust-in-peace security pipeline.