Skip to content

GlyphTypeface.ComputeSubset rejects stock Windows fonts (Calibri, Cambria, Constantia, Corbel) after TtfDelta bounds-check hardening #11834

Closed

Description

@JeppeJepsen

Description

Since the August 2026 servicing update, GlyphTypeface.ComputeSubset throws FileFormatException for several stock, unmodified Windows system fonts. This breaks all XPS export, XPS-based PDF export and printing for documents that use them.

The fonts are structurally valid — every table offset is in bounds and all checksums are correct. They render perfectly on screen. Only subsetting fails, which makes the failure easy to misdiagnose, since the exception message names the font file and implies it is corrupt.

The trigger is the legacy kern table: in these fonts the subtable's uint16 length field has wrapped, and the new bounds validation rejects the entire font rather than skipping the offending optional table.

Affected: Calibri, Cambria, Constantia and Corbel — 15 faces in total. Calibri is a Microsoft default font, so a stock Windows install plus FontFamily="Calibri" is sufficient to hit this.

Reproduction Steps

Console app, <UseWPF>true</UseWPF>, x64, on Windows with stock Calibri installed:

using System.Reflection;
using System.Windows.Media;

var m = typeof(GlyphTypeface).GetMethod("ComputeSubset",
    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

var gt = new GlyphTypeface(new Uri("file:///C:/Windows/Fonts/calibrib.ttf"));

var glyphs = new List<ushort>();
foreach (var c in "Hello world 0123456789")
    if (gt.CharacterToGlyphMap.TryGetValue(c, out var g) && !glyphs.Contains(g))
        glyphs.Add(g);

var bytes = (byte[])m.Invoke(gt, new object[] { glyphs });   // throws

Swap calibrib.ttf for calibril.ttf (Calibri Light) and it succeeds — same machine, same process, same code.

The public path reaches the same code: build a FlowDocument with FontFamily="Calibri", then XpsDocument.CreateXpsDocumentWriter(doc).Write(paginator).

Expected behavior

Stock Windows system fonts subset successfully, producing a valid XPS/PDF document, as they did before the update.

Actual behavior

System.IO.FileFormatException: 'file:///C:/WINDOWS/FONTS/CALIBRIB.TTF' file does not conform to the expected file format specification.

No document is produced — the whole serialization call aborts, with no partial output and no font fallback.

   at MS.Internal.TrueTypeSubsetter.ComputeSubset(Void* fontData, Int32 fileSize, Uri sourceUri, Int32 directoryOffset, UInt16[] glyphArray)
   at MS.Internal.FontFace.TrueTypeFontDriver.ComputeFontSubset(ICollection`1 glyphs)
   at System.Windows.Media.GlyphTypeface.ComputeSubset(ICollection`1 glyphs)
   at System.Windows.Xps.Serialization.FEMCacheItem.SubSetFont(ICollection`1 glyphs, Stream stream)
   at System.Windows.Xps.Serialization.FEMCacheItem.Commit()
   at System.Windows.Xps.Serialization.XpsFontSubsetter.CommitFontSubsetsSignal(FontSubsetterCommitPolicies signal)
   at System.Windows.Xps.Serialization.XpsSerializationManager.ReleaseXmlWriter(Type writerType)
   at System.Windows.Xps.Serialization.ReachHierarchySimulator.SimulateEndFixedDocumentSequence(XmlWriter xmlWriter)
   at System.Windows.Xps.Serialization.XpsSerializationManager.SaveAsXaml(Object serializedObject)

On .NET 8/9/10 the message wording differs slightly: File 'file:///C:/Windows/Fonts/calibrib.ttf' has an invalid file format.

Regression?

Yes. Verified by A/B on one machine — same font, same test code, swapping only PresentationCore.dll via DEVPATH:

PresentationCore.dll Origin Result
4.8.9032.0 (built 2024-04-01) pre-update, from WinSxS OK — 499,120-byte subset
4.8.9344.0 (built 2026-07-08) KB5120708, installed 2026-08-11 FAIL

Our production failure occurred 2026-08-12, the day after KB5120708 installed.

The affected fonts have shipped with an identically sized kern table across Calibri 6.24, 6.26 and 6.27 — all three fail now and all worked before — so the fonts did not change. Only the subsetter's tolerance did.

I do not have an unpatched .NET Core runtime installed to bisect that side, but given the shared TtfDelta sources I would expect 8.0.29 / 9.0.18 / 10.0.10 to succeed.

Known Workarounds

None that are both correct and cost-free.

  1. Strip the kern table from the font and load the repaired copy from a private font folder. Verified to fix all 15 faces with no visual or metric change. Requires redistributing a modified Microsoft font binary.
  2. Patch the fonts in C:\Windows\Fonts in place. Needs takeown/icacls, a FontCache service stop and a reboot, and is undone by any future font-carrying Windows Update, sfc /scannow or DISM repair.
  3. Use an unaffected font — Calibri Light, Candara, Arial, Georgia and Consolas all subset fine. Changes document appearance.
  4. Disable subsetting via XpsSerializationManager.SetFontSubsettingPolicy(FontSubsetterCommitPolicies.None), embedding the whole font instead. Avoids the crash but replaces a ~500 KB subset with ~1.6 MB of font data per document, which was unacceptable for us.

The intended opt-out switch does not work — details under Other information.

Rolling back the update is not a workaround, as it re-opens CVE-2026-62886 / CVE-2026-62897.

Impact

Radius — any WPF app that subsets fonts: XPS export, XPS-based PDF export, and printing. Triggered by 15 stock faces: Calibri, Cambria, Constantia and Corbel (Regular/Bold/Italic/BoldItalic each, except Cambria which has no affected Regular). No third-party or exotic font is needed.

Intensity — total functional failure rather than degradation. Screen rendering is unaffected, so the app looks healthy right up until export.

Reach — every machine that has taken the August 2026 security update. Because it is security servicing, it deploys broadly and automatically and cannot reasonably be declined.

Concrete example — our production system issues official education diplomas. Diploma generation stopped completely on 2026-08-12, mid-season, with no way for affected students to obtain their documents until we ship a workaround.

Configuration

Reproduces on:

  • .NET Framework 4.8.1 — PresentationCore.dll 4.8.9344.0 (NET481REL1LAST_25H2_B), installed via KB5120708 on 2026-08-11
  • .NET 8.0.30, 9.0.19, 10.0.11 (Microsoft.WindowsDesktop.App)

Does not reproduce on: .NET Framework 4.8.1 with PresentationCore.dll 4.8.9032.0.

OS: Windows 11 Pro 25H2, build 10.0.26200.9168

Architecture: x64. The 32-bit PresentationCore.dll contains the same code; I tested x64 only.

Specific to this configuration? No — it depends on the font file, not the machine, OS version or architecture. Font under test: Calibri Bold 6.27, C:\Windows\Fonts\calibrib.ttf, 1,615,616 bytes.

Other information

Where the problem is

src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/TrueTypeSubsetter/TtfDelta/ — the TTF_SAFE_CHECKS_ENABLED() validation added by #11642 ("Fix potential overflow issues in WPF native code", merged 2026-05-18), which rewrote 13 files in that directory. Most likely ttfacc.cpp, ttftable.cpp or modtable.cpp, where table lengths are read and bounds-checked.

Why these fonts trip it

The legacy kern subtable header stores its own length in a uint16 (max 65,535), while the table directory stores the real length in a uint32. For calibrib.ttf:

Field Value
kern length from table directory (uint32) 160,164
Subtable header length at kern+6 (uint16) 29,088

160,164 mod 65,536 = 29,088 — the field has wrapped. This is a decades-old limitation of the kern format, and these fonts have always exceeded it. DirectWrite ignores the table entirely and applies kerning from GPOS, so nobody noticed until the subsetter started validating the field.

Measured across 25 fonts, the correlation is perfect — 15/15 fonts whose kern table exceeds 65,535 bytes fail; 10/10 that don't, succeed:

Font kern bytes uint16 field Subsets?
calibri / calibrib / calibrii / calibriz 149,796–160,254 wrapped fail
cambriab / cambriai / cambriaz 190,000–218,000 wrapped fail
constan / constanb / constani / constanz 219,000–232,000 wrapped fail
corbel / corbelb / corbeli / corbelz 80,000–100,000 wrapped fail
calibril / calibrili 12,288 / 12,300 valid ok
corbell / corbelli ~41,000 valid ok
Candara 48,792 valid ok
arial / times / verdana 3,000–5,000 valid ok
consola / georgia no kern — ok

Suggested fix

An inconsistent legacy kern subtable should cause that table to be skipped or truncated, not the whole font to be rejected. It is optional and unused by the WPF/DirectWrite text stack. I verified that stripping only kern restores subsetting for every affected face with no visual or metric change and zero missing characters (including Danish æøåÆØÅ): calibri 520,552 B, calibrib 513,800 B, calibrii 256,064 B, calibriz 255,208 B, corbel 81,332 B, constan 118,996 B, cambriab 241,084 B.

Second bug: the TtfDelta AppContext switch is not wired up

Switch.MS.Internal.TtfDelta.DisableDirectWriteForwarderBoundsCheckProtection is present as a UTF-16 literal in the shipped binary, and MS.Internal.CoreAppContextSwitches.DisableDirectWriteForwarderBoundsCheckProtection honours it — but enabling it does not change behaviour. Tested on .NET Framework 4.8.9344.0 and .NET 8.0.30:

Configuration AppContext.TryGetSwitch CoreAppContextSwitches Result
AppContext.SetSwitch(name, true) True — FAIL
app.config AppContextSwitchOverrides True — FAIL
app.config + MediaSystem booted via RenderTargetBitmap True True FAIL
All three bounds-check switches enabled True True FAIL

Reflecting over PresentationCore shows managed setters exist for the sibling mitigations but not for this one:

  • MS.Win32.Penimc.UnsafeNativeMethods.SetDisablePenImcBoundsCheckProtection(Boolean) — present
  • MS.Win32.PresentationCore.UnsafeNativeMethods+MilCoreApi.WpfGfx_SetDisableBoundsCheckProtection(Boolean) — present
  • TtfDelta equivalent — no managed method, though the native symbol SetDisableDirectWriteForwarderBoundsCheckProtection and the global g_fDWFBoundsCheckEnabled are both present in the binary.

In #11642 the .NET Core truetype.cpp reads the switch itself via System::AppContext::TryGetSwitch in a static-once block at the top of ComputeSubset; that path appears not to take effect in the shipped builds. Please wire this up — it is the documented escape hatch, and currently the only mitigation that would not require modifying Microsoft's font binaries.

Related

Activity

  1. JeppeJepsen commented on Aug 12, 2026

    @JeppeJepsen
    Author

    Also filed against .NET Framework 4.8.1, since this repo does not service it and a fix shipped in Microsoft.WindowsDesktop.App will not reach .NET Framework applications: https://developercommunity.visualstudio.com/t/KB5120708:-GlyphTypefaceComputeSubset-r/11134941

    The .NET Framework build is affected identically.
    PresentationCore.dll 4.8.9344.0, shipped in KB5120708 on 2026-08-11, fails.
    PresentationCore.dll 4.8.9032.0 succeeds on the same machine with the same font and the same code (DEVPATH A/B).
    Since the two implementations are forks of the same TtfDelta sources, both will need the fix.

  2. added
    BugProduct bug (most likely)
    regressionstatus: This issue is a regression from a previous build or release
    Priority:0Work that we can't release without
    Cost:MWork that requires one engineer up to 2 weeks
    on Aug 13, 2026
  3. dfergcsd commented on Aug 13, 2026

    @dfergcsd

    Independent reproduction, and two data points that may help — one correcting the affected-font list, one filling in the .NET Core bisect you weren't able to run.

    Environment: Windows 11 Pro 26200, .NET Framework 4.8.1 PresentationCore.dll 4.8.9344.0 (KB5120708, installed 2026-08-12), plus Microsoft.WindowsDesktop.App 8.0.30 / 9.0.19 / 10.0.11. Hit in production the following day — PDF export and printing from a structural engineering app, same SaveAsXaml stack.

    Scanning all 193 installed font families, exactly five fail: Calibri, Cambria, Cambria Math, Constantia, Corbel. Your kern correlation holds on every one I measured.

    Cambria Regular and Cambria Math are affected

    The table lists cambriab/cambriai/cambriaz and notes Cambria has no affected Regular, but Cambria Regular does not ship as cambria.ttf on this machine — it is cambria.ttc, and the collection carries a wrapped kern shared by both faces:

    Face kern (uint32) subtable uint16 Wrapped
    cambria.ttc face 0 — Cambria 178,308 47,232 yes
    cambria.ttc face 1 — Cambria Math 178,308 47,232 yes

    Both fail SaveAsXaml at regular weight, so the affected count is higher than 15 once the collection is counted. Worth checking whether .ttc handling needs its own coverage in the fix.

    .NET Core bisect

    You expected 8.0.29 / 9.0.18 / 10.0.10 to succeed but had no unpatched runtime to test. I can't confirm those exact patches, but the major-version split is unambiguous — same binary, same fonts, same machine, only Microsoft.WindowsDesktop.App rolled via runtimeconfig.json:

    Runtime wpfgfx_cor3.dll built Calibri
    6.0.36 2024-10-26 PASS
    7.0.20 2024-05-20 PASS
    8.0.30 2026-07-23 FAIL
    9.0.19 2026-07-21 FAIL
    10.0.11 2026-07-24 FAIL

    6 and 7 are out of support and never received the change, which matches your PresentationCore A/B on the .NET Framework side and confirms both forks are affected.

    On workaround 4

    Disabling subsetting was unusable for you at ~1.6 MB per document, but it becomes practical when applied only on failure rather than globally: catch the FileFormatException, check SourceUri names a font, and re-serialize that one document with FontSubsetterCommitPolicies.None. Unaffected documents keep their normal subset output and pay nothing, and it self-heals once this is fixed — no font list to maintain.

    One trap worth flagging for anyone taking that route to a printer rather than a file: PrintQueue.AddJob(..., fastCopy: false) re-validates the package and fails again on the embedded font, now reported against the obfuscated resource rather than the original:

    FileFormatException: 'pack://...xps/Resources/05a6b082-....ODTTF'
      file does not conform to the expected file format specification
    

    fastCopy: true streams the package to the spooler untouched and succeeds. Same root cause, second hop.

    Also confirming your finding that the AppContext switch has no effect — no managed setter for the TtfDelta equivalent, so there is no configuration-only mitigation.

  4. ToreDemant commented on Aug 18, 2026

    @ToreDemant

    Could this have been introduced with #11835

  5. miloush commented on Aug 23, 2026

    @miloush
    Contributor

    To be fair the fonts should be fixed. EDIT: since Windows supports only one subtable of format 0, there isn't anything the fonts can do.

  6. himgoyalmicro commented on Aug 26, 2026

    @himgoyalmicro
    Contributor

    We are investigating the issue and working on a fix.

  7. self-assigned this
    on Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

BugProduct bug (most likely)Cost:MWork that requires one engineer up to 2 weeksPriority:0Work that we can't release withoutWave1regressionstatus: This issue is a regression from a previous build or release

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions