Chrome · GPU
CVE-2026-87647
Uninitialized Memory in GPU
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc |
modified | |
TEST_Fgpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc |
modified | |
ifgpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc |
modified |
Files Changed
gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.ccgpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
Patch
From 5696f96dbbe9994e7025880bd78057444557ccdf Mon Sep 17 00:00:00 2001 From: vikas soni <[email protected]> Date: Tue, 01 Sep 2026 13:58:04 -0700 Subject: [PATCH] [GPU Security]: Require initial data for compressed GL SharedImages. GLCommonImageBackingFactory::CanCreateTexture only ran compressed-format validation when pixel_data was non-empty, so a compressed SharedImage could be created with no initial data. Compressed formats are not color-renderable and therefore cannot be cleared after creation, and glCompressedTexImage2D does not accept null data for non-zero dimensions. Run compressed-format validation unconditionally and require that pixel_data matches the computed compressed size, which rejects requests with empty pixel data. Bug: 536673946 Change-Id: I6c25102d8d10685aadbd993efac0bb6cf5296cd6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8299334 Commit-Queue: vikas soni <[email protected]> Reviewed-by: Vasiliy Telezhnikov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1690102} --- diff --git a/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc index 48fbdfd..f9153cc 100644 --- a/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc +++ b/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc @@ -219,62 +219,63 @@ return false; } - // If we have initial data to upload, ensure it is sized appropriately. - if (!pixel_data.empty()) { - DCHECK_EQ(format_infos.size(), 1u); + if (format_infos[0].is_compressed) { + CHECK_EQ(format_infos.size(), 1u); const FormatInfo& format_info = format_infos[0]; + const char* error_message = "unspecified"; + if (!gles2::ValidateCompressedTexDimensions( + target, /*level=*/0, size.width(), size.height(), /*depth=*/1, + format_info.image_internal_format, &error_message)) { + DVLOG(2) << "CreateSharedImage: " + "ValidateCompressedTexDimensionsFailed with error: " + << error_message; + return false; + } - if (format_info.is_compressed) { - const char* error_message = "unspecified"; - if (!gles2::ValidateCompressedTexDimensions( - target, /*level=*/0, size.width(), size.height(), /*depth=*/1, - format_info.image_internal_format, &error_message)) { - DVLOG(2) << "CreateSharedImage: " - "ValidateCompressedTexDimensionsFailed with error: " - << error_message; - return false; - } + GLsizei bytes_required = 0; + if (!gles2::GetCompressedTexSizeInBytes( + /*function_name=*/nullptr, size.width(), size.height(), + /*depth=*/1, format_info.image_internal_format, &bytes_required, + /*error_state=*/nullptr)) { + DVLOG(2) << "CreateSharedImage: Unable to compute required size for " + "initial texture upload."; + return false; + } - GLsizei bytes_required = 0; - if (!gles2::GetCompressedTexSizeInBytes( - /*function_name=*/nullptr, size.width(), size.height(), - /*depth=*/1, format_info.image_internal_format, &bytes_required, - /*error_state=*/nullptr)) { - DVLOG(2) << "CreateSharedImage: Unable to compute required size for " + // Compressed textures cannot be cleared so they must be created with + // initial pixel data of the correct size. + if (bytes_required < 0 || + pixel_data.size() != static_cast<size_t>(bytes_required)) { + DVLOG(2) << "CreateSharedImage: Initial data does not have expected " + "size."; + return false; + } + } else if (!pixel_data.empty()) { + // If we have initial data to upload, ensure it is sized appropriately. + CHECK_EQ(format_infos.size(), 1u); + const FormatInfo& format_info = format_infos[0]; + uint32_t bytes_required; + uint32_t unpadded_row_size = 0u; + uint32_t padded_row_size = 0u; + if (!gles2::GLES2Util::ComputeImageDataSizes( + size.width(), size.height(), /*depth=*/1, format_info.gl_format, + format_info.gl_type, /*alignment=*/4, &bytes_required, + &unpadded_row_size, &padded_row_size)) { + LOG(ERROR) << "CreateSharedImage: Unable to compute required size for " "initial texture upload."; - return false; - } + return false; + } - if (bytes_required < 0 || - pixel_data.size() != static_cast<size_t>(bytes_required)) { - DVLOG(2) << "CreateSharedImage: Initial data does not have expected " + // The GL spec, used in the computation for required bytes in the function + // above, assumes no padding is required for the last row in the image. + // But the client data does include this padding, so we add it for the + // data validation check here. + uint32_t padding = padded_row_size - unpadded_row_size; + bytes_required += padding; + if (pixel_data.size() != bytes_required) { + LOG(ERROR) << "CreateSharedImage: Initial data does not have expected " "size."; - return false; - } - } else { - uint32_t bytes_required; - uint32_t unpadded_row_size = 0u; - uint32_t padded_row_size = 0u; - if (!gles2::GLES2Util::ComputeImageDataSizes( - size.width(), size.height(), /*depth=*/1, format_info.gl_format, - format_info.gl_type, /*alignment=*/4, &bytes_required, - &unpadded_row_size, &padded_row_size)) { - LOG(ERROR) << "CreateSharedImage: Unable to compute required size for " - "initial texture upload."; - return false; - } - - // The GL spec, used in the computation for required bytes in the function - // above, assumes no padding is required for the last row in the image. - // But the client data does include this padding, so we add it for the - // data validation check here. - uint32_t padding = padded_row_size - unpadded_row_size; - bytes_required += padding; - if (pixel_data.size() != bytes_required) { - LOG(ERROR) << "CreateSharedImage: Initial data does not have expected " - "size."; - return false; - } + return false; } } diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc index 94139b6c..13f1050 100644 --- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc +++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc @@ -167,6 +167,33 @@ using GLTextureImageBackingFactoryWithReadbackTest = GLTextureImageBackingFactoryWithUploadTest; +TEST_F(GLTextureImageBackingFactoryTest, CompressedFormatRequiresInitialData) { + if (!supports_etc1_) { + GTEST_SKIP(); + } + + auto format = viz::SinglePlaneFormat::kETC1; + gfx::Size size(64, 64); + // Note: The specific usage doesn't matter here as long as it's supported by + // GLTextureImageBacking. + gpu::SharedImageUsageSet usage = SHARED_IMAGE_USAGE_GLES2_READ; + + // Compressed textures cannot be cleared so they must be created with initial + // pixel data. + bool supported = backing_factory_->CanCreateSharedImage( + usage, format, size, /*thread_safe=*/false, gfx::EMPTY_BUFFER, + GrContextType::kGL, {}); + EXPECT_FALSE(supported); + + // With correctly sized initial data the format is supported. + size_t required_size = format.MaybeEstimatedSizeInBytes(size).value(); + std::vector<uint8_t> initial_data(required_size); + supported = backing_factory_->CanCreateSharedImage( + usage, format, size, /*thread_safe=*/false, gfx::EMPTY_BUFFER, + GrContextType::kGL, initial_data); + EXPECT_TRUE(supported); +} + TEST_F(GLTextureImageBackingFactoryTest, InvalidFormat) { auto format = viz::SinglePlaneFormat::kBGR_565; gfx::Size size(256, 256);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
index 94139b6c..13f1050 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
@@ -167,6 +167,33 @@
using GLTextureImageBackingFactoryWithReadbackTest =
GLTextureImageBackingFactoryWithUploadTest;
+TEST_F(GLTextureImageBackingFactoryTest, CompressedFormatRequiresInitialData) {
+ if (!supports_etc1_) {
+ GTEST_SKIP();
+ }
+
+ auto format = viz::SinglePlaneFormat::kETC1;
+ gfx::Size size(64, 64);
+ // Note: The specific usage doesn't matter here as long as it's supported by
+ // GLTextureImageBacking.
+ gpu::SharedImageUsageSet usage = SHARED_IMAGE_USAGE_GLES2_READ;
+
+ // Compressed textures cannot be cleared so they must be created with initial
+ // pixel data.
+ bool supported = backing_factory_->CanCreateSharedImage(
+ usage, format, size, /*thread_safe=*/false, gfx::EMPTY_BUFFER,
+ GrContextType::kGL, {});
+ EXPECT_FALSE(supported);
+
+ // With correctly sized initial data the format is supported.
+ size_t required_size = format.MaybeEstimatedSizeInBytes(size).value();
+ std::vector<uint8_t> initial_data(required_size);
+ supported = backing_factory_->CanCreateSharedImage(
+ usage, format, size, /*thread_safe=*/false, gfx::EMPTY_BUFFER,
+ GrContextType::kGL, initial_data);
+ EXPECT_TRUE(supported);
+}
+
TEST_F(GLTextureImageBackingFactoryTest, InvalidFormat) {
auto format = viz::SinglePlaneFormat::kBGR_565;
gfx::Size size(256, 256);
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