Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in ANGLE
DescriptionHeap buffer overflow in ANGLE
ComponentANGLE
Bug ClassOOB
Tracker500540748
Fix commit86fc5f2794c6 (angle/angle) +222/-65
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Files Changed

  • src/common/angleutils.h
  • src/libANGLE/formatutils.cpp
  • src/libANGLE/formatutils.h
  • src/libANGLE/validationES.cpp
  • src/libANGLE/validationES2.cpp
  • src/libANGLE/validationES3.cpp
From 86fc5f2794c692a051527e77f99f1f81e9f9eb6b Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <[email protected]>
Date: Tue, 21 Apr 2026 17:07:56 -0400
Subject: [PATCH] Reland "Disallow ANGLEX formats from being used"

This is a reland of commit e46357c5ce11a9ea4fd9451556d2f7f7b805cc06

Original change's description:
> Disallow ANGLEX formats from being used
>
> Bug: chromium:500540748
> Change-Id: I1084d36b6f2fa02ac7a6573137df73d873bb7f43
> Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7783631
> Reviewed-by: Geoff Lang <[email protected]>
> Commit-Queue: Shahbaz Youssefi <[email protected]>

Bug: chromium:500540748
Change-Id: Id71982918caa0bb1eef4bd1e031dc7791146b13c
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7792965
Auto-Submit: Shahbaz Youssefi <[email protected]>
Commit-Queue: Shahbaz Youssefi <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
---

diff --git a/src/common/angleutils.h b/src/common/angleutils.h
index 7128843..5f8e993 100644
--- a/src/common/angleutils.h
+++ b/src/common/angleutils.h
@@ -337,6 +337,8 @@
 #    define snprintf _snprintf
 #endif
 
+// Note: when adding internal formats, update IsAngleInternalFormat() so they aren't accidentally
+// accessible by the application.
 #define GL_A1RGB5_ANGLEX 0x6AC5
 #define GL_BGRX8_ANGLEX 0x6ABA
 #define GL_BGR565_ANGLEX 0x6ABB
diff --git a/src/libANGLE/formatutils.cpp b/src/libANGLE/formatutils.cpp
index 8981af5..507c369 100644
--- a/src/libANGLE/formatutils.cpp
+++ b/src/libANGLE/formatutils.cpp
@@ -1688,6 +1688,84 @@
     return typeIter->second;
 }
 
+// Used by validation to determine if an internal format is internal to ANGLE and should not be
+// allowed for app use.
+bool IsAngleInternalFormat(GLenum internalFormat)
+{
+    switch (internalFormat)
+    {
+        case GL_A1RGB5_ANGLEX:
+        case GL_BGRX8_ANGLEX:
+        case GL_BGR565_ANGLEX:
+        case GL_BGRA4_ANGLEX:
+        case GL_BGR5_A1_ANGLEX:
+        case GL_INT_64_ANGLEX:
+        case GL_UINT_64_ANGLEX:
+        case GL_BGRA8_SRGB_ANGLEX:
+        case GL_BGR10_A2_ANGLEX:
+        case GL_BGRX8_SRGB_ANGLEX:
+        case GL_RGBX8_SRGB_ANGLEX:
+        case GL_R10X6G10X6B10X6A10X6_UNORM_ANGLEX:
+        case GL_RGBA8_TYPELESS_ANGLEX:
+        case GL_RGBA8_TYPELESS_SRGB_ANGLEX:
+        case GL_BGRA8_TYPELESS_ANGLEX:
+        case GL_BGRA8_TYPELESS_SRGB_ANGLEX:
+        case GL_R8_SSCALED_ANGLEX:
+        case GL_RG8_SSCALED_ANGLEX:
+        case GL_RGB8_SSCALED_ANGLEX:
+        case GL_RGBA8_SSCALED_ANGLEX:
+        case GL_R8_USCALED_ANGLEX:
+        case GL_RG8_USCALED_ANGLEX:
+        case GL_RGB8_USCALED_ANGLEX:
+        case GL_RGBA8_USCALED_ANGLEX:
+        case GL_R16_SSCALED_ANGLEX:
+        case GL_RG16_SSCALED_ANGLEX:
+        case GL_RGB16_SSCALED_ANGLEX:
+        case GL_RGBA16_SSCALED_ANGLEX:
+        case GL_R16_USCALED_ANGLEX:
+        case GL_RG16_USCALED_ANGLEX:
+        case GL_RGB16_USCALED_ANGLEX:
+        case GL_RGBA16_USCALED_ANGLEX:
+        case GL_R32_SSCALED_ANGLEX:
+        case GL_RG32_SSCALED_ANGLEX:
+        case GL_RGB32_SSCALED_ANGLEX:
+        case GL_RGBA32_SSCALED_ANGLEX:
+        case GL_R32_USCALED_ANGLEX:
+        case GL_RG32_USCALED_ANGLEX:
+        case GL_RGB32_USCALED_ANGLEX:
+        case GL_RGBA32_USCALED_ANGLEX:
+        case GL_R32_SNORM_ANGLEX:
+        case GL_RG32_SNORM_ANGLEX:
+        case GL_RGB32_SNORM_ANGLEX:
+        case GL_RGBA32_SNORM_ANGLEX:
+        case GL_R32_UNORM_ANGLEX:
+        case GL_RG32_UNORM_ANGLEX:
+        case GL_RGB32_UNORM_ANGLEX:
+        case GL_RGBA32_UNORM_ANGLEX:
+        case GL_R32_FIXED_ANGLEX:
+        case GL_RG32_FIXED_ANGLEX:
+        case GL_RGB32_FIXED_ANGLEX:
+        case GL_RGBA32_FIXED_ANGLEX:
+        case GL_RGB10_A2_SINT_ANGLEX:
+        case GL_RGB10_A2_SNORM_ANGLEX:
+        case GL_RGB10_A2_SSCALED_ANGLEX:
+        case GL_RGB10_A2_USCALED_ANGLEX:
+        case GL_A2_RGB10_UNORM_ANGLEX:
+        case GL_A2_RGB10_SNORM_ANGLEX:
+        case GL_A2_RGB10_USCALED_ANGLEX:
+        case GL_A2_RGB10_SSCALED_ANGLEX:
+        case GL_X2_RGB10_UINT_ANGLEX:
+        case GL_X2_RGB10_SINT_ANGLEX:
+        case GL_X2_RGB10_USCALED_ANGLEX:
+        case GL_X2_RGB10_SSCALED_ANGLEX:
+        case GL_X2_RGB10_UNORM_ANGLEX:
+        case GL_X2_RGB10_SNORM_ANGLEX:
+            return true;
+        default:
+            return false;
+    }
+}
+
 GLuint InternalFormat::computePixelBytes(GLenum formatType) const
 {
     const auto &typeInfo = GetTypeInfo(formatType);
diff --git a/src/libANGLE/formatutils.h b/src/libANGLE/formatutils.h
index 7137b9c..dcf5061 100644
--- a/src/libANGLE/formatutils.h
+++ b/src/libANGLE/formatutils.h
@@ -331,6 +331,7 @@
 
 const InternalFormat &GetSizedInternalFormatInfo(GLenum internalFormat);
 const InternalFormat &GetInternalFormatInfo(GLenum internalFormat, GLenum type);
+bool IsAngleInternalFormat(GLenum internalFormat);
 
 // ES2 requires that format is equal to internal format at all glTex*Image2D entry points and the
 // implementation can decide the true, sized, internal format. The ES2FormatMap determines the
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 89e363a..67f817a 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -1706,7 +1706,7 @@
     // sized but it does state that the format must be in the ES2.0 spec table 4.5 which contains
     // only sized internal formats.
     const InternalFormat &formatInfo = GetSizedInternalFormatInfo(convertedInternalFormat);
-    if (formatInfo.internalFormat == GL_NONE)
+    if (formatInfo.internalFormat == GL_NONE || IsAngleInternalFormat(internalformat))
     {
         ANGLE_VALIDATION_ERROR(GL_INVALID_ENUM, kInvalidRenderbufferInternalFormat);
         return false;
@@ -8168,7 +8168,7 @@
     // The ES3.1 spec(section 8.8) states that an INVALID_ENUM error is generated if internalformat
     // is one of the unsized base internalformats listed in table 8.11.
     const InternalFormat &formatInfo = GetSizedInternalFormatInfo(internalFormat);
-    if (formatInfo.internalFormat == GL_NONE)
+    if (formatInfo.internalFormat == GL_NONE || IsAngleInternalFormat(internalFormat))
     {
         ANGLE_VALIDATION_ERROR(GL_INVALID_ENUM, kUnsizedInternalFormatUnsupported);
         return false;
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index c751e08..2537b3a 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -1812,7 +1812,8 @@
     }
 
     const InternalFormat &formatInfo = GetSizedInternalFormatInfo(internalformat);
-    if (formatInfo.format == GL_NONE || formatInfo.type == GL_NONE)
+    if (formatInfo.format == GL_NONE || formatInfo.type == GL_NONE ||
+        IsAngleInternalFormat(internalformat))
     {
         ANGLE_VALIDATION_ERROR(GL_INVALID_ENUM, kInvalidFormat);
         return false;
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
index dfb060e..c331225 100644
--- a/src/libANGLE/validationES3.cpp
+++ b/src/libANGLE/validationES3.cpp
@@ -574,6 +574,12 @@
     }
 
     // Validate texture formats
+    if (IsAngleInternalFormat(internalformat))
+    {
+        ANGLE_VALIDATION_ERRORF(GL_INVALID_ENUM, kInvalidInternalFormat, internalformat);
+        return false;
+    }
+
     GLenum actualInternalFormat =
         isSubImage ? texture->getFormat(target, level).info->internalFormat : internalformat;
     if (isSubImage && actualInternalFormat == GL_NONE)
@@ -1543,6 +1549,13 @@
         return false;
     }
 
+    // Forbid use of ANGLE internal formats
+    if (IsAngleInternalFormat(internalformat))
+    {
+        ANGLE_VALIDATION_ERRORF(GL_INVALID_ENUM, kInvalidInternalFormat, internalformat);
+        return false;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/ImageTest.cpp b/src/tests/gl_tests/ImageTest.cpp
index 960ab5f..f364266 100644
--- a/src/tests/gl_tests/ImageTest.cpp
+++ b/src/tests/gl_tests/ImageTest.cpp
@@ -5782,6 +5782,38 @@
     destroyAndroidHardwareBuffer(ahb);
 }
 
+// Test that RGBX data are preserved when importing from AHB created with sRGB color space.  Using
+// immutable textures.
+TEST_P(ImageTestES3, RGBXAHBImportPreservesData_Colorspace_TexStorage)
+{
+    EGLWindow *window = getEGLWindow();
+
+    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt() || !has2DTextureExt());
+    ANGLE_SKIP_TEST_IF(!hasEglImageStorageExt());
+    ANGLE_SKIP_TEST_IF(!hasAndroidImageNativeBufferExt() || !hasAndroidHardwareBufferSupport());
+    ANGLE_SKIP_TEST_IF(!hasAhbLockPlanesSupport());
+
+    const GLubyte kRed50SRGB[]   = {188, 0, 0, 255};
+    const GLubyte kRed50Linear[] = {128, 0, 0, 255};
+
+    // Create the Image
+    AHardwareBuffer *ahb;
+    EGLImageKHR ahbImage;
+    createEGLImageAndroidHardwareBufferSource(1, 1, 1, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
+                                              kDefaultAHBUsage, kColorspaceAttribs,
+                                              {{kRed50SRGB, 4}}, &ahb, &ahbImage);
+
+    GLTexture ahbTexture;
+    createEGLImageTargetTextureStorage(ahbImage, GL_TEXTURE_2D, ahbTexture, nullptr);
+
+    verifyResults2D(ahbTexture, kRed50Linear);
+    verifyResultAHB(ahb, {{kRed50SRGB, 4}});
+
+    // Clean up
+    eglDestroyImageKHR(window->getDisplay(), ahbImage);
+    destroyAndroidHardwareBuffer(ahb);
+}
+
 // Tests that RGBX can be successfully loaded with 3-channel data and read back as 4-channel data.
 TEST_P(ImageTestES3, RGBXAHBUploadDownload)
 {
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index c316c5e..5484dbb 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -494,7 +494,6 @@
     void testCopyImage(const APIExtensionVersion usedExtension);
     void testCopyImageDepthStencil(const APIExtensionVersion usedExtension);
 
-    void InternalFormatNotEnabledHelper(GLenum internalFormat, GLenum uploadFormat);
     void TextureUploadPBOHelper(GLenum internalFormat, GLenum uploadFormat);
 };
 
@@ -7258,20 +7257,6 @@
     ASSERT_GL_NO_ERROR();
 }
 
-void Texture2DTestES3::InternalFormatNotEnabledHelper(GLenum internalFormat, GLenum uploadFormat)
-{
-    GLTexture texture;
-    glBindTexture(GL_TEXTURE_2D, texture);
-    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, uploadFormat, GL_UNSIGNED_BYTE,
-                 nullptr);
-    GLenum error = glGetError();
-    EXPECT_NE(static_cast<GLenum>(GL_NONE), error) << "internalFormat: " << internalFormat;
-
-    glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, 1, 1);
-    error = glGetError();
-    EXPECT_NE(static_cast<GLenum>(GL_NONE), error) << "internalFormat: " << internalFormat;
-}
-
 void Texture2DTestES3::TextureUploadPBOHelper(GLenum internalFormat, GLenum uploadFormat)
 {
     constexpr GLint kWidth  = 16;
@@ -7321,7 +7306,6 @@
             switch (internalFormat)
             {
                 case GL_RGBX8_ANGLE:
-                case GL_RGBX8_SRGB_ANGLEX:
                     EXPECT_EQ(readback[pixelIndex].R, pboData[pboIndex + 0])
                         << "at " << x << ", " << y;
                     EXPECT_EQ(readback[pixelIndex].G, pboData[pboIndex + 1])
@@ -7330,16 +7314,6 @@
                         << "at " << x << ", " << y;
                     break;
 
-                case GL_BGRX8_ANGLEX:
-                case GL_BGRX8_SRGB_ANGLEX:
-                    // Upload is GL_BGR_EXT so pboData has [B, G, R]
-                    EXPECT_EQ(readback[pixelIndex].R, pboData[pboIndex + 2])
-                        << "at " << x << ", " << y;
-                    EXPECT_EQ(readback[pixelIndex].G, pboData[pboIndex + 1])
-                        << "at " << x << ", " << y;
-                    EXPECT_EQ(readback[pixelIndex].B, pboData[pboIndex + 0])
-                        << "at " << x << ", " << y;
-                    break;
                 default:
                     UNREACHABLE();
             }
@@ -8994,38 +8968,103 @@
     // Note: This is the opposite of the usual test for extension. We only run the test
     // if the extension is NOT available.
     ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_ANGLE_rgbx_internal_format"));
-    InternalFormatNotEnabledHelper(GL_RGBX8_ANGLE, GL_RGB);
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBX8_ANGLE, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
+    GLenum error = glGetError();
+    EXPECT_NE(static_cast<GLenum>(GL_NONE), error);
+
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBX8_ANGLE, 1, 1);
+    error = glGetError();
+    EXPECT_NE(static_cast<GLenum>(GL_NONE), error);
 }
 
-// Test that GL_RGBX8_SRGB_ANGLEX results in GL_INVALID_ENUM if the extensions are not enabled.
-TEST_P(Texture2DTestES3, InternalFormatNotEnabled_RGBX8_SRGB_ANGLEX)
+// Test that GL_*_ANGLEX formats result in error.
+TEST_P(Texture2DTestES3, InternalFormatNotEnabled_ANGLEX)
 {
-    // Note: This is the opposite of the usual test for extension. We only run the test
-    // if both extensions are NOT available. If neither is available, or if one both not
-    // both are available, then we need to test the format can't be used.
-    ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_ANGLE_rgbx_internal_format") &&
-                       IsGLExtensionEnabled("GL_EXT_sRGB"));
-    InternalFormatNotEnabledHelper(GL_RGBX8_SRGB_ANGLEX, GL_RGB);
-}
+    auto verify = [](GLenum internalFormat, GLenum format, GLenum type) {
+        GLTexture texture;
+        glBindTexture(GL_TEXTURE_2D, texture);
 
-// Test that GL_BGRX8_ANGLEX results in GL_INVALID_ENUM if the extensions are not enabled.
-TEST_P(Texture2DTestES3, InternalFormatNotEnabled_BGRX8_ANGLEX)
-{
-    // Note: This is the opposite of the usual test for extension. We only run the test
-    // if the extension is NOT available.
-    ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_EXT_texture_format_BGRA8888"));
-    InternalFormatNotEnabledHelper(GL_BGRX8_ANGLEX, GL_BGR_EXT);
-}
+        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, format, type, nullptr);
+        EXPECT_GL_ERROR(GL_INVALID_ENUM) << internalFormat;
 
-// Test that GL_BGRX8_SRGB_ANGLEX results in GL_INVALID_ENUM if the extensions are not enabled.
-TEST_P(Texture2DTestES3, InternalFormatNotEnabled_BGRX8_SRGB_ANGLEX)
-{
-    // Note: This is the opposite of the usual test for extension. We only run the test
-    // if both extensions are NOT available. If neither is available, or if one both not
-    // both are available, then we need to test the format can't be used.
-    ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_EXT_texture_format_BGRA8888") &&
-                       IsGLExtensionEnabled("GL_EXT_sRGB"));
-    InternalFormatNotEnabledHelper(GL_BGRX8_SRGB_ANGLEX, GL_BGR_EXT);
+        glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, 1, 1);
+        EXPECT_GL_ERROR(GL_INVALID_ENUM) << internalFormat;
+
+        GLRenderbuffer rbo;
+        glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+        glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, 1, 1);
+        EXPECT_GL_ERROR(GL_INVALID_ENUM) << internalFormat;
+    };
+
+    verify(GL_A1RGB5_ANGLEX, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT);
+    verify(GL_BGRX8_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_BYTE);
+    verify(GL_BGR565_ANGLEX, GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
+    verify(GL_BGRA4_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT);
+    verify(GL_BGR5_A1_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT);
+    verify(GL_INT_64_ANGLEX, GL_RED, GL_INT);
+    verify(GL_UINT_64_ANGLEX, GL_RED, GL_UNSIGNED_INT);
+    verify(GL_BGRA8_SRGB_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_BYTE);
+    verify(GL_BGR10_A2_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_BGRX8_SRGB_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_BYTE);
+    verify(GL_RGBX8_SRGB_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_BYTE);
+    verify(GL_R10X6G10X6B10X6A10X6_UNORM_ANGLEX, GL_RGBA, GL_UNSIGNED_SHORT);
+    verify(GL_RGBA8_TYPELESS_ANGLEX, GL_RGBA, GL_UNSIGNED_BYTE);
+    verify(GL_RGBA8_TYPELESS_SRGB_ANGLEX, GL_RGBA, GL_UNSIGNED_BYTE);
+    verify(GL_BGRA8_TYPELESS_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_BYTE);
+    verify(GL_BGRA8_TYPELESS_SRGB_ANGLEX, GL_BGRA_EXT, GL_UNSIGNED_BYTE);
+    verify(GL_R8_SSCALED_ANGLEX, GL_RED, GL_BYTE);
+    verify(GL_RG8_SSCALED_ANGLEX, GL_RG, GL_BYTE);
+    verify(GL_RGB8_SSCALED_ANGLEX, GL_RGB, GL_BYTE);
+    verify(GL_RGBA8_SSCALED_ANGLEX, GL_RGBA, GL_BYTE);
+    verify(GL_R8_USCALED_ANGLEX, GL_RED, GL_UNSIGNED_BYTE);
+    verify(GL_RG8_USCALED_ANGLEX, GL_RG, GL_UNSIGNED_BYTE);
+    verify(GL_RGB8_USCALED_ANGLEX, GL_RGB, GL_UNSIGNED_BYTE);
+    verify(GL_RGBA8_USCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_BYTE);
+    verify(GL_R16_SSCALED_ANGLEX, GL_RED, GL_SHORT);
+    verify(GL_RG16_SSCALED_ANGLEX, GL_RG, GL_SHORT);
+    verify(GL_RGB16_SSCALED_ANGLEX, GL_RGB, GL_SHORT);
+    verify(GL_RGBA16_SSCALED_ANGLEX, GL_RGBA, GL_SHORT);
+    verify(GL_R16_USCALED_ANGLEX, GL_RED, GL_UNSIGNED_SHORT);
+    verify(GL_RG16_USCALED_ANGLEX, GL_RG, GL_UNSIGNED_SHORT);
+    verify(GL_RGB16_USCALED_ANGLEX, GL_RGB, GL_UNSIGNED_SHORT);
+    verify(GL_RGBA16_USCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_SHORT);
+    verify(GL_R32_SSCALED_ANGLEX, GL_RED, GL_INT);
+    verify(GL_RG32_SSCALED_ANGLEX, GL_RG, GL_INT);
+    verify(GL_RGB32_SSCALED_ANGLEX, GL_RGB, GL_INT);
+    verify(GL_RGBA32_SSCALED_ANGLEX, GL_RGBA, GL_INT);
+    verify(GL_R32_USCALED_ANGLEX, GL_RED, GL_UNSIGNED_INT);
+    verify(GL_RG32_USCALED_ANGLEX, GL_RG, GL_UNSIGNED_INT);
+    verify(GL_RGB32_USCALED_ANGLEX, GL_RGB, GL_UNSIGNED_INT);
+    verify(GL_RGBA32_USCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_INT);
+    verify(GL_R32_SNORM_ANGLEX, GL_RED, GL_INT);
+    verify(GL_RG32_SNORM_ANGLEX, GL_RG, GL_INT);
+    verify(GL_RGB32_SNORM_ANGLEX, GL_RGB, GL_INT);
+    verify(GL_RGBA32_SNORM_ANGLEX, GL_RGBA, GL_INT);
+    verify(GL_R32_UNORM_ANGLEX, GL_RED, GL_UNSIGNED_INT);
+    verify(GL_RG32_UNORM_ANGLEX, GL_RG, GL_UNSIGNED_INT);
+    verify(GL_RGB32_UNORM_ANGLEX, GL_RGB, GL_UNSIGNED_INT);
+    verify(GL_RGBA32_UNORM_ANGLEX, GL_RGBA, GL_UNSIGNED_INT);
+    verify(GL_R32_FIXED_ANGLEX, GL_RED, GL_INT);
+    verify(GL_RG32_FIXED_ANGLEX, GL_RG, GL_INT);
+    verify(GL_RGB32_FIXED_ANGLEX, GL_RGB, GL_INT);
+    verify(GL_RGBA32_FIXED_ANGLEX, GL_RGBA, GL_INT);
+    verify(GL_RGB10_A2_SINT_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_RGB10_A2_SNORM_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_RGB10_A2_SSCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_RGB10_A2_USCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_A2_RGB10_UNORM_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_A2_RGB10_SNORM_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_A2_RGB10_USCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_A2_RGB10_SSCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_X2_RGB10_UINT_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_X2_RGB10_SINT_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_X2_RGB10_USCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_X2_RGB10_SSCALED_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_X2_RGB10_UNORM_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
+    verify(GL_X2_RGB10_SNORM_ANGLEX, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
 }
 
 // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
@@ -9151,15 +9190,6 @@
     TextureUploadPBOHelper(GL_RGBX8_ANGLE, GL_RGB);
 }
 
-// Regression test for a bug where D3D11 backend incorrectly computes the source row pitch
-// for emulated RGBX/BGRX textures during TexSubImage2D uploads from a PBO.
-TEST_P(Texture2DTestES3, TextureUploadPBO_RGBX8_SRGB_ANGLEX)
-{
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_rgbx_internal_format"));
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_sRGB"));
-    TextureUploadPBOHelper(GL_RGBX8_SRGB_ANGLEX, GL_RGB);
-}
-
 // Test that GL_RGBX8_ANGLE can be read back into a PBO.
 TEST_P(Texture2DTestES3, TextureRGBXReadPBO)
 {
Loading diff…

Original Bug Report

reported by [email protected]

OOB Write in ANGLE Vulkan via GL_BGR5_A1_ANGLEX format mismatch

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 format mapping mismatch exists in ANGLE’s Vulkan backend when processing the GL_BGR5_A1_ANGLEX internal format. The system allocates a staging buffer assuming 2 bytes per pixel, but selects a load function that writes 4 bytes per pixel, leading to a potential out-of-bounds write (heap overflow) in the GPU process.

Affected files:

  • third_party/angle/src/libANGLE/renderer/load_functions_table_autogen.cpp
  • third_party/angle/src/image_util/loadimage.cpp
  • third_party/angle/src/libANGLE/renderer/vulkan/vk_format_table_autogen.cpp
  • third_party/angle/src/libANGLE/renderer/Format_table_autogen.cpp

Estimated timestamp from git blame: 2021-09-08

Description

A potential out-of-bounds (OOB) write vulnerability exists in ANGLE’s Vulkan backend. The issue arises from a discrepancy between how ANGLE calculates the required staging buffer size for a specific internal format and the amount of data written by the function selected to load that data.

Specifically, when an image is loaded using the internal format GL_BGR5_A1_ANGLEX (0x6ABD) paired with the type GL_UNSIGNED_BYTE, the following occurs in third_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp (ImageHelper::stageSubresourceUpdateImpl):

  1. Allocation: ANGLE maps GL_BGR5_A1_ANGLEX to the Vulkan storage format angle::FormatID::B5G5R5A1_UNORM. This format has a defined pixelBytes of 2. Consequently, ANGLE allocates a Vulkan staging buffer sized for exactly 2 bytes per pixel (2 * width * height * depth).
  2. Load Function: ANGLE retrieves the load function for this format/type combination. In load_functions_table_autogen.cpp, GL_BGR5_A1_ANGLEX with GL_UNSIGNED_BYTE maps to LoadRGB5A1ToRGBA8.
  3. OOB Write: The LoadRGB5A1ToRGBA8 function (in loadimage.cpp) performs a 16-bit to 32-bit pixel expansion. For every pixel processed, it unconditionally writes 4 bytes (uint8_t) to the destination staging buffer.

Because the buffer is allocated for 2 bytes per pixel, writing 4 bytes per pixel results in a massive contiguous heap overflow, writing past the allocated buffer bounds by 2 * width * height * depth bytes.

Impact

A compromised renderer process can trigger this vulnerability by sending a maliciously crafted TexImage2D IPC command to the GPU process. The passthrough command decoder forwards this to ANGLE, where ValidES3FormatCombination explicitly accepts the parameters. This results in a highly reliable heap overflow in the unsandboxed GPU process. The attacker dictates the overflow size via texture dimensions and partially controls the corrupted memory content through the deterministic 5-5-5-1 to 8-8-8-8 pixel expansion, potentially leading to arbitrary code execution.

Suggested Steps to Trigger (Theoretical)

Note: These are potential steps based on code analysis; our tooling cannot yet execute a working proof of concept.

  1. Compromise a renderer process.
  2. Construct a TexImage2D command via the command buffer IPC mechanism.
  3. Set the parameters to trigger the vulnerable path:
    • internalformat = 0x6ABD (GL_BGR5_A1_ANGLEX)
    • format = GL_BGRA_EXT
    • type = GL_UNSIGNED_BYTE
  4. Provide shared memory containing the 16-bit pixel data.
  5. The GPU process will allocate a 2bpp staging buffer but write 4bpp into it, causing memory corruption.

Suggested Fix

The autogeneration scripts that map internal formats to storage formats and load functions must be synchronized.

Either:

  1. Change the mapping in load_functions_table_autogen.cpp so that GL_BGR5_A1_ANGLEX + GL_UNSIGNED_BYTE uses a load function that does not expand to 32-bits (i.e., writes exactly 2 bytes per pixel to match B5G5R5A1_UNORM).
  2. If 32-bit expansion is required, change the storage format mapping in vk_format_table_autogen.cpp for GL_BGR5_A1_ANGLEX to a format with pixelBytes = 4 (e.g., a standard 8-8-8-8 format) so the staging buffer is allocated with sufficient size.

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