← WebKit Silent-Fix Report — 2026-W25

b5b06c0fc2  [GPU Process]: Before decoding FEColorMatrix validate the length of the `values` vector

severity high class OOB confidence 0.82 WebKit GPU Process filters (FEColorMatrix) exploitable-grade
Said Abou-Hallawa Tue Jun 16 11:30:01 2026 -0700 full: b5b06c0fc266fbe80ac09bd769f0557e0a6fc8f0 bug report ↗ view on GitHub ↗
Primitive: OOB read from mismatched values-vector length
Triage note: IPC input validation added before decoding a filter; PoC uses IPCTestingAPI to send a type/values mismatch that crashes the GPU process.
Contents

The bug at a glance

This is a GPU-process out-of-bounds read caused by trusting an IPC-supplied FEColorMatrix whose values vector length does not match its type. The GPU process handles input from the less-privileged WebContent process, so an OOB read there is a cross-process memory-safety issue that can crash the GPU process or disclose its memory. The PoC uses the IPC testing API to send a SATURATE matrix with an empty values array; because it is a read/crash primitive rather than a demonstrated write, high rather than critical.

WebKit’s GPU process renders SVG filters on behalf of web pages. An FEColorMatrix effect carries a type (matrix, saturate, hue-rotate, luminance-to-alpha) and a values array whose required length depends on that type. The IPC decoder rebuilt an FEColorMatrix from web-process-supplied fields without checking that values matched type, so a compromised or scripted web process could send, e.g., a SATURATE matrix with zero values and have the GPU process index into the empty array.

Root cause

In the DOM path, SVGFEColorMatrixElement enforces the type/length invariant: before the patch isInvalidValuesLength() rejected a MATRIX with size != 20, or a HUEROTATE/SATURATE with size != 1, and createFilterEffect() returned nullptr for an invalid length. That guard lived only on the element side. The FEColorMatrix object itself, once constructed, computes its color transform from m_values assuming the correct count – SATURATE and HUEROTATE read values[0] to build their 3x3 components, MATRIX consumes 20 entries – so an FEColorMatrix with a mismatched (e.g. empty) values vector reads past the end of the vector.

The GPU process reconstructs filter effects from IPC. WebCoreArgumentCoders.serialization.in declared FEColorMatrix as decoding a ColorMatrixType type() and a Vector<float> values() with no cross-field validation, then calling FEColorMatrix::create(type, values, …). A web process (or an attacker using IPCTestingAPI) can therefore send a FilteredNativeImage message whose filter contains an FEColorMatrix with type FECOLORMATRIX_TYPE_SATURATE (2) but values: [] . The decoder builds the effect and, when the filter is applied, the SATURATE path reads values[0] on an empty Vector<float> – an out-of-bounds heap read in the GPU process.

The fix centralizes the invariant into a reusable predicate FEColorMatrix::areValuesValidForType(ColorMatrixType, const Vector<float>&): MATRIX requires size == 20, SATURATE and HUEROTATE require size == 1, LUMINANCETOALPHA is always valid (ignores values), and UNKNOWN is invalid. This predicate is now enforced in three places. FEColorMatrix::create() gains ASSERT(areValuesValidForType(type, values)) as a debug tripwire. SVGFEColorMatrixElement drops isInvalidValuesLength() and calls FEColorMatrix::areValuesValidForType() in both svgAttributeChanged() and createFilterEffect() (returning nullptr when invalid). Critically, the serialization coder adds [Validator='WebCore::FEColorMatrix::areValuesValidForType(*type, *values)'] on the values() member, so the IPC decoder itself rejects a decoded FEColorMatrix whose values length is wrong for its type, causing the decode to fail (surfacing as a TypeError to the sender) before any FEColorMatrix is created in the GPU process.

The validator closing the gap on the IPC boundary is the security-relevant change: DOM-side checks never protected the GPU process against a malicious sender, and this bug is precisely a missing IPC input-validation check that allowed a type/length mismatch to reach length-dependent code.

Key code

New type/length predicate and its IPC validator annotation

bool FEColorMatrix::areValuesValidForType(ColorMatrixType type, const Vector<float>& values)
{
    switch (type) {
    case ColorMatrixType::FECOLORMATRIX_TYPE_MATRIX:
        return values.size() == 20;
    case ColorMatrixType::FECOLORMATRIX_TYPE_SATURATE:
    case ColorMatrixType::FECOLORMATRIX_TYPE_HUEROTATE:
        return values.size() == 1;
    case ColorMatrixType::FECOLORMATRIX_TYPE_LUMINANCETOALPHA:
        return true;
    case ColorMatrixType::FECOLORMATRIX_TYPE_UNKNOWN:
        return false;
    }
}

// WebCoreArgumentCoders.serialization.in:
// [Validator='WebCore::FEColorMatrix::areValuesValidForType(*type, *values)'] Vector<float> values();

Patch walkthrough

  • Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in — Annotates the FEColorMatrix values() member with [Validator='WebCore::FEColorMatrix::areValuesValidForType(*type, *values)'], so the generated IPC decoder validates the values vector length against the type and fails the decode on mismatch – the core cross-process fix.
  • Source/WebCore/platform/graphics/filters/FEColorMatrix.cpp — Adds static areValuesValidForType(type, values) encoding the per-type length rules (MATRIX==20, SATURATE/HUEROTATE==1, LUMINANCETOALPHA always valid, UNKNOWN invalid) and adds ASSERT(areValuesValidForType(type, values)) in create().
  • Source/WebCore/platform/graphics/filters/FEColorMatrix.h — Declares WEBCORE_EXPORT static bool areValuesValidForType(ColorMatrixType, const Vector<float>&) so it is usable from the element code and the generated serialization code.
  • Source/WebCore/svg/SVGFEColorMatrixElement.cpp/.h — Removes the local isInvalidValuesLength() and routes svgAttributeChanged() and createFilterEffect() through FEColorMatrix::areValuesValidForType(type, values), returning nullptr when invalid; unifies the DOM and IPC validation on one predicate.
  • LayoutTests/ipc/fecolormatrix-type-values-mismatch-crash.html (+ expected) — New IPC-testing-API test that sends a FilteredNativeImage message containing an FEColorMatrix with type 2 (SATURATE) and values: [] and passes if WebKit (the GPU process) does not crash.

Background

GPU Process — A separate WebKit process that performs graphics/media work (including SVG filter rendering) on behalf of WebContent. It receives serialized objects over IPC from the less-trusted web process, so it must validate all decoded inputs.

FEColorMatrix / SVG feColorMatrix — An SVG filter primitive that applies a color transform. Its type selects one of matrix (20 values), saturate (1 value), hueRotate (1 value), or luminanceToAlpha (no values), and its behavior indexes the values array accordingly.

Type/length invariant — For FEColorMatrix the required length of values is a function of type. Code such as the SATURATE/HUEROTATE component builders reads values[0]; violating the invariant (e.g. empty values with SATURATE) yields an out-of-bounds read of the Vector<float> backing store.

Serialization Validator annotation — WebKit’s IPC serialization DSL supports a [Validator=’…’] attribute on a decoded member that runs a predicate over already-decoded fields; if it returns false the decode fails, blocking construction of an object with an illegal field combination. This is the mechanism used to enforce the invariant at the IPC boundary.

IPCTestingAPI — A debug-only facility exposing window.IPC to script so tests (and, when enabled, attackers) can send arbitrary IPC messages. The regression test uses it to hand-craft a FilteredNativeImage message with a mismatched FEColorMatrix.

Vulnerability window

  1. Introduction — The FEColorMatrix IPC coder decoded type and values independently with no cross-field check, while length-dependent code assumed a valid count; only the DOM element validated length.
  2. Trigger — A compromised/scripted web process sends a FilteredNativeImage message whose FEColorMatrix has type SATURATE (2) but an empty values array.
  3. Decode — The GPU process decodes and constructs the FEColorMatrix without rejecting the mismatch.
  4. Apply — During filter application the SATURATE path reads values[0] on the empty vector – an out-of-bounds heap read in the GPU process.
  5. Impact — GPU-process crash or disclosure of adjacent heap memory into the filtered image, depending on layout.
  6. Fix — 315306@main adds areValuesValidForType and enforces it via the serialization [Validator], so the mismatched message fails to decode before any FEColorMatrix is built.

Proof of concept

The added LayoutTest (IPCTestingAPIEnabled) stands up a RemoteRenderingBackend and RemoteImageBuffer over a stream connection, then sends a FilteredNativeImage message whose filter contains an FEColorMatrix with type 2 (SATURATE) and an empty values array. Pre-fix the GPU process constructs the effect and reads values[0] out of bounds when applying the SATURATE transform, crashing the GPU process; post-fix the serialization [Validator] rejects the decode (surfacing as a TypeError to the sender) and the GPU process does not crash. The expected result file is simply ‘This test passes if WebKit does not crash.’

remoteImageBuffer.FilteredNativeImage({
    filter: { subclasses: { variantType: 'WebCore::SVGFilterRenderer', variant: {
        primitiveUnits: 0,
        expression: { alias: [ {index:0,level:0,geometry:{}}, {index:1,level:0,geometry:{}} ] },
        effects: [
            { subclasses: { variantType: 'WebCore::SourceGraphic', variant: { operatingColorSpace: srgbColorSpace } } },
            { subclasses: { variantType: 'WebCore::FEColorMatrix', variant: {
                type: 2,    // FECOLORMATRIX_TYPE_SATURATE
                values: [], // Empty - should have at least 1 value
                operatingColorSpace: srgbColorSpace
            } } }
        ],
        geometry: { referenceBox:{location:{x:0,y:0},size:{width:128,height:128}},
                    filterRegion:{location:{x:0,y:0},size:{width:128,height:128}},
                    scale:{width:1,height:1} },
        filterRenderingModes: 1, isShowingDebugOverlay: false, renderingResourceIdentifierIfExists: {}
    } } }
});

Exploitation

  1. Precondition — Attacker needs to send IPC to the GPU process – either via a compromised WebContent process or, in test/debug builds, IPCTestingAPI. The bug is a cross-process input-validation gap, not directly reachable from unprivileged JS in a shipping build.
  2. Primitive — Send an FEColorMatrix with a type/values length mismatch (e.g. SATURATE with empty values) inside a filter; the GPU process reads values out of bounds when applying the effect.
  3. Impact — Out-of-bounds heap read in the GPU process: crash (DoS of the rendering process) or, depending on layout, disclosure of adjacent GPU-process heap into rendered output.
  4. Escalation — As part of a chain starting from a WebContent compromise, an info-leak in the GPU process aids defeating ASLR there. This artifact demonstrates a read/crash, not a write primitive.

Detection & hunting

For defenders and SOC / detection engineers:

  • GPU-process crashes in FEColorMatrix application
  • Malformed FEColorMatrix over IPC
  • IPCTestingAPI usage in production

Audit directions

  • Other filter primitives over IPC
  • Validator coverage vs constructor asserts
  • length-dependent FEColorMatrix code
  • DOM/IPC parity

Before / after

Loading diff…