Medium chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized resource in ANGLE
DescriptionUninitialized resource in ANGLE
ComponentANGLE
Bug ClassUninitialized Memory
Tracker516824665
Fix commit4e0fcf14911c (angle/angle) +113/-58
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • scripts/code_generation_hashes/ANGLE_load_functions_table.json
  • scripts/code_generation_hashes/GL_format_map.json
  • src/libANGLE/es3_format_type_combinations.json
  • src/libANGLE/format_map_autogen.cpp
  • src/libANGLE/renderer/gen_load_functions_table.py
  • src/libANGLE/renderer/load_functions_table_autogen.cpp
From 4e0fcf14911c55b9f304c0cb36c7d6bf76a3a7b2 Mon Sep 17 00:00:00 2001
From: wangra <wangra@google.com>
Date: Fri, 26 Jun 2026 12:41:48 -0400
Subject: [PATCH] Validate sub-image update type against allocated texture format

Enforces type matching in ES3 sub-image updates to prevent validation
bypasses. Also replaces UNIMPLEMENTED() and UNREACHABLE() with FATAL()
in generated backend texture load functions to prevent undefined
behavior or potential memory leaks.

Test: angle_end2end_tests --gtest_filter=
"*Texture2DTestES3.InvalidFormatTypeCombosShouldFail*"
Bug: b/522715859, b/516824665
Change-Id: I20c9283687b22c231b1e59fb8e4482bdf38eb4b7
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8008914
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Ran Wang <wangra@google.com>
---

diff --git a/scripts/code_generation_hashes/ANGLE_load_functions_table.json b/scripts/code_generation_hashes/ANGLE_load_functions_table.json
index c10dbea..304c927 100644
--- a/scripts/code_generation_hashes/ANGLE_load_functions_table.json
+++ b/scripts/code_generation_hashes/ANGLE_load_functions_table.json
@@ -2,9 +2,9 @@
   "src/libANGLE/renderer/angle_format.py":
     "49c55a74e763b1cfd0d96c4b49b4e402",
   "src/libANGLE/renderer/gen_load_functions_table.py":
-    "73e96775a0bd1a424f3f8585b1c5175f",
+    "10e66c76a350a1c70fadd2e10a28981e",
   "src/libANGLE/renderer/load_functions_data.json":
     "cf538e24465732f6ca79cc3b91833451",
   "src/libANGLE/renderer/load_functions_table_autogen.cpp":
-    "f21075147182a86735b7837ecd60d26d"
+    "c10713aabfbb66dae7d9b7ee89454bc4"
 }
diff --git a/scripts/code_generation_hashes/GL_format_map.json b/scripts/code_generation_hashes/GL_format_map.json
index 73563bb..a01ecac 100644
--- a/scripts/code_generation_hashes/GL_format_map.json
+++ b/scripts/code_generation_hashes/GL_format_map.json
@@ -1,8 +1,8 @@
 {
   "src/libANGLE/es3_format_type_combinations.json":
-    "a1b6346f897ae0a78c900a1a7bf095bc",
+    "6db47ff74ca02136aae216441fb8ead3",
   "src/libANGLE/format_map_autogen.cpp":
-    "0af6c5aba2a2392d0cd96ef1b0ea3a00",
+    "832b3fbe708f00006fe35c9c6d5bba84",
   "src/libANGLE/format_map_data.json":
     "4234d7b249edacad5105d4b049339125",
   "src/libANGLE/gen_format_map.py":
diff --git a/src/libANGLE/es3_format_type_combinations.json b/src/libANGLE/es3_format_type_combinations.json
index 6ed0b8e..c61d893 100644
--- a/src/libANGLE/es3_format_type_combinations.json
+++ b/src/libANGLE/es3_format_type_combinations.json
@@ -163,7 +163,7 @@
     ],
     "From GL_ANGLE_depth_texture and OES_depth_texture":
     [
-        [ "GL_DEPTH_COMPONENT32_OES",  "GL_DEPTH_COMPONENT", "GL_UNSIGNED_INT_24_8" ],
+        [ "GL_DEPTH_COMPONENT32_OES",  "GL_DEPTH_COMPONENT", "GL_UNSIGNED_INT" ],
         [ "GL_DEPTH_COMPONENT",        "GL_DEPTH_COMPONENT", "GL_UNSIGNED_SHORT" ],
         [ "GL_DEPTH_COMPONENT",        "GL_DEPTH_COMPONENT", "GL_UNSIGNED_INT" ]
     ],
@@ -192,5 +192,10 @@
         [ "GL_RGBX8_ANGLE", "GL_RGB", "GL_UNSIGNED_BYTE" ],
         [ "GL_RGBX8_SRGB_ANGLEX", "GL_RGB", "GL_UNSIGNED_BYTE" ],
         [ "GL_RGBX8_ANGLE", "GL_RGBA", "GL_UNSIGNED_BYTE" ]
+    ],
+    "From GL_ANGLE_yuv_internal_format":
+    [
+        [ "GL_G8_B8R8_2PLANE_420_UNORM_ANGLE", "GL_G8_B8R8_2PLANE_420_UNORM_ANGLE", "GL_UNSIGNED_BYTE" ],
+        [ "GL_G8_B8_R8_3PLANE_420_UNORM_ANGLE", "GL_G8_B8_R8_3PLANE_420_UNORM_ANGLE", "GL_UNSIGNED_BYTE" ]
     ]
 }
diff --git a/src/libANGLE/format_map_autogen.cpp b/src/libANGLE/format_map_autogen.cpp
index 60bdec6..8ff3dd7 100644
--- a/src/libANGLE/format_map_autogen.cpp
+++ b/src/libANGLE/format_map_autogen.cpp
@@ -586,6 +586,8 @@
         case GL_BGRA_EXT:
         case GL_DEPTH_COMPONENT:
         case GL_DEPTH_STENCIL:
+        case GL_G8_B8R8_2PLANE_420_UNORM_ANGLE:
+        case GL_G8_B8_R8_3PLANE_420_UNORM_ANGLE:
         case GL_LUMINANCE:
         case GL_LUMINANCE_ALPHA:
         case GL_RED:
@@ -762,18 +764,8 @@
                     {
                         case GL_DEPTH_COMPONENT24:
                         case GL_DEPTH_COMPONENT16:
-                        case GL_DEPTH_COMPONENT:
-                            return true;
-                        default:
-                            break;
-                    }
-                    break;
-                }
-                case GL_UNSIGNED_INT_24_8:
-                {
-                    switch (internalFormat)
-                    {
                         case GL_DEPTH_COMPONENT32_OES:
+                        case GL_DEPTH_COMPONENT:
                             return true;
                         default:
                             break;
@@ -828,6 +820,44 @@
             }
             break;
 
+        case GL_G8_B8R8_2PLANE_420_UNORM_ANGLE:
+            switch (type)
+            {
+                case GL_UNSIGNED_BYTE:
+                {
+                    switch (internalFormat)
+                    {
+                        case GL_G8_B8R8_2PLANE_420_UNORM_ANGLE:
+                            return true;
+                        default:
+                            break;
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+            break;
+
+        case GL_G8_B8_R8_3PLANE_420_UNORM_ANGLE:
+            switch (type)
+            {
+                case GL_UNSIGNED_BYTE:
+                {
+                    switch (internalFormat)
+                    {
+                        case GL_G8_B8_R8_3PLANE_420_UNORM_ANGLE:
+                            return true;
+                        default:
+                            break;
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+            break;
+
         case GL_LUMINANCE:
             switch (type)
             {
diff --git a/src/libANGLE/renderer/gen_load_functions_table.py b/src/libANGLE/renderer/gen_load_functions_table.py
index f9a2bed..1ec23cf 100755
--- a/src/libANGLE/renderer/gen_load_functions_table.py
+++ b/src/libANGLE/renderer/gen_load_functions_table.py
@@ -26,6 +26,7 @@
 //   Contains the GetLoadFunctionsMap for texture_format_util.h
 //
 
+#include "common/debug.h"
 #include "libANGLE/renderer/load_functions_table.h"
 
 #include "image_util/copyimage.h"
@@ -58,7 +59,7 @@
                                size_t outputRowPitch,
                                size_t outputDepthPitch)
 {{
-    UNIMPLEMENTED();
+    FATAL() << "UNIMPLEMENTED load function.";
 }}
 
 void UnreachableLoadFunction(const ImageLoadContext &context,
@@ -72,7 +73,7 @@
                              size_t outputRowPitch,
                              size_t outputDepthPitch)
 {{
-    UNREACHABLE();
+    FATAL() << "UNREACHABLE load function.";
 }}
 
 {load_functions_data}}}  // namespace
diff --git a/src/libANGLE/renderer/load_functions_table_autogen.cpp b/src/libANGLE/renderer/load_functions_table_autogen.cpp
index 345756f..32d21e6 100644
--- a/src/libANGLE/renderer/load_functions_table_autogen.cpp
+++ b/src/libANGLE/renderer/load_functions_table_autogen.cpp
@@ -9,6 +9,7 @@
 //   Contains the GetLoadFunctionsMap for texture_format_util.h
 //
 
+#include "common/debug.h"
 #include "libANGLE/renderer/load_functions_table.h"
 
 #include "image_util/copyimage.h"
@@ -41,7 +42,7 @@
                                size_t outputRowPitch,
                                size_t outputDepthPitch)
 {
-    UNIMPLEMENTED();
Loading diff…

Original Bug Report

reported by vm...@google.com

ANGLE ES2 glTexSubImage2D type validation bypass leading to GPU memory disclosure

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: A potential validation gap in ANGLE’s ES2 sub-image path allows non-WebGL contexts to bypass type-to-internalformat consistency checks. A mismatched type parameter causes the Vulkan backend to fallback to a no-op conversion function, leaving dynamic staging memory uninitialized. This recycled memory, which can contain cross-origin GPU data, is then uploaded to the texture and can potentially be read back by an attacker.

Affected files:

  • third_party/angle/src/libANGLE/validationES2.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/TextureVk.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp
  • third_party/angle/src/libANGLE/Texture.cpp
  • third_party/angle/src/libANGLE/renderer/load_functions_table_autogen.cpp

Estimated timestamp from git blame: 2024-11-25

Description

A potential security vulnerability has been identified in ANGLE’s GLES2 validation layer, which may allow a compromised renderer to bypass texture type validation and disclose recycled GPU memory containing cross-origin data.

Root Cause Analysis

In third_party/angle/src/libANGLE/validationES2.cpp, within ValidateES2TexImageParametersBase, the validation path for glTexSubImage2D (isSubImage == true) checks format compatibility but fails to enforce type compatibility for non-WebGL contexts:

        bool formatsMatch = format == textureInternalFormat.format;
        ...
        if (!formatsMatch)
        {
            ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kTextureFormatMismatch);
            return false;
        }

        if (context->isWebGL())
        {
            ...
            isValid = (GetInternalFormatInfo(format, type).sizedInternalFormat ==
                       textureSizedInternalFormat);
            ...
        }

For non-WebGL ES2 contexts (e.g., created with CONTEXT_TYPE_OPENGLES2), context->isWebGL() evaluates to false. This bypasses the strict consistency check between the caller-provided type and the actual texture’s internal format.

When this occurs, the Vulkan backend (in TextureVk::setSubImageImpl) attempts to load the update. Because the format and type are mismatched, vkFormat.getTextureLoadFunction(formatSupport, type) maps to UnreachableLoadFunction via the autogenerated load function table (load_functions_table_autogen.cpp).

In official release builds, UNREACHABLE() expands to a no-op because ANGLE_ENABLE_ASSERTS is disabled. Consequently, UnreachableLoadFunction returns immediately without writing anything to the mapped staging buffer memory.

ANGLE allocates staging buffers from a shared vk::BufferPool without zero-initialization. Since the load function does not write to the buffer, the stale, recycled memory contents remain completely unmodified and are directly copied into the destination texture on the GPU.

Potential Steps to Trigger

Note: These are potential steps based on static analysis, as our tooling cannot execute code.

  1. From a compromised renderer, establish a non-WebGL ES2 context via the Command Buffer Mojo interface.
  2. Allocate a texture with a standard format (e.g., GL_RGBA and GL_UNSIGNED_BYTE).
  3. Call glTexSubImage2D with a mismatched type (e.g., GL_UNSIGNED_SHORT_4_4_4_4). Validation succeeds due to the WebGL-only guard check.
  4. The backend utilizes the uninitialized staging buffer, copying recycled GPU memory into the texture.
  5. Bind the texture to a Framebuffer and read back the contents via glReadPixels or shader sampling to retrieve stale GPU memory.

Suggested Remediation

We suggest enforcing format and type validation consistency checks for all contexts (not just WebGL contexts) inside ValidateES2TexImageParametersBase when handling sub-image updates, or ensuring that the load function table safely handles or rejects invalid type configurations in non-assert builds instead of executing a no-op UnreachableLoadFunction.

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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