CVE-2026-95381
Overview
Background
- `DeserializeOopTypeface`
- the printing deserialization callback in
printing/common/metafile_utils.ccthat reconstructs anSkTypefacefrom an out-of-process serialized picture stream. - `SkTypeface::MakeDeserialize`
- Skia’s legacy typeface deserializer that parses embedded font bytes from a stream using the platform font manager.
- `skia::MakeTypefaceWithFontations`
- a Skia-ext helper that deserializes an
SkFontDescriptorand, when stream data is attached, instantiates the typeface strictly through the memory-safe Fontations engine. - `FontationsPrinting`
- the Blink runtime-enabled feature that gates whether printing routes typeface instantiation through Fontations.
Root Cause Analysis
Verify before building a PoC
What the commit does not establish: Mostly accurate. One function is misattributed to the paint_preview namespace. 2ded1aec alone sits behind an experimental flag. The fix spans 167ea6db, 2ded1aec and 619c0826 (the only commit with 'Fixed:'). The parser defect itself is not shown.
167ea6db8d29), which an audit found was a preparatory/helper change, not the fix. This page now points to the commit whose diff removes the bug.In the pre-fix printing path, DeserializeOopTypeface reads a typeface id and a data_included flag from the serialized stream (metafile_utils.cc:504-511), and when data is present (metafile_utils.cc:521 DCHECK(data_included)) it calls SkTypeface::MakeDeserialize(&stream, skia::DefaultFontMgr()) at metafile_utils.cc:522-523 to parse the embedded font bytes. Those bytes are the typeface payload written by the serialize side (metafile_utils.cc:486-498), so DeserializeOopTypeface feeds an attacker-influenceable font stream directly into the legacy font parser, matching the advisory’s “improper input validation in Printing.”
The fix commit 2ded1aec replaces that single call with a branch: when blink::features::kFontationsPrinting is enabled it instantiates the typeface via skia::MakeTypefaceWithFontations(&stream), otherwise it falls back to SkTypeface::MakeDeserialize. MakeTypefaceWithFontations (added in skia/ext/font_utils.cc by 5139162/relanded by 167ea6db) deserializes an SkFontDescriptor and, if desc.hasStream(), parses the attached bytes only through SkTypeface_Make_Fontations(desc.detachStream(), desc.getFontArguments()), i.e. the memory-safe Fontations font engine rather than the platform parser. Because 2ded1aec keeps the SkTypeface::MakeDeserialize call in the else branch, its new code path runs only when FontationsPrinting is enabled, and at 2ded1aec that feature was still status: "experimental" (runtime_enabled_features.json5:3217-3218 at 619c08268865^), not on by default. Commit 619c08268865 (2026-09-15, “Enable the FontationsPrinting runtime enabled feature by default”) flips it to stable and carries the Fixed: 497603247 footer, so users were protected by default only from 619c08268865 onward. An earlier commit e13ea4a had instead wrapped MakeDeserialize with an OTS sanitizer argument &skia::SanitizeTypefaceStream, but the pre-fix source at 2ded1aec^ shows metafile_utils.cc:522-523 back to a plain MakeDeserialize call, so that OTS approach was not the surviving printing fix.
SkTypeface::MakeDeserialize parser; 2ded1aec reroutes that exact call through the memory-safe Fontations engine (skia::MakeTypefaceWithFontations) behind the FontationsPrinting flag, and the protection takes effect by default only when 619c08268865 enables that flag.Attack Path
- Craft a malicious embedded font
Produce a serialized OOP typeface stream whose
data_includedpayload contains a malformed embedded font. (inferred) - Reach the printing deserializer
Have the stream deserialized by
DeserializeOopTypeface, which atmetafile_utils.cc:522-523passes the bytes toSkTypeface::MakeDeserialize. (reachability of the untrusted producer is inferred) - Mis-parse in the legacy font path The legacy deserializer parses the attacker-controlled font bytes with insufficient validation, the defect the advisory records. (inferred for the precise parser effect)
- Mitigation engaged
Only with
FontationsPrintingenabled (by default from619c08268865; before that theelsebranch still callsSkTypeface::MakeDeserialize), the same bytes are instead parsed by the memory-safe Fontations engine viaskia::MakeTypefaceWithFontations.
Impact Assessment
FontationsPrinting flag, so builds between 2ded1aec and 619c08268865 (which turns the flag on by default) remained unprotected by default.Files Changed
printing/DEPSprinting/common/BUILD.gnprinting/common/metafile_utils.ccthird_party/blink/public/common/BUILD.gn
Audit Directions
- Untrusted font deserializationFlag any call to
SkTypeface::MakeDeserializeon a stream that originates from another process and confirm it is routed through the memory-safe Fontations path. - Flag-gated security fixesWhen a hardening lands behind a runtime feature, verify the enabling commit (here
619c08268865, the one with theFixed:footer) actually ships before treating the hole as closed, and date the fix from that commit. - Abandoned mitigationsCheck whether an earlier mitigation (e.g. the
skia::SanitizeTypefaceStreamOTS wrapper ine13ea4a) was reverted from the vulnerable path before relying on it.
Patch
diff --git a/printing/common/metafile_utils.cc b/printing/common/metafile_utils.cc
index 2921e53..bf618fa 100644
--- a/printing/common/metafile_utils.cc
+++ b/printing/common/metafile_utils.cc
@@ -12,6 +12,7 @@
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/containers/span_reader.h"
+#include "base/feature_list.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
@@ -22,6 +23,7 @@
#include "skia/ext/codec_utils.h"
#include "skia/ext/font_utils.h"
#include "skia/ext/skia_utils_base.h"
+#include "third_party/blink/public/common/features.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkFontMgr.h"
#include "third_party/skia/include/core/SkImage.h"
@@ -519,8 +521,13 @@
// Typeface not encountered before, expect it to be present in the stream.
DCHECK(data_included);
- sk_sp<SkTypeface> typeface =
- SkTypeface::MakeDeserialize(&stream, skia::DefaultFontMgr());
+ sk_sp<SkTypeface> typeface;
+ if (base::FeatureList::IsEnabled(blink::features::kFontationsPrinting)) {
+ typeface = skia::MakeTypefaceWithFontations(&stream);
+ } else {
+ typeface = SkTypeface::MakeDeserialize(&stream, skia::DefaultFontMgr());
+ }
+
context->emplace(id, typeface);
return typeface;
}
diff --git a/printing/DEPS b/printing/DEPS
index a16613b..3b4727f6 100644
--- a/printing/DEPS
+++ b/printing/DEPS
@@ -5,6 +5,7 @@
"+printing/printing_jni_headers",
"+services/data_decoder/public/cpp",
"+skia/ext",
+ "+third_party/blink/public/common/features.h",
"+third_party/icu/source/common/unicode",
"+third_party/icu/source/i18n/unicode",
"+third_party/re2/src/re2",
diff --git a/printing/common/BUILD.gn b/printing/common/BUILD.gn
index f8f7793..3707ef02 100644
--- a/printing/common/BUILD.gn
+++ b/printing/common/BUILD.gn
@@ -19,5 +19,6 @@
deps = [
"//pdf:pdf_accessibility_constants",
"//printing/buildflags",
+ "//third_party/blink/public/common",
]
}
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index 9556300b..5a5d9a6 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -113,6 +113,7 @@
"//content/*",
"//fuchsia/*",
"//pdf/*",
+ "//printing/*",
"//services/*",
"//storage/*",
"//third_party/blink/*",