Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in WebGL
DescriptionInappropriate implementation in WebGL
ComponentWebGL
Bug ClassLogic Error
Tracker521757779
Fix commitd886618e9707 (chromium/src) +16/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
modified

Files Changed

  • third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
From d886618e97076917bb0acfe1316f64f60ad90f30 Mon Sep 17 00:00:00 2001
From: Ken Russell <kbr@chromium.org>
Date: Tue, 09 Jun 2026 23:08:26 -0700
Subject: [PATCH] Add missing PBO checks to a couple of texSubImage2D overloads.

Tested with new WebGL conformance test being added in
https://github.com/KhronosGroup/WebGL/pull/3775 .

Co-authored with jetski-cli.

Fixed: 521757779
Change-Id: I2df1c84c4d76358d68a16e044f62c6b37edb8437
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7916452
Auto-Submit: Kenneth Russell <kbr@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644453}
---

diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
index 2ccc970..51568c0 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
@@ -1776,6 +1776,14 @@
     GLenum type,
     HTMLVideoElement* video,
     ExceptionState& exception_state) {
+  if (isContextLost()) {
+    return;
+  }
+  if (bound_pixel_unpack_buffer_) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "texSubImage2D",
+                      "a buffer is bound to PIXEL_UNPACK_BUFFER");
+    return;
+  }
   WebGLRenderingContextBase::texSubImage2D(script_state, target, level, xoffset,
                                            yoffset, format, type, video,
                                            exception_state);
@@ -1791,6 +1799,14 @@
     GLenum type,
     VideoFrame* frame,
     ExceptionState& exception_state) {
+  if (isContextLost()) {
+    return;
+  }
+  if (bound_pixel_unpack_buffer_) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "texSubImage2D",
+                      "a buffer is bound to PIXEL_UNPACK_BUFFER");
+    return;
+  }
   WebGLRenderingContextBase::texSubImage2D(script_state, target, level, xoffset,
                                            yoffset, format, type, frame,
                                            exception_state);
Loading diff…

Original Bug Report

reported by vm...@google.com

WebGL2: Potential raw renderer heap address leak in texSubImage2D overloads with bound PBO

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: WebGL2RenderingContextBase contains two potential 7-argument texSubImage2D overloads that accept HTMLVideoElement and VideoFrame but omit checking if a Pixel Unpack Buffer (PBO) is bound. If a PBO is bound, a raw renderer-process heap address pointer is passed to the client-side GLES2 implementation, which truncates the pointer and converts it to a PBO byte offset. An attacker can potentially exploit this behavior to leak the lower 32 bits of a raw renderer heap address, bypassing ASLR.

Affected files:

  • third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc

Estimated timestamp from git blame: 2016-03-03

Vulnerability Analysis

Root Cause

In third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc (around lines 1769-1797), there are two 7-argument texSubImage2D overloads designed for WebGL1-compatibility that accept HTMLVideoElement and VideoFrame.

Unlike their sibling DOM-source overloads in the same file (such as those for ImageData at L1716, HTMLImageElement at L1737, CanvasRenderingContextHost at L1759, and ImageBitmap at L1810), these two overloads forward execution to the base class WebGLRenderingContextBase without performing a check on bound_pixel_unpack_buffer_ or calling isContextLost():

void WebGL2RenderingContextBase::texSubImage2D(
    ScriptState* script_state,
    GLenum target,
    GLint level,
    GLint xoffset,
    GLint yoffset,
    GLenum format,
    GLenum type,
    HTMLVideoElement* video,
    ExceptionState& exception_state) {
  WebGLRenderingContextBase::texSubImage2D(script_state, target, level, xoffset,
                                           yoffset, format, type, video,
                                           exception_state);
}

Because the base class WebGLRenderingContextBase implements WebGL1, it has no knowledge of WebGL2 state such as Pixel Unpack Buffers (PBOs) and cannot check bound_pixel_unpack_buffer_ itself.

Technical Flow and Pointer Coercion

When an attacker calls this 7-argument texSubImage2D overload with a bound PBO, the following potential path is executed:

  1. Execution reaches WebGLRenderingContextBase::texSubImage2D (in webgl_rendering_context_base.cc), which dispatches to TexImageHelperHTMLVideoElement or TexImageHelperVideoFrame.
  2. When uploading a software/CPU-backed image or frame, execution routes to TexImageSkImage where the pixel buffer backing store address is extracted as a raw renderer heap pointer (e.g., via pixmap.addr() or image_conversion_data.data()). This raw pointer is stored in gl_data and passed to TexImageBase.
  3. TexImageBase invokes the GLES2 client-side library ContextGL()->TexSubImage2D(..., gl_data).
  4. In GLES2Implementation::TexSubImage2D (gpu/command_buffer/client/gles2_implementation.cc), because bound_pixel_unpack_buffer_ is bound, the function treats the pointer argument as a buffer offset rather than client-side memory. It performs pointer-to-integer conversion via ToGLuint(pixels):
    inline uint32_t ToGLuint(const void* ptr) {
      return static_cast<uint32_t>(reinterpret_cast<size_t>(ptr));
    }
    
    On 64-bit platforms, this truncates the 64-bit raw renderer heap pointer to its lower 32 bits, and passes it to the GPU command helper as a 32-bit unsigned offset.
  5. The service-side GPU interpreter (or ANGLE) reads from the bound PBO at this truncated address offset.

Potential Attack/Exploitation Steps

(Note: These are suggested/potential steps as our tooling agent does not have the capability to run code to confirm)

  1. Obtain a WebGL2 rendering context.
  2. Create a CPU-backed software image source, such as a 1x1 VideoFrame from a standard canvas or a <video> element.
  3. Create and bind a Pixel Unpack Buffer (PBO) filled with a known sequence pattern (e.g., data[i] = i).
  4. With the PBO bound, invoke the 7-argument overload gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video_or_frame).
  5. The pointer to the temporary image data in the renderer heap is truncated to its lower 32 bits, and used as the PBO offset.
  6. Read back the uploaded texture to a framebuffer. The texels uploaded will contain the data from the PBO located at the byte offset corresponding to the lower 32 bits of the heap address, allowing full recovery of the low 32 bits.
  7. Alternatively, if the PBO is smaller than the truncated address, the driver generates GL_INVALID_OPERATION. The attacker can binary-search the exact offset by varying the PBO buffer size.

This provides a highly reliable renderer-process ASLR bypass / information leak oracle.

Suggested Fix

Add the standard isContextLost() and bound_pixel_unpack_buffer_ checks to both of the 7-argument WebGL2 overloads in third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc before forwarding to the base class:

void WebGL2RenderingContextBase::texSubImage2D(
    ScriptState* script_state,
    GLenum target,
    GLint level,
    GLint xoffset,
    GLint yoffset,
    GLenum format,
    GLenum type,
    HTMLVideoElement* video,
    ExceptionState& exception_state) {
  if (isContextLost())
    return;
  if (bound_pixel_unpack_buffer_) {
    SynthesizeGLError(GL_INVALID_OPERATION, "texSubImage2D",
                      "a buffer is bound to PIXEL_UNPACK_BUFFER");
    return;
  }
  WebGLRenderingContextBase::texSubImage2D(script_state, target, level, xoffset,
                                           yoffset, format, type, video,
                                           exception_state);
}

Do the equivalent for the VideoFrame overload at line 1784.

Evaluated with Chrome root at commit: 3947e01999a53d4e2382e39736cb79d79c7dffcf


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker