Skip to content

avar::map_value: unchecked i16 arithmetic can wrap on extreme AxisValueMap entries (correctness bug, not a security report) #219

Description

@scadastrangelove

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions