You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add Fonts namespace (TrueType/SFNT font parsing) - #12
…cmap glyph clamp
- CmapTable.cs: use overflow-safe (long) arithmetic for the subtable-offset
bounds check in Parse (an offset near int.MaxValue previously wrapped
around and passed the check, leading to an IndexOutOfRangeException
downstream), and apply the same overflow-safe pattern to the equivalent
entry-point bounds checks in TryParseFormat4/TryParseFormat12.
- GlyfLocaReader.cs: reject a simple-glyph flag repeat count that exceeds
the remaining point slots with InvalidDataException instead of silently
clipping it to the remaining count, matching this class's documented
malformed-input contract.
- TrueTypeFont.cs: clamp GetGlyphIndex's result to 0 when the cmap subtable
maps a codepoint to a glyph index outside [0, GlyphCount), so callers can
always safely pass its result straight to GetGlyphOutline; updated the
XML doc comment to make this guarantee explicit.
- Added/extended unit tests reproducing each malformed-input scenario using
SyntheticFontBuilder synthetic fonts (no third-party font fixtures).
These branches bound format-4/12 parsing by the enclosing cmap table, but never enforce each subtable's declared length. A malformed candidate can therefore consume bytes belonging to a following subtable and return bogus mappings instead of falling back to 0, contrary to CmapTable's malformed-input contract. Derive and validate a per-subtable end from the format-specific length before parsing.
segCountX2 is a byte count and must be even, but dividing an odd value truncates it while the subsequent offsets still use the odd raw count. That misaligns the format-4 arrays and can produce mappings from malformed data instead of returning the documented empty/fallback result; reject odd values before deriving segCount.
Composite flags for SCALED_COMPONENT_OFFSET and UNSCALED_COMPONENT_OFFSET are not decoded, so every component translation is passed through unchanged. A valid composite that requests a scaled offset will therefore have its outline displaced incorrectly; handle the offset-mode flags (or reject unsupported modes) before calling AppendTransformed, and cover the scaled-offset case.
…posite glyph offset scaling
- KernTable.Parse: widen the subtable end-position arithmetic (`pos + 6` and
`pos + subtableLength`) to `long` before comparing against `tableEnd`, matching
the overflow-safe pattern already used elsewhere in this file and in
SfntContainer/CmapTable. Prevents a wrapped (negative) subtable end from
bypassing the bounds check and later driving a corrupted `pos` into an
out-of-bounds read.
- CmapTable.Parse: bound each candidate format-4/format-12 subtable by its own
declared `length` field (not just the enclosing `cmap` table's end), via a new
`ComputeSubtableEnd` helper. Previously a subtable could read past its own
declared length into a following subtable's bytes and return a bogus (but
structurally passable) mapping instead of being rejected.
- CmapTable.TryParseFormat4: reject an odd `segCountX2` outright instead of
silently truncating `segCount` via integer division, which previously
misaligned the parallel endCode/startCode/idDelta/idRangeOffset arrays and
could still produce a bogus (rather than failing) lookup.
- GlyfLocaReader.DecodeCompositeGlyph: honor the `SCALED_COMPONENT_OFFSET` /
`UNSCALED_COMPONENT_OFFSET` composite glyph flags. When a component sets
`SCALED_COMPONENT_OFFSET` (and not `UNSCALED_COMPONENT_OFFSET`), its dx/dy
translation is now transformed through the component's own scale/2x2 matrix
before being applied, instead of always being applied unscaled.
Added SyntheticFontBuilder support for the new composite glyph flags, corrected
CmapFormat4/CmapFormat12 to write their subtables' real declared length (no
longer a placeholder now that CmapTable enforces it), and added regression
tests for each of the four fixes - each verified to fail against the pre-fix
code and pass against the post-fix code. Updated the composite-glyph design
doc, reqstream, and verification docs for the newly-observable
SCALED_COMPONENT_OFFSET behavior.
A version-1.0 'maxp' table only had its 6-byte version+numGlyphs
prefix validated, so a font declaring version 1.0 but truncated to
6 bytes was incorrectly accepted instead of throwing
InvalidDataException per this class's documented structural-
validation contract. A version-1.0 maxp table is exactly 32 bytes
per the OpenType/TrueType spec; require that length once the
version field confirms 1.0.
Also updates SyntheticFontBuilder's maxp fixture to emit a full,
valid 32-byte version-1.0 table (placeholder values for the fields
this library never reads) instead of the previously under-sized
6-byte form, and adds a regression test proving a truncated
version-1.0 maxp table is rejected (confirmed to fail against the
pre-fix code and pass post-fix).
…sed)
Relocates the staged OpenSans-Regular.ttf and OpenSans-OFL.txt
fixtures from the repo root into a new test/.../FontFixtures/
folder (mirroring the existing PngSuite/TiffFixtures/JpegFixtures
content-item convention), renaming the license file to
OpenSans.LICENSE to match PngSuite.LICENSE's naming, and adding a
README.md documenting provenance and the SIL OFL 1.1 terms.
Wires FontFixtures\** into the test project as CopyToOutputDirectory
content, then adds a genuine integration test
(TrueTypeFontRealFontIntegrationTests) that loads the real font via
TrueTypeFont.Load, resolves a real glyph index for 'A', extracts its
real outline, transforms it from font design units (y-up) into pixel
space (y-down, scaled/margined to fit a 128x128 Surface), fills it
via PathFiller, and asserts on the actual rendered pixels: ink is
present within the glyph's transformed bounding box, and the
canvas's far corners remain fully transparent. Also checks a real
positive advance width and that a self-kerning lookup does not
throw. No reqstream/design/verification artifacts are touched - this
is a test-only addition proving existing documented behavior.
… round 2)
- CmapTable format-4: reject subtables whose segments are not strictly
increasing/non-overlapping by endCode, whose startCode exceeds endCode, or
whose final segment is not the mandatory 0xFFFF terminator.
- CmapTable format-4: widen the indirect idRangeOffset glyph-index address
computation to long arithmetic and reject addresses that point backward
(before glyphIdArray) or past the subtable end, instead of only bounds-
checking the upper side with unwidened int arithmetic.
- CmapTable format-12: reject subtables whose groups are not strictly
increasing/non-overlapping by (startCharCode, endCharCode).
- KernTable format-0: widen the body-offset bounds check to long arithmetic
to match the other overflow-safe checks already in this file.
- Add regression tests for all four fixes (CmapTableTests, KernTableTests),
each verified to fail against the pre-fix code and pass post-fix.
- .reviewmark.yaml: add the missing test file to the Fonts unit review set.
- true-type-font.sysml: add the missing test file to the unit's testRef.
- fonts.md / fonts/true-type-font.md: correct the verification environment
description - one integration test now loads a real, licensed (SIL OFL 1.1)
OpenSans-Regular.ttf production font fixture to prove end-to-end
composition with the Drawing pipeline on real-world glyph data; all other
Fonts tests remain synthetic-fixture-based.
- Require both startCode and endCode of the final format-4 segment to be
0xFFFF, per the OpenType spec's mandatory terminator sentinel (previously
only endCode was checked, letting a spec-violating 'catch-all' segment
such as startCode=0, endCode=0xFFFF through).
- Add an explicit non-overlap check for format-4 segments: each segment's
startCode must be greater than the previous segment's endCode. The prior
comment incorrectly claimed strictly-increasing endCode alone implied
non-overlap; it does not (e.g. (0,10) followed by (5,20) passes a naive
strictly-increasing-endCode check yet overlaps). Comment updated to
describe the now-explicit non-overlap guarantee.
- Add regression tests covering both gaps (rejecting a non-0xFFFF terminator
startCode, and rejecting overlapping-but-endCode-increasing segments), and
fix an existing test whose synthetic fallback subtable relied on the same
previously-unenforced terminator requirement.
… docs
- CmapTable.TryParseFormat4: compute the endCode/startCode/idDelta/
idRangeOffset/glyphIdArray offset chain in long arithmetic before the
bounds check, so a malformed subtable offset combined with a large
segCountX2 can no longer overflow 32-bit int addition, wrap past the
glyphIdArrayOffset > tableEnd guard, and trigger an
IndexOutOfRangeException or an out-of-bounds read in the array
population loop.
- CmapTable format-12 lookup delegate: compute
startGlyphIds[mid] + (code - starts[mid]) in long arithmetic and reject
results that would not fit in a non-negative int, so a malformed group
can no longer silently wrap to a small, plausible-looking (but bogus)
glyph index.
- docs/verification/canvas-net/fonts/true-type-font.md: scope the
"no third-party font files are used" claim to the synthetic-fixture
coverage only, removing the self-contradiction with the following
sentences describing the real Open Sans integration test fixture.
- Add CmapTableTests regression coverage for both offset-chain overflow
and format-12 glyph-id wraparound, each verified to fail pre-fix and
pass post-fix.
The format-12 lookup only rejects negative inputs. Because a malformed subtable can also declare a range above Unicode's maximum, GetGlyphIndex(0x110000) can return a nonzero glyph instead of the documented .notdef fallback for an invalid Unicode codepoint. Bound the lookup to U+10FFFF (and reject out-of-range format-12 groups when parsing).
TryParseFormat12 allocated three parallel uint[] arrays sized directly by an
attacker-controlled numGroups after only an enclosing-table bounds check,
letting a bounds-valid but adversarial format-12 cmap subtable (declaring a
near-maximal numGroups paired with a suitably large but internally-consistent
declared table/file size) exhaust process memory.
Add MaxFormat12Groups (65536), checked before any further bounds checking or
allocation, following the same explicit-resource-cap philosophy already used
by GlyfLocaReader's MaxDepth/MaxTotalComponents. Real fonts - even CJK-heavy
ones covering the full Unicode codepoint space - need at most a few thousand
groups, so 65536 is generous headroom while keeping worst-case allocation to
a few hundred KB.
Kept the three parallel uint[] arrays (starts/ends/startGlyphIds) rather than
combining them into one struct array: with numGroups now capped, the total
byte footprint is the same order of magnitude either way, and the difference
is only per-array object overhead - not a meaningful part of the DoS surface -
so combining them was judged not worth the added code churn/risk.
Adds a regression test proving a subtable declaring numGroups one above the
cap (with real, non-overlapping group data making it otherwise bounds-valid)
is rejected; verified the test fails without the cap and passes with it.
The cmap header version is never checked before numTables is read. Since the supported cmap header version is 0, a malformed table with a nonzero version but otherwise valid records is currently accepted and can return mappings, contrary to this helper's documented malformed-input fallback to glyph 0. Reject nonzero versions before parsing the encoding records.
This test allocates a byte[] whose length is essentially Array.MaxLength (about 2 GiB). The allocation can fail with OutOfMemoryException before CmapTable.Parse runs, making the test unsuitable for CI and unable to verify the overflow path reliably. Exercise the widened address arithmetic through a small-buffer/helper abstraction instead of materializing a near-int.MaxValue array.
This issue also appears on line 793 of the same file.
This test constructs a byte[] of about 2 GiB to make tableOffset overflow when adding the subtable length. That allocation is not realistic for the test process and can fail before KernTable.Parse is called; test the arithmetic helper directly or refactor the bounds calculation so it can be exercised with a small backing buffer.
Format-12 startCharCode/endCharCode values are limited to Unicode U+10FFFF, but this validation checks only ordering. A malformed group such as [0x110000, 0x110000] is accepted, and GetGlyphIndex(0x110000) can then return a glyph instead of the documented 0 fallback for malformed cmap data. Reject groups whose end code exceeds 0x10FFFF and cover that case with a regression test.
This public method delegates directly to KernTable, which only bounds indices to ushort and does not know this font's GlyphCount. Consequently, a two-glyph font containing a kern pair such as (100, 101) returns that adjustment for out-of-range glyph indices, contrary to the documented/requirement contract that out-of-range kerning queries return 0. Check both indices against GlyphCount before delegating and add a regression for an in-range ushort pair outside the font.
Avoid oversized allocation in cmap offset overflow test
This allocation is not sparse: with idRangeOffsetOffset = Array.MaxLength - 30, the computed length is roughly 2.146 GB. The test can therefore exhaust the test process or fail on the runtime's maximum object size before CmapTable.Parse runs, despite the comment claiming only a small buffer is needed. Exercise the narrow arithmetic helper through reflection (as the KernTable overflow test does) or redesign the fixture so it does not allocate from the artificial absolute offset.
This issue also appears on line 793 of the same file.
Avoid oversized allocation in kern offset overflow test
This test also allocates a dense array whose length is about 2.147 GB (tableOffset + tableLength), while tableOffset was deliberately selected near int.MaxValue. That exceeds the runtime's addressable array limit or available test memory, so the test fails before KernTable.Parse can verify the overflow guard. Use a direct helper/reflection test or another fixture that fits in a normal byte array.
Prevents a large simple glyph referenced many times via composite
components from producing hundreds of millions of copied path
commands from a tiny font file (memory/CPU exhaustion DoS). Adds a
MaxTotalPoints budget alongside the existing depth/component-count
caps, charged before allocation/copy at both the simple-glyph decode
site and the composite AppendTransformed copy site.
- CmapTable: reject format-12 groups whose endCharCode exceeds
U+10FFFF, so malformed cmap data cannot resolve a glyph for an
invalid code point.
- TrueTypeFont.GetKerning: validate both glyph indices against
GlyphCount before delegating to KernTable, so a kern pair that is a
valid ushort but out of range for this font's glyph count returns 0
instead of a stale/incorrect adjustment, while preserving the
never-throws contract.
- Rework the CmapTable format-4 and KernTable subtable-position
overflow regression tests to exercise the extracted arithmetic
helpers directly via reflection instead of allocating near-2GB byte
arrays to simulate huge file offsets.
- Correct the design doc's composite-glyph-bounds description and add
the missing requirement/verification entry for the new total
point/command budget.
The requirement added for the total point/command budget was not
referenced from its parent's children list, leaving it unreachable
from the quality/system-tagged root and failing reqstream's
orphaned-requirement check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements Phase 5 of the CanvasNet roadmap: a new DemaConsulting.CanvasNet.Fonts namespace with TrueType/SFNT font support.
Passed local code-review and formal-review before opening this PR (established process for this session).