e57f9072af FontPlatformSerializedAttributes::toCFDictionary should skip null key/value pairs in paletteColors and variations
Triage note: IPC-reachable null deref in the GPU process via crafted CacheFont message; skipping null pairs is a real crash-hardening fix.
Contents
The bug at a glance
OBSERVED: the PAIR_VECTOR_TO_DICTIONARY macro in FontPlatformSerializedAttributes::toCFDictionary now skips any key/value pair where item.first or item.second is null before calling CFDictionaryAddValue. INFERRED (and stated in the commit): paletteColors and variations are IPC-decoded vectors of optional-encoded key/value pairs, so a crafted CacheFont message can carry null RetainPtrs; passing NULL to CFDictionaryAddValue raises NSInvalidArgumentException that propagates uncaught and crashes the GPU process. Medium: an IPC-reachable, unauthenticated (any web content via WebProcess->GPUProcess) remote crash of the GPU process; a reliability/DoS hardening fix with no demonstrated memory corruption.
This is a trust-boundary deserialization bug. FontPlatformSerializedAttributes crosses from the (attacker-influenced) WebContent process into the GPU process via the CacheFont IPC. The key/value entries of paletteColors/variations are encoded as IPC optionals, so a false prefix legitimately decodes to a null RetainPtr. The macro that builds the CFDictionary assumed both halves were always present and fed them straight to CoreFoundation, which rejects NULL by throwing an Objective-C exception that no one catches – taking down the shared GPU process.
Root cause
FontPlatformSerializedAttributes::toCFDictionary() reconstructs a CoreText font descriptor’s attribute dictionary from data deserialized over IPC. Two of its fields, paletteColors and variations, are vectors of key/value pairs: the key is a RetainPtr<CFNumberRef> and the value a RetainPtr<CGColorRef> (palette colors) or RetainPtr<CFNumberRef> (variations). Each half is encoded in IPC as an optional – the wire format carries a bool ‘has value’ prefix, and a false prefix decodes to a null RetainPtr on the receiving side.
The conversion is driven by the PAIR_VECTOR_TO_DICTIONARY macro. Pre-patch it iterated the vector and unconditionally called CFDictionaryAddValue(newResult.get(), item.first.get(), item.second.get()). CFDictionaryAddValue with the kCFTypeDictionary callbacks does not tolerate a NULL key or value: CoreFoundation raises an NSInvalidArgumentException. This conversion runs in the GPU process while handling the CacheFont message from a WebContent process, and the exception is not caught anywhere on that path, so it propagates and crashes the GPU process.
Because the pair halves are attacker-controllable optionals, a WebContent process (driven by ordinary web content, or the IPC testing API) can send a CacheFont whose serializableAttributes.paletteColors or variations contains entries like [{}, {}] (both null) or [number, {}] (null value) or [{}, number] (null key). Any such entry reaches CFDictionaryAddValue with a NULL argument and triggers the crash.
The fix wraps the add in a guard: for each item, only call CFDictionaryAddValue when item.first && item.second, silently skipping any pair with a null key or value. This keeps CoreFoundation from ever seeing a NULL and preserves the well-formed entries. OBSERVED: the added IPC test sends CacheFont with several null palette-color and variation entries, then does a FinalizeRenderingUpdate round-trip; if the GPU had crashed the round-trip would time out, so its success verifies the process survived.
Key code
Skip null key/value pairs in PAIR_VECTOR_TO_DICTIONARY
#define PAIR_VECTOR_TO_DICTIONARY(key, vector) \
if (vector) { \
RetainPtr<CFMutableDictionaryRef> newResult = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); \
- for (auto& item : *vector) \
- CFDictionaryAddValue(newResult.get(), item.first.get(), item.second.get()); \
+ for (auto& item : *vector) { \
+ if (item.first && item.second) \
+ CFDictionaryAddValue(newResult.get(), item.first.get(), item.second.get()); \
+ } \
CFDictionaryAddValue(result.get(), key, newResult.get()); \
}
Patch walkthrough
Source/WebCore/platform/graphics/coretext/FontPlatformDataCoreText.cpp— The PAIR_VECTOR_TO_DICTIONARY macro (used for paletteColors and variations in FontPlatformSerializedAttributes::toCFDictionary) changed its per-item body from an unconditional CFDictionaryAddValue(newResult.get(), item.first.get(), item.second.get()) to a guardedif (item.first && item.second) CFDictionaryAddValue(...). This prevents passing a NULL key or value – produced by an IPC optional decoding to a null RetainPtr – to CoreFoundation, which would otherwise throw an uncaught NSInvalidArgumentException and crash the GPU process.LayoutTests/ipc/cache-font-null-palette-colors-crash.html— IPC test (IPCTestingAPIEnabled) that sends a CacheFont message whose serializableAttributes.paletteColors and variations contain null-keyed/null-valued pairs, then verifies GPU-process liveness with a FinalizeRenderingUpdate round-trip that would time out if the process had crashed.
Background
FontPlatformSerializedAttributes — IPC-serializable representation of a CoreText font descriptor’s attributes. toCFDictionary() rebuilds the CFDictionaryRef in the receiving (GPU) process from bytes originating in a WebContent process.
CacheFont IPC — A GPUConnectionToWebProcess / RemoteRenderingBackend message that ships font platform data to the GPU process for caching. Its platformData carries the serializableAttributes, including paletteColors and variations.
IPC optional encoding — Optionals are encoded as a bool presence prefix followed by the value only if present. A false prefix legitimately decodes to a null RetainPtr, so any optional-encoded field can arrive null from a crafted sender.
CFDictionaryAddValue with NULL — Under kCFTypeDictionary callbacks CoreFoundation rejects a NULL key or value by raising NSInvalidArgumentException. Uncaught in the GPU process, it crashes the whole process.
Vulnerability window
- Serialization design — paletteColors/variations pair halves were encoded as IPC optionals, making a null RetainPtr a representable decoded value.
- Unchecked conversion — PAIR_VECTOR_TO_DICTIONARY fed item.first/second directly to CFDictionaryAddValue with no null check.
- Crash — A crafted CacheFont with null palette/variation entries passed NULL to CoreFoundation, raising an uncaught NSInvalidArgumentException that crashed the GPU process.
- Discovery — bug 315820 / rdar://177612731 identified the IPC-reachable GPU-process crash.
- Fix + test — Add
if (item.first && item.second)guard; new IPC test confirms GPU liveness via a FinalizeRenderingUpdate round-trip.
Proof of concept
VERBATIM from LayoutTests/ipc/cache-font-null-palette-colors-crash.html (IPCTestingAPIEnabled). paletteColors and variations carry pairs where one or both halves are {} (an absent optional -> null RetainPtr): [{},{}] (both null), [number,{}] (null value), [{},number] (null key). Pre-patch each null half reached CFDictionaryAddValue and threw an uncaught NSInvalidArgumentException, crashing the GPU process; the test then confirms the crash by a FinalizeRenderingUpdate round-trip that times out without the fix.
const number = { optionalValue: { get: { alias: { variantType: "char", variant: 1 } } } };
try {
remoteBackend.CacheFont({
data: { renderingResourceIdentifier: {}, origin: 0, isInterstitial: 0, visibility: 0, isTextOrientationFallback: 0 },
platformData: {
m_size: 12, m_orientation: 0, m_widthVariant: 0, m_textRenderingMode: 0,
m_syntheticBold: false, m_syntheticOblique: false,
serializableAttributes: { optionalValue: {
fontName: "Helvetica", descriptorLanguage: "", descriptorTextStyle: "",
matrix: {}, ignoreLegibilityWeight: {}, baselineAdjust: {}, fallbackOption: {},
fixedAdvance: {}, orientation: {}, palette: {}, size: {}, sizeCategory: {},
track: {}, unscaledTracking: {},
paletteColors: { optionalValue: [
[{}, {}],
[number, {}],
] },
variations: { optionalValue: [
[{}, {}],
[{}, number],
[number, {}],
] },
opticalSize: {}, traits: {}, featureSettings: {}, additionalNumber: {}
} },
m_options: 0, m_url: {}, m_psName: {}
},
renderingResourceIdentifier: {}
});
} catch { }
Exploitation
- Reach the IPC — From a compromised or scripted WebContent process (or via the IPC testing API), craft a CacheFont message to the GPU process’s RemoteRenderingBackend.
- Embed null pairs — Set serializableAttributes.paletteColors or variations entries with an absent optional for the key or value, decoding to a null RetainPtr in the GPU process.
- Crash GPU process — CFDictionaryAddValue receives NULL and raises NSInvalidArgumentException; uncaught, it crashes the shared GPU process, disrupting rendering for all clients – a cross-process DoS from web-reachable input.
Detection & hunting
For defenders and SOC / detection engineers:
- NSInvalidArgumentException in GPU process —
- CacheFont with null palette/variation pairs —
Audit directions
- Optional-decoded IPC fields into CF/CG APIs —
- Font attribute reconstruction —
- Uncaught ObjC exceptions across IPC —