Medium chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in ANGLE
DescriptionUninitialized Use in ANGLE
ComponentANGLE
Bug ClassUninitialized Memory
Tracker500501226
Fix commitcc89b6a44821 (angle/angle) +28/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • src/libANGLE/validationES2.cpp
  • src/tests/gl_tests/TextureUploadFormatTest.cpp
From cc89b6a44821850faf8d880c9e8e088c2cefb68e Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <[email protected]>
Date: Thu, 09 Apr 2026 16:07:02 -0400
Subject: [PATCH] Fix format check for a few glTexImage2D combinations

Bug: chromium:500501226
Change-Id: Ieebc02a6f93914af4b38003ac4c01b17ac3b471d
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7745487
Reviewed-by: Amirali Abdolrashidi <[email protected]>
Commit-Queue: Amirali Abdolrashidi <[email protected]>
---

diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index 43f58f7..840c35b 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -959,7 +959,6 @@
                                        GLsizei imageSize,
                                        const void *pixels)
 {
-
     TextureType texType = TextureTargetToType(target);
     if (!ValidImageSizeParameters(context, entryPoint, texType, level, width, height, 1,
                                   isSubImage))
@@ -1488,7 +1487,7 @@
                     }
                     if (context->getExtensions().requiredInternalformatOES &&
                         context->getExtensions().textureType2101010REVEXT &&
-                        GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)
+                        type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)
                     {
                         nonEqualFormatsAllowed = true;
                     }
@@ -1502,7 +1501,7 @@
                     }
                     if (context->getExtensions().requiredInternalformatOES &&
                         context->getExtensions().textureType2101010REVEXT &&
-                        GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)
+                        type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)
                     {
                         nonEqualFormatsAllowed = true;
                     }
diff --git a/src/tests/gl_tests/TextureUploadFormatTest.cpp b/src/tests/gl_tests/TextureUploadFormatTest.cpp
index 40131bf..9d63a75 100644
--- a/src/tests/gl_tests/TextureUploadFormatTest.cpp
+++ b/src/tests/gl_tests/TextureUploadFormatTest.cpp
@@ -683,6 +683,32 @@
     TestAll(UploadSource::PBO);
 }
 
+// Test invalid upload format combinations in ES2
+TEST_P(TextureUploadFormatTest, InvalidTypeAndFormat)
+{
+    constexpr std::array<uint32_t, 16> kData{};
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D, texture);
+
+    if (IsGLExtensionEnabled("GL_OES_rgb8_rgba8") && IsGLExtensionEnabled("GL_OES_texture_float"))
+    {
+        // Regression test for when the format check for GL_UNSIGNED_INT_2_10_10_10_REV_EXT
+        // accidentally allowed all formats.
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_FLOAT, kData.data());
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    }
+
+    if (IsGLExtensionEnabled("GL_OES_required_internalformat") &&
+        IsGLExtensionEnabled("GL_OES_texture_float"))
+    {
+        // Regression test for when the format check for GL_UNSIGNED_INT_2_10_10_10_REV_EXT
+        // accidentally allowed all formats.
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565_OES, 1, 1, 0, GL_RGB, GL_FLOAT, kData.data());
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    }
+}
+
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(TextureUploadFormatTest);
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureUploadFormatTest_ES3);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/TextureUploadFormatTest.cpp b/src/tests/gl_tests/TextureUploadFormatTest.cpp
index 40131bf..9d63a75 100644
--- a/src/tests/gl_tests/TextureUploadFormatTest.cpp
+++ b/src/tests/gl_tests/TextureUploadFormatTest.cpp
@@ -683,6 +683,32 @@
     TestAll(UploadSource::PBO);
 }
 
+// Test invalid upload format combinations in ES2
+TEST_P(TextureUploadFormatTest, InvalidTypeAndFormat)
+{
+    constexpr std::array<uint32_t, 16> kData{};
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D, texture);
+
+    if (IsGLExtensionEnabled("GL_OES_rgb8_rgba8") && IsGLExtensionEnabled("GL_OES_texture_float"))
+    {
+        // Regression test for when the format check for GL_UNSIGNED_INT_2_10_10_10_REV_EXT
+        // accidentally allowed all formats.
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_FLOAT, kData.data());
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    }
+
+    if (IsGLExtensionEnabled("GL_OES_required_internalformat") &&
+        IsGLExtensionEnabled("GL_OES_texture_float"))
+    {
+        // Regression test for when the format check for GL_UNSIGNED_INT_2_10_10_10_REV_EXT
+        // accidentally allowed all formats.
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565_OES, 1, 1, 0, GL_RGB, GL_FLOAT, kData.data());
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    }
+}
+
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(TextureUploadFormatTest);
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureUploadFormatTest_ES3);
Loading diff…

Original Bug Report

reported by [email protected]

GPU memory leak via ANGLE ES2 texture validation typo

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 without the security team.

Overview: A logic error in ANGLE’s OpenGL ES 2.0 validation allows invalid texture internal format and type combinations to be accepted. This bypasses validation and causes backend rendering engines to use a no-op fallback load function, resulting in uninitialized staging memory containing stale GPU process data being uploaded to the texture. This can lead to a cross-process information disclosure.

Affected files:

  • third_party/angle/src/libANGLE/validationES2.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp
  • third_party/angle/src/libANGLE/renderer/metal/TextureMtl.mm
  • third_party/angle/src/libANGLE/renderer/d3d/d3d11/Image11.cpp

Estimated timestamp from git blame: 2024-12-03

Description

A typo in ANGLE’s glTexImage2D validation logic allows invalid combinations of texture internal formats and types to bypass parameter checking, potentially leading to a cross-process information leak of uninitialized GPU memory.

In third_party/angle/src/libANGLE/validationES2.cpp, within ValidateES2TexImageParametersBase, there is a missing type == comparison when validating the GL_RGB565_OES and GL_RGB8_OES internal formats if the GL_EXT_texture_type_2_10_10_10_REV extension is present.

Specifically, at lines 1491 and 1505:

1491:                         GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)
... 
1505:                         GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)

Because GL_UNSIGNED_INT_2_10_10_10_REV_EXT is used as a bare expression, the compiler evaluates its constant value (0x8368). Since this is non-zero, the condition evaluates to true as long as format == GL_RGB. This erroneously sets nonEqualFormatsAllowed = true, bypassing the strict OpenGL ES 2.0 requirement that internalformat must match format (line 1753), and allowing invalid types like GL_FLOAT to be accepted.

Chrome’s WebGL passthrough command decoder delegates all texture parameter validation to ANGLE via robust entry points, so this invalid state propagates to the ANGLE backends (Vulkan, Metal, D3D11).

When the backend attempts to load the image data, it queries for a conversion function based on the invalid combination (e.g., GL_RGB565 + GL_FLOAT). Because this combination is invalid, the auto-generated load function tables fall back to the default switch case, which returns UnreachableLoadFunction.

In Chromium Release builds, UnreachableLoadFunction evaluates to a complete no-op. Consequently, the staging buffers allocated to hold the incoming pixel data prior to GPU transfer are never written to. These staging buffers are often suballocated from shared pools (e.g., via VMA in Vulkan) and contain stale, uninitialized data from previous GPU process operations.

Because the attacker provided a non-null pixels array, ANGLE marks the texture state as InitState::Initialized (third_party/angle/src/libANGLE/Texture.cpp:49), which bypasses ANGLE’s robust resource initialization that would otherwise zero-clear the texture. The uninitialized staging buffer is then copied to the GPU texture, allowing a compromised renderer to read back the leaked GPU memory.

Potential Reproduction Steps

These are suggested steps an attacker would follow to trigger the vulnerability from a compromised renderer process or malicious JavaScript:

  1. Create a WebGL 1.0 (OpenGL ES 2.0) context.
  2. Request and enable the required extensions: GL_OES_required_internalformat, GL_EXT_texture_type_2_10_10_10_REV, and GL_OES_texture_float.
  3. Call gl.texImage2D with:
    • target: GL_TEXTURE_2D
    • internalformat: GL_RGB565_OES (or GL_RGB8_OES)
    • format: GL_RGB
    • type: GL_FLOAT
    • pixels: A non-null Float32Array of appropriate size.
  4. Due to the typo, ANGLE validation passes. The backend uses the no-op UnreachableLoadFunction, leaving staging memory uninitialized.
  5. Bind the resulting texture to a Framebuffer Object (FBO).
  6. Call gl.readPixels() to exfiltrate the uninitialized GPU-process memory back to the attacker.

Suggested Fix

Correct the logic in third_party/angle/src/libANGLE/validationES2.cpp for the GL_RGB565_OES and GL_RGB8_OES cases by adding the missing type == comparison:

// At lines 1491 and 1505:
- GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)
+ type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGB)

Evaluated with Chrome root at commit: 137d451a126685dd5010e6609db9f6d4a78d8234


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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