Chrome · WebGL
CVE-2026-17727
OOB in WebGL
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
include/platform/autogen/FeaturesGL_autogen.hinclude/platform/gl_features.jsonsrc/libANGLE/renderer/gl/TextureGL.cppsrc/libANGLE/renderer/gl/renderergl_utils.cppsrc/tests/gl_tests/TextureTest.cpp
Patch
From 88229d73b7cf2b464ffd94621928690c08b16a1b Mon Sep 17 00:00:00 2001 From: Ken Russell <[email protected]> Date: Tue, 14 Jul 2026 17:44:47 -0700 Subject: [PATCH] GL: reset base level to 0 for ASTC uploads on PowerVR. Add workaround which resets the base level of the destination texture while performing TexSubImage2D uploads of ASTC data. Apply this workaround on Imagination's GLES drivers. Add a new test which both catches the crash bug on Pixel 10 as well as verifies rendering from the ASTC-uploaded texture. Co-authored with Gemini. [agy] CONV: b6f783ec-ad2a-4e4b-95c8-86d2fe49e5ee Bug: chromium:529932631 Change-Id: I15753dd2d1e5de85bf923289ae589ccd74c05fb1 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8094449 Auto-Submit: Kenneth Russell <[email protected]> Commit-Queue: Kenneth Russell <[email protected]> Reviewed-by: Amirali Abdolrashidi <[email protected]> Reviewed-by: Geoff Lang <[email protected]> --- diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h index f39bce2..9f60d6b 100644 --- a/include/platform/autogen/FeaturesGL_autogen.h +++ b/include/platform/autogen/FeaturesGL_autogen.h @@ -254,6 +254,12 @@ &members, }; + FeatureInfo resetBaseLevelForASTCSubImage = { + "resetBaseLevelForASTCSubImage", + FeatureCategory::OpenGLWorkarounds, + &members, + }; + FeatureInfo limitMax3dArrayTextureSizeTo1024 = { "limitMax3dArrayTextureSizeTo1024", FeatureCategory::OpenGLWorkarounds, diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json index f035663..127cccf 100644 --- a/include/platform/gl_features.json +++ b/include/platform/gl_features.json @@ -309,6 +309,15 @@ "issue": "https://crbug.com/705865" }, { + "name": "reset_base_level_for_ASTC_sub_image", + "category": "Workarounds", + "description": [ + "Reset texture base level before calling glCompressedTexSubImage2D for ASTC textures to ", + "work around PowerVR driver bug." + ], + "issue": "https://crbug.com/528131939" + }, + { "name": "limit_max_3d_array_texture_size_to_1024", "category": "Workarounds", "description": [ diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp index b4fd17d..d0f10ee 100644 --- a/src/libANGLE/renderer/gl/TextureGL.cpp +++ b/src/libANGLE/renderer/gl/TextureGL.cpp @@ -744,6 +744,13 @@ stateManager->bindTexture(getType(), mTextureID); ANGLE_TRY(stateManager->setPixelUnpackState(context, unpack)); + + const bool isASTC = gl::IsASTC2DFormat(format) || gl::IsASTC3DFormat(format); + if (features.resetBaseLevelForASTCSubImage.enabled && isASTC) + { + ANGLE_TRY(setBaseLevel(context, 0)); + } + if (nativegl::UseTexImage2D(getType())) { ASSERT(area.z == 0 && area.depth == 1); diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp index 714dd1a..5cbbd9f 100644 --- a/src/libANGLE/renderer/gl/renderergl_utils.cpp +++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp @@ -2491,6 +2491,8 @@ ANGLE_FEATURE_CONDITION(features, resetTexImage2DBaseLevel, IsApple() && isIntel && GetMacOSVersion() >= OSVersion(10, 12, 4)); + ANGLE_FEATURE_CONDITION(features, resetBaseLevelForASTCSubImage, IsPowerVR(vendor)); + ANGLE_FEATURE_CONDITION(features, adjustSrcDstRegionForBlitFramebuffer, IsLinux() || (IsAndroid() && isNvidia) || (IsWindows() && isNvidia) || (IsApple() && functions->standard == STANDARD_GL_ES)); diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp index dcfc647..1614822 100644 --- a/src/tests/gl_tests/TextureTest.cpp +++ b/src/tests/gl_tests/TextureTest.cpp @@ -11453,6 +11453,114 @@ } } +// Test that compressed sub-image updates work when TEXTURE_BASE_LEVEL > 0. +// This is a workaround for a PowerVR driver bug where it miscomputes the offset. +TEST_P(Texture2DTestES3, ASTCCompressedSubImageWithBaseLevel) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_KHR_texture_compression_astc_ldr")); + + // Use shaders that match the proof-of-concept. + // They don't use vertex attributes, but gl_VertexID to generate a quad. + const char *kVS = R"(#version 300 es +out vec2 uv; +void main() +{ + vec2 p = vec2(gl_VertexID & 1, gl_VertexID >> 1); + uv = p; + gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); +})"; + + const char *kFS = R"(#version 300 es +precision highp float; +uniform sampler2D t; +in vec2 uv; +out vec4 c; +void main() +{ + c = texture(t, uv); +})"; + + ANGLE_GL_PROGRAM(program, kVS, kFS); + glUseProgram(program); + GLint texLocation = glGetUniformLocation(program, "t"); + ASSERT_NE(-1, texLocation); + glUniform1i(texLocation, 0); + ASSERT_GL_NO_ERROR(); + + // 8x5 ASTC format. + GLenum format = GL_COMPRESSED_RGBA_ASTC_8x5_KHR; + constexpr GLsizei kWidth = 8; + constexpr GLsizei kHeight = 160; + constexpr GLsizei kLevels = 5; + // Void-extent blocks for ASTC. + // Format: 0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, R_lo, R_hi, G_lo, G_hi, B_lo, B_hi, + // A_lo, A_hi Red: (255, 0, 0, 255) -> R=0xFFFF, G=0x0000, B=0x0000, A=0xFFFF + constexpr uint8_t kBlockRed[16] = {0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}; + // Green: (0, 255, 0, 255) -> R=0x0000, G=0xFFFF, B=0x0000, A=0xFFFF + constexpr uint8_t kBlockGreen[16] = {0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF}; + + // Level 4: 1x10 pixels. 8x5 blocks. Padded: 8x10. Blocks: 1x2 = 2. + std::vector<uint8_t> dataRed; + dataRed.reserve(32); + dataRed.insert(dataRed.end(), std::begin(kBlockRed), std::end(kBlockRed)); + dataRed.insert(dataRed.end(), std::begin(kBlockRed), std::end(kBlockRed)); + + // Level 3: 1x20 pixels. 8x5 blocks. Padded: 8x20. Blocks: 1x4 = 4. + std::vector<uint8_t> dataGreen; + dataGreen.reserve(64); + for (int i = 0; i < 4; ++i) + { + dataGreen.insert(dataGreen.end(), std::begin(kBlockGreen), std::end(kBlockGreen)); + } + + // Loop multiple times to increase chances of hitting OOB write/crash if workaround fails. + // Keep textures alive to groom the heap similarly to the WebGL PoC. + constexpr int kIterations = 16; + std::vector<GLTexture> textures(kIterations); + for (int i = 0; i < kIterations; ++i) + { + glBindTexture(GL_TEXTURE_2D, textures[i]); + + glTexStorage2D(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight); + ASSERT_GL_NO_ERROR(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 4); + ASSERT_GL_NO_ERROR(); + + // Upload Red to level 4. + glCompressedTexSubImage2D(GL_TEXTURE_2D, 4, 0, 0, 1, 10, format, + static_cast<GLsizei>(dataRed.size()), dataRed.data()); + ASSERT_GL_NO_ERROR(); + + // Upload Green to level 3. + glCompressedTexSubImage2D(GL_TEXTURE_2D, 3, 0, 0, 1, 20, format, + static_cast<GLsizei>(dataGreen.size()), dataGreen.data()); + ASSERT_GL_NO_ERROR(); + + // Draw. Since BASE_LEVEL is 4, it should sample from level 4 (Red). + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glFinish(); + ASSERT_GL_NO_ERROR(); + + EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 0, 0, 255), 1); + + // Change BASE_LEVEL to 3. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 3); + ASSERT_GL_NO_ERROR(); + + // Draw again. Now it should sample from level 3 (Green).
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index dcfc647..1614822 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -11453,6 +11453,114 @@
}
}
+// Test that compressed sub-image updates work when TEXTURE_BASE_LEVEL > 0.
+// This is a workaround for a PowerVR driver bug where it miscomputes the offset.
+TEST_P(Texture2DTestES3, ASTCCompressedSubImageWithBaseLevel)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_KHR_texture_compression_astc_ldr"));
+
+ // Use shaders that match the proof-of-concept.
+ // They don't use vertex attributes, but gl_VertexID to generate a quad.
+ const char *kVS = R"(#version 300 es
+out vec2 uv;
+void main()
+{
+ vec2 p = vec2(gl_VertexID & 1, gl_VertexID >> 1);
+ uv = p;
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
+})";
+
+ const char *kFS = R"(#version 300 es
+precision highp float;
+uniform sampler2D t;
+in vec2 uv;
+out vec4 c;
+void main()
+{
+ c = texture(t, uv);
+})";
+
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
+ glUseProgram(program);
+ GLint texLocation = glGetUniformLocation(program, "t");
+ ASSERT_NE(-1, texLocation);
+ glUniform1i(texLocation, 0);
+ ASSERT_GL_NO_ERROR();
+
+ // 8x5 ASTC format.
+ GLenum format = GL_COMPRESSED_RGBA_ASTC_8x5_KHR;
+ constexpr GLsizei kWidth = 8;
+ constexpr GLsizei kHeight = 160;
+ constexpr GLsizei kLevels = 5;
+ // Void-extent blocks for ASTC.
+ // Format: 0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, R_lo, R_hi, G_lo, G_hi, B_lo, B_hi,
+ // A_lo, A_hi Red: (255, 0, 0, 255) -> R=0xFFFF, G=0x0000, B=0x0000, A=0xFFFF
+ constexpr uint8_t kBlockRed[16] = {0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF};
+ // Green: (0, 255, 0, 255) -> R=0x0000, G=0xFFFF, B=0x0000, A=0xFFFF
+ constexpr uint8_t kBlockGreen[16] = {0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF};
+
+ // Level 4: 1x10 pixels. 8x5 blocks. Padded: 8x10. Blocks: 1x2 = 2.
+ std::vector<uint8_t> dataRed;
+ dataRed.reserve(32);
+ dataRed.insert(dataRed.end(), std::begin(kBlockRed), std::end(kBlockRed));
+ dataRed.insert(dataRed.end(), std::begin(kBlockRed), std::end(kBlockRed));
+
+ // Level 3: 1x20 pixels. 8x5 blocks. Padded: 8x20. Blocks: 1x4 = 4.
+ std::vector<uint8_t> dataGreen;
+ dataGreen.reserve(64);
+ for (int i = 0; i < 4; ++i)
+ {
+ dataGreen.insert(dataGreen.end(), std::begin(kBlockGreen), std::end(kBlockGreen));
+ }
+
+ // Loop multiple times to increase chances of hitting OOB write/crash if workaround fails.
+ // Keep textures alive to groom the heap similarly to the WebGL PoC.
+ constexpr int kIterations = 16;
+ std::vector<GLTexture> textures(kIterations);
+ for (int i = 0; i < kIterations; ++i)
+ {
+ glBindTexture(GL_TEXTURE_2D, textures[i]);
+
+ glTexStorage2D(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight);
+ ASSERT_GL_NO_ERROR();
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 4);
+ ASSERT_GL_NO_ERROR();
+
+ // Upload Red to level 4.
+ glCompressedTexSubImage2D(GL_TEXTURE_2D, 4, 0, 0, 1, 10, format,
+ static_cast<GLsizei>(dataRed.size()), dataRed.data());
+ ASSERT_GL_NO_ERROR();
+
+ // Upload Green to level 3.
+ glCompressedTexSubImage2D(GL_TEXTURE_2D, 3, 0, 0, 1, 20, format,
+ static_cast<GLsizei>(dataGreen.size()), dataGreen.data());
+ ASSERT_GL_NO_ERROR();
+
+ // Draw. Since BASE_LEVEL is 4, it should sample from level 4 (Red).
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ glFinish();
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 0, 0, 255), 1);
+
+ // Change BASE_LEVEL to 3.
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 3);
+ ASSERT_GL_NO_ERROR();
+
+ // Draw again. Now it should sample from level 3 (Green).
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ glFinish();
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0, 255, 0, 255), 1);
+ }
+}
+
// Test that the selected decode precision is actually used for texture decoding.
TEST_P(Texture2DTestES3, ASTCDecodeModeSwitch)
{
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page