[2] Untrusted texture-upload offset reinterpreted as a client pointer
A number crossed the IPC boundary and came back out as a pointer
High. The GPU process forms a raw pointer out of a 64-bit value the WebContent process chose and reads an attacker-chosen number of bytes into a sampleable texture. The precondition is code execution already held in WebContent — the renderer-side WebGL2RenderingContext rejects these offset overloads when no unpack buffer is bound, so the call must be emitted as raw IPC.
WebGL commands issued by a web page do not run in the page's own process: they are serialized over IPC to the GPU process, replayed against a driver-facing implementation there, and only the results travel back. That replay happens in GraphicsContextGLANGLE, WebKit's wrapper over ANGLE — the translation layer that turns GL ES calls into Metal on Cocoa. In OpenGL ES 3, the pixel argument of a texture upload has two meanings decided purely by binding state: with a buffer bound to GL_PIXEL_UNPACK_BUFFER it is an offset into that buffer, and with nothing bound it is a client-memory address the driver dereferences.
The angle: a compromised WebContent process can name any address in the GPU process and have the driver copy a chosen number of bytes from it into a texture, turning GPU-process heap memory into pixels it can read back.
Patch Details
The change covers the offset-based tex(Sub)Image and compressedTex(Sub)Image overloads in GraphicsContextGLANGLE. Each of these takes a GCGLintptr offset that arrives from WebContent over IPC and previously passed it straight to ANGLE as the pixels / data argument via reinterpret_cast<GLvoid*>(offset). The patch enforces the binding-state precondition in WebKit's own code before the cast, so the offset is only ever formed into a pointer when GL state guarantees it is buffer-relative. A regression test is added that issues compressedTexImage2D(TEXTURE_2D, 0, COMPRESSED_RGB8_ETC2, 4, 4, 0, 8, 0x41414140) with no pixel unpack buffer bound.
An untrusted integer reinterpreted as a raw pointer, with the binding-state precondition that made the reinterpretation safe enforced only inside a vendored dependency.
WebContent process | GPU process
--------------------------|-------------------------------------------
compressedTexImage2D( | RemoteGraphicsContextGL decodes the message
..., imageSize = 8, | |
offset = 0x41414140) | v
| GraphicsContextGLANGLE
| Before: reinterpret_cast<GLvoid*>(offset)
| no PBO bound -> ANGLE treats the
| value as a client pointer -> Metal
| reads imageSize bytes from it
| After: pointer formed only when a pixel
| unpack buffer is bound
Background
Where this lives. In the GPU-process configuration, WebGL calls are decoded by RemoteGraphicsContextGL (Source/WebKit/GPUProcess/graphics/) from an IPC::StreamServerConnection fed by the WebContent process, and replayed against GraphicsContextGLANGLE inside the GPU process. ANGLE then lowers those calls to Metal on Cocoa platforms.
Pixel unpack buffers. OpenGL ES 3 added buffer objects as a source for texture uploads. Binding a buffer to GL_PIXEL_UNPACK_BUFFER changes the meaning of the upload's data argument from "pointer to client memory" to "byte offset into the bound buffer". The API reuses one parameter slot for both meanings, so the type of that value is a function of GL state at call time, not of the call itself.
Validation ownership. ANGLE ships its own validation layer, and WebKit historically leaned on it for GL-level preconditions rather than duplicating the checks in the wrapper. That arrangement makes the vendored dependency's validation coverage part of WebKit's security boundary.
Analysis
The bug is a missing precondition check on untrusted input, not a lifetime bug. The invariant "a pixel unpack buffer is bound, therefore this integer is buffer-relative" was never enforced on the WebKit side — it was inherited from ANGLE. The commit message states that an upstream ANGLE roll (chromium/angle 849f7128) removed that validation for compressed texture uploads; with the check gone, nothing in the GPU process examined the binding state before the cast.
The old code path was makeContextCurrent() followed by GL_CompressedTexImage2D(..., imageSize, reinterpret_cast<GLvoid*>(offset)). With no buffer bound, ANGLE treats the value as a client pointer and, per the commit message, its Metal backend reads imageSize bytes from that address in the GPU process address space into the texture. Reachability sits at the IPC boundary rather than in ordinary script: the WebContent-side WebGL2RenderingContext tracks m_boundPixelUnpackBuffer and is expected to reject the offset overloads when it is null, so realising this requires an attacker who already holds WebContent code execution and can emit the message directly.
The attacker controls both halves of the read: the address, through offset, and the length, through imageSize or the widthxheightxbpp product for the uncompressed overloads. The added test encodes exactly that shape with a recognisable sentinel address. Because the destination is a texture, the copied bytes are subsequently sampleable and reachable through the normal readback path, which is what converts an out-of-bounds read into an information disclosure rather than a crash.
This vulnerability weakens the WebContent-to-GPU process boundary at its widest point: a value that crosses the boundary as an integer is used as a pointer on the privileged side. The fix restores the invariant in WebKit's own code rather than depending on the vendored dependency to keep the check.
Audit directions
- Dual-interpretation GL parameters. Any GL entry point whose argument type is decided by binding state is a candidate for the same confusion, and the danger is that the unsafe interpretation is the default one (nothing bound) rather than an exotic configuration. Sweep the remaining offset-taking overloads in
GraphicsContextGLANGLE—readPixels,drawElements, the buffer-mapping paths — for casts of an IPC-supplied integer into a pointer, and confirm each has a WebKit-side binding-state check rather than a comment about ANGLE validating it. - Checks owned by a vendored dependency. When a security precondition lives only in third-party code, a routine dependency roll can silently delete it. The forward-facing question is which other WebKit-side wrappers over ANGLE, libwebrtc, or the codec libraries rely on the dependency's validation layer for an invariant that WebKit's threat model actually needs. In code review, a
reinterpret_caston a parameter that came off an IPC decode is the tell — it should never appear without an adjacent state check in the same function.