CVE-2026-17687
Overview
Files Changed
src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cppsrc/tests/angle_end2end_tests_expectations.txtsrc/tests/gl_tests/VulkanImageTest.cpp
Patch
From 9fae371a99eb19d6e0880f229d7f1d9ecebe751b Mon Sep 17 00:00:00 2001
From: Stacy Gaikovaia <gaiko@google.com>
Date: Mon, 01 Jun 2026 11:39:52 -0400
Subject: [PATCH] No actualImageFormatID in VkImageImageSiblingVk
Add test for the fallback format. Suppress on PIXEL6 VULKAN
where the config is not yet supported.
Bug: chromium:516985726
Change-Id: If93c9fe0904390f22720537b3466b78de116bb8b
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7836133
Commit-Queue: Amirali Abdolrashidi <abdolrashidi@google.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
---
diff --git a/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp b/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp
index 4438d40..7eb8bf4 100644
--- a/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp
+++ b/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp
@@ -45,21 +45,10 @@
{
vk::Renderer *renderer = displayVk->getRenderer();
- const angle::FormatID formatID = vk::GetFormatIDFromVkFormat(mVkImageInfo.format);
+ angle::FormatID formatID = vk::GetFormatIDFromVkFormat(mVkImageInfo.format);
ANGLE_VK_CHECK(displayVk, formatID != angle::FormatID::NONE, VK_ERROR_FORMAT_NOT_SUPPORTED);
- const vk::Format &vkFormat = renderer->getFormat(formatID);
- const vk::ImageFormatSupport formatSupport = isRenderable(nullptr)
- ? vk::ImageFormatSupport::Renderable
- : vk::ImageFormatSupport::SampleOnly;
- angle::FormatID actualImageFormatID = vkFormat.getActualImageFormatID(formatSupport);
- if (renderer->getFeatures().preferBGR565ToRGB565.enabled &&
- formatID == angle::FormatID::R5G6B5_UNORM &&
- actualImageFormatID == angle::FormatID::B5G6R5_UNORM)
- {
- actualImageFormatID = angle::FormatID::R5G6B5_UNORM;
- }
- const angle::Format &format = angle::Format::Get(actualImageFormatID);
+ const angle::Format &format = angle::Format::Get(formatID);
angle::FormatID intendedFormatID;
if (mInternalFormat != GL_NONE)
@@ -72,17 +61,17 @@
}
else
{
+ const vk::Format &vkFormat = renderer->getFormat(formatID);
intendedFormatID = vkFormat.getIntendedFormatID();
mFormat = gl::Format(format.glInternalFormat);
}
// Create the image
constexpr bool kIsRobustInitEnabled = false;
- vk::ImageHelper::ImageFormats imageFormats = {
- vk::GetVkFormatFromFormatID(renderer, actualImageFormatID)};
+ vk::ImageHelper::ImageFormats imageFormats = {vk::GetVkFormatFromFormatID(renderer, formatID)};
mImage = new vk::ImageHelper();
mImage->init2DWeakReference(displayVk, mVkImage.release(), getSize(), false, intendedFormatID,
- actualImageFormatID, mVkImageInfo.flags, mVkImageInfo.usage, 1,
+ formatID, mVkImageInfo.flags, mVkImageInfo.usage, 1,
kIsRobustInitEnabled, imageFormats);
return angle::Result::Continue;
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index a8f8e3a..aeb808a 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -881,6 +881,9 @@
465140993 PIXEL6 VULKAN : GLSLTest_ES3_PackUnpackEmulation.PackSnorm2x16/* = SKIP
491488984 PIXEL6 VULKAN : GLSLTest_ES31.HelperInvocationsAfterDiscard/* = SKIP
+// Blocked on preferBGR565ToRGB565 availability
+519580088 PIXEL6 VULKAN : VulkanImageTest.ImportRGB565/ES3_Vulkan_ForceFallbackFormat = SKIP
+
// Pixel 10 expectations.
480069042 PIXEL10 GLES : AtomicCounterBufferTest31.DependentAtomicCounterBufferChange/* = SKIP
510449869 PIXEL10 GLES : BasicCopyTextureTest.SelfCopyOOBWrite/* = SKIP
diff --git a/src/tests/gl_tests/VulkanImageTest.cpp b/src/tests/gl_tests/VulkanImageTest.cpp
index 3d0341a..b16aa96 100644
--- a/src/tests/gl_tests/VulkanImageTest.cpp
+++ b/src/tests/gl_tests/VulkanImageTest.cpp
@@ -1024,12 +1024,120 @@
EXPECT_GL_NO_ERROR();
}
+// Test importing VkImage with VK_FORMAT_R5G6B5_UNORM_PACK16 when ForceFallbackFormat is enabled.
+// This forces ANGLE to use a fallback format (RGBA8) for internal GLES RGB565 textures,
+// but the imported sibling must still use the actual RGB565 format.
+TEST_P(VulkanImageTest, ImportRGB565)
+{
+ EGLWindow *window = getEGLWindow();
+ EGLDisplay display = window->getDisplay();
+
+ ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
+
+ VulkanHelper helper;
+ helper.initializeFromANGLE();
+
+ // Check if VK_FORMAT_R5G6B5_UNORM_PACK16 is supported by the physical device.
+ VkFormatProperties properties;
+ vkGetPhysicalDeviceFormatProperties(helper.getPhysicalDevice(), VK_FORMAT_R5G6B5_UNORM_PACK16,
+ &properties);
+ ANGLE_SKIP_TEST_IF(!(properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT));
+
+ constexpr VkImageUsageFlags kDefaultImageUsageFlags =
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+
+ VkImage vkImage = VK_NULL_HANDLE;
+ VkDeviceMemory vkDeviceMemory = VK_NULL_HANDLE;
+ VkDeviceSize deviceSize = 0u;
+ VkImageCreateInfo imageCreateInfo = {};
+
+ VkResult result = VK_SUCCESS;
+ result = helper.createImage2D(VK_FORMAT_R5G6B5_UNORM_PACK16, 0, kDefaultImageUsageFlags,
+ {kWidth, kHeight, 1}, &vkImage, &vkDeviceMemory, &deviceSize,
+ &imageCreateInfo);
+ EXPECT_EQ(result, VK_SUCCESS);
+ EXPECT_EQ(imageCreateInfo.sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);
+
+ uint64_t info = reinterpret_cast<uint64_t>(&imageCreateInfo);
+ EGLint attribs[] = {
+ EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
+ static_cast<EGLint>((info >> 32) & 0xffffffff),
+ EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
+ static_cast<EGLint>(info & 0xffffffff),
+ EGL_NONE,
+ };
+ EGLImageKHR eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_VULKAN_IMAGE_ANGLE,
+ reinterpret_cast<EGLClientBuffer>(&vkImage), attribs);
+ EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);
+
+ // Create a custom FBO with RGBA8 attachment for rendering to avoid 16x16 window limits.
+ GLTexture colorTex;
+ glBindTexture(GL_TEXTURE_2D, colorTex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ nullptr);
+
+ GLFramebuffer framebuffer;
+ glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Set up the imported RGB565 texture.
+ GLTexture texture;
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLuint textures[1] = {texture};
+ GLenum layouts[1] = {GL_NONE};
+ glAcquireTexturesANGLE(1, textures, layouts);
+
+ // Fill the texture with red color (0xF800 in RGB565).
+ std::vector<GLushort> pixels(kWidth * kHeight, 0xF800);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
+ pixels.data());
+ EXPECT_GL_NO_ERROR();
+
+ // Draw a quad to our FBO sampling from the imported texture.
+ ANGLE_GL_PROGRAM(drawTex2D, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
+ glUseProgram(drawTex2D);
+
+ glViewport(0, 0, kWidth, kHeight);
+ glClearColor(0.f, 0.f, 1.f, 1.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ drawQuad(drawTex2D, essl1_shaders::PositionAttrib(), 0.5f);
+ EXPECT_GL_NO_ERROR();
+
+ // Verify the FBO is now red.
+ EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
+
+ // Release the texture before destroying the image.
+ glReleaseTexturesANGLE(1, textures, layouts);
+
+ EXPECT_GL_NO_ERROR();
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ framebuffer.reset();
+ colorTex.reset();
+ texture.reset();
+
+ glFinish();
+
+ EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
+ vkDestroyImage(helper.getDevice(), vkImage, nullptr);
+ vkFreeMemory(helper.getDevice(), vkDeviceMemory, nullptr);
+}
+
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
-ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VulkanImageTest);
+ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(VulkanImageTest,
+ ES3_VULKAN().enable(Feature::ForceFallbackFormat));
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(
VulkanRGB565Test,
Regression Test / PoC
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index a8f8e3a..aeb808a 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -881,6 +881,9 @@
465140993 PIXEL6 VULKAN : GLSLTest_ES3_PackUnpackEmulation.PackSnorm2x16/* = SKIP
491488984 PIXEL6 VULKAN : GLSLTest_ES31.HelperInvocationsAfterDiscard/* = SKIP
+// Blocked on preferBGR565ToRGB565 availability
+519580088 PIXEL6 VULKAN : VulkanImageTest.ImportRGB565/ES3_Vulkan_ForceFallbackFormat = SKIP
+
// Pixel 10 expectations.
480069042 PIXEL10 GLES : AtomicCounterBufferTest31.DependentAtomicCounterBufferChange/* = SKIP
510449869 PIXEL10 GLES : BasicCopyTextureTest.SelfCopyOOBWrite/* = SKIP
diff --git a/src/tests/gl_tests/VulkanImageTest.cpp b/src/tests/gl_tests/VulkanImageTest.cpp
index 3d0341a..b16aa96 100644
--- a/src/tests/gl_tests/VulkanImageTest.cpp
+++ b/src/tests/gl_tests/VulkanImageTest.cpp
@@ -1024,12 +1024,120 @@
EXPECT_GL_NO_ERROR();
}
+// Test importing VkImage with VK_FORMAT_R5G6B5_UNORM_PACK16 when ForceFallbackFormat is enabled.
+// This forces ANGLE to use a fallback format (RGBA8) for internal GLES RGB565 textures,
+// but the imported sibling must still use the actual RGB565 format.
+TEST_P(VulkanImageTest, ImportRGB565)
+{
+ EGLWindow *window = getEGLWindow();
+ EGLDisplay display = window->getDisplay();
+
+ ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
+
+ VulkanHelper helper;
+ helper.initializeFromANGLE();
+
+ // Check if VK_FORMAT_R5G6B5_UNORM_PACK16 is supported by the physical device.
+ VkFormatProperties properties;
+ vkGetPhysicalDeviceFormatProperties(helper.getPhysicalDevice(), VK_FORMAT_R5G6B5_UNORM_PACK16,
+ &properties);
+ ANGLE_SKIP_TEST_IF(!(properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT));
+
+ constexpr VkImageUsageFlags kDefaultImageUsageFlags =
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+
+ VkImage vkImage = VK_NULL_HANDLE;
+ VkDeviceMemory vkDeviceMemory = VK_NULL_HANDLE;
+ VkDeviceSize deviceSize = 0u;
+ VkImageCreateInfo imageCreateInfo = {};
+
+ VkResult result = VK_SUCCESS;
+ result = helper.createImage2D(VK_FORMAT_R5G6B5_UNORM_PACK16, 0, kDefaultImageUsageFlags,
+ {kWidth, kHeight, 1}, &vkImage, &vkDeviceMemory, &deviceSize,
+ &imageCreateInfo);
+ EXPECT_EQ(result, VK_SUCCESS);
+ EXPECT_EQ(imageCreateInfo.sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);
+
+ uint64_t info = reinterpret_cast<uint64_t>(&imageCreateInfo);
+ EGLint attribs[] = {
+ EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
+ static_cast<EGLint>((info >> 32) & 0xffffffff),
+ EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
+ static_cast<EGLint>(info & 0xffffffff),
+ EGL_NONE,
+ };
+ EGLImageKHR eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_VULKAN_IMAGE_ANGLE,
+ reinterpret_cast<EGLClientBuffer>(&vkImage), attribs);
+ EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);
+
+ // Create a custom FBO with RGBA8 attachment for rendering to avoid 16x16 window limits.
+ GLTexture colorTex;
+ glBindTexture(GL_TEXTURE_2D, colorTex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ nullptr);
+
+ GLFramebuffer framebuffer;
+ glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Set up the imported RGB565 texture.
+ GLTexture texture;
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLuint textures[1] = {texture};
+ GLenum layouts[1] = {GL_NONE};
+ glAcquireTexturesANGLE(1, textures, layouts);
+
+ // Fill the texture with red color (0xF800 in RGB565).
+ std::vector<GLushort> pixels(kWidth * kHeight, 0xF800);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
+ pixels.data());
+ EXPECT_GL_NO_ERROR();
+
+ // Draw a quad to our FBO sampling from the imported texture.
+ ANGLE_GL_PROGRAM(drawTex2D, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
+ glUseProgram(drawTex2D);
+
+ glViewport(0, 0, kWidth, kHeight);
+ glClearColor(0.f, 0.f, 1.f, 1.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ drawQuad(drawTex2D, essl1_shaders::PositionAttrib(), 0.5f);
+ EXPECT_GL_NO_ERROR();
+
+ // Verify the FBO is now red.
+ EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
+
+ // Release the texture before destroying the image.
+ glReleaseTexturesANGLE(1, textures, layouts);
+
+ EXPECT_GL_NO_ERROR();
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ framebuffer.reset();
+ colorTex.reset();
+ texture.reset();
+
+ glFinish();
+
+ EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
+ vkDestroyImage(helper.getDevice(), vkImage, nullptr);
+ vkFreeMemory(helper.getDevice(), vkDeviceMemory, nullptr);
+}
+
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
-ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VulkanImageTest);
+ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(VulkanImageTest,
+ ES3_VULKAN().enable(Feature::ForceFallbackFormat));
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(
VulkanRGB565Test,
- ES3_VULKAN_SWIFTSHADER().enable(Feature::PreferBGR565ToRGB565));
+ ES3_VULKAN_SWIFTSHADER().enable(Feature::PreferBGR565ToRGB565),
+ ES3_VULKAN().enable(Feature::PreferBGR565ToRGB565).enable(Feature::ForceFallbackFormat));
ANGLE_INSTANTIATE_TEST_ES3(VulkanMemoryTest);
} // namespace angle
Original Bug Report
GPU memory corruption due to mismatched fallback format in VkImageImageSiblingVk
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: ANGLE’s Vulkan backend can resolve a wrapped external VkImage to a mismatched fallback format when full format capabilities are not supported by the driver. This leads to the creation of a VkImageView with an incompatible format class on an image created without mutable format flags, violating Vulkan specifications and potentially causing GPU-process memory corruption. This issue can potentially be triggered by a compromised renderer process via GLES2 SharedImage requests.
Affected files:
third_party/angle/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp
Estimated timestamp from git blame: 2021-12-03
Root Cause Analysis
In third_party/angle/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp, VkImageImageSiblingVk::initImpl wraps an external VkImage and resolves its actual internal format using ANGLE’s driver-specific fallback table instead of enforcing compatibility with the original format:
// third_party/angle/src/libANGLE/renderer/vulkan/VkImageImageSiblingVk.cpp
angle::Result VkImageImageSiblingVk::initImpl(DisplayVk *displayVk)
{
const angle::FormatID formatID = vk::GetFormatIDFromVkFormat(mVkImageInfo.format);
...
const vk::Format &vkFormat = renderer->getFormat(formatID);
const vk::ImageFormatSupport formatSupport = isRenderable(nullptr)
? vk::ImageFormatSupport::Renderable
: vk::ImageFormatSupport::SampleOnly;
angle::FormatID actualImageFormatID = vkFormat.getActualImageFormatID(formatSupport);
...
mImage->init2DWeakReference(displayVk, mVkImage.release(), getSize(), false, intendedFormatID,
actualImageFormatID, mVkImageInfo.flags, mVkImageInfo.usage, 1,
kIsRobustInitEnabled, imageFormats);
When importing a VkImage created with VK_FORMAT_R4G4B4A4_UNORM_PACK16 (16-bit, 2 bpp), if the underlying Vulkan driver supports the format for attachment usage but lacks full blending or filtering support, vkFormat.getActualImageFormatID(formatSupport) resolves to the fallback format VK_FORMAT_R8G8B8A8_UNORM (32-bit, 4 bpp) based on the mapping defined in vk_format_table_autogen.cpp:
static constexpr ImageFormatInitInfo kInfo[] = {
{angle::FormatID::R4G4B4A4_UNORM, nullptr},
{angle::FormatID::R8G8B8A8_UNORM, nullptr},
};
This mismatched format is stored as mActualFormatID in the ImageHelper instance. Consequently, during subsequent rendering or sampling operations, ANGLE creates image views targeting the underlying VkImage using mActualFormatID via initLayerImageViewImpl:
angle::Result ImageHelper::initLayerImageViewImpl(...) const {
VkImageViewCreateInfo viewInfo = {};
viewInfo.image = mImage.getHandle(); // Underlying R4G4B4A4 image
viewInfo.format = imageFormat; // VK_FORMAT_R8G8B8A8_UNORM
Because the underlying VkImage was allocated without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT (and since R4G4B4A4 and R8G8B8A8 belong to entirely different, incompatible format classes), this directly violates VUID-VkImageViewCreateInfo-image-01762 and VUID-VkImageViewCreateInfo-image-01761.
Depending on how the underlying Vulkan driver handles image views and memory strides, sampling or rendering through this mismatched view can cause out-of-bounds GPU memory reads and writes, potentially leading to memory corruption or a GPU process compromise.
Potential Trigger Path
An attacker in control of a compromised renderer process could potentially trigger this issue through the following sequence:
- The compromised renderer requests a SharedImage with
format = viz::SinglePlaneFormat::kRGBA_4444andusagecontaining GLES2 read/write flags. - In the GPU process,
AngleVulkanImageBackingFactorycreates aVulkanImageusing the formatVK_FORMAT_R4G4B4A4_UNORM_PACK16andflags = 0(no mutable format bit). - During GL representation initialization,
InitializePassthroughTexturecallsCreateEGLImagewith the native image metadata. - Inside ANGLE,
DisplayVk::createExternalImageSiblinginstantiates aVkImageImageSiblingVkto wrap the importedVkImage. - On affected Vulkan drivers where
R4G4B4A4_UNORMlacks full support (e.g. failingHasFullTextureFormatSupport),VkImageImageSiblingVk::initImplresolvesactualImageFormatIDtoVK_FORMAT_R8G8B8A8_UNORM. - When the renderer performs sampling or drawing operations on the texture, ANGLE invokes
vkCreateImageViewusing the mismatchedVK_FORMAT_R8G8B8A8_UNORMformat on the 16-bitR4G4B4A4image, triggering undefined driver behavior.
Note: These steps are based on static analysis of the codebase and represent a potential execution flow, as our tooling environment does not currently have the capability to compile or run proof-of-concept exploit code.
Suggested Remediation
When wrapping an external VkImage via VkImageImageSiblingVk, ANGLE cannot alter the physical allocation or creation flags of the underlying image. Therefore, ANGLE must not fallback to an incompatible format class during initImpl if the driver does not support the requested capabilities for the original format.
The initialization in VkImageImageSiblingVk::initImpl should be updated to ensure the resolved actualImageFormatID is strictly compatible with the format of the external VkImage. If the driver’s capabilities prevent utilizing the image’s original format, the method should return VK_ERROR_FORMAT_NOT_SUPPORTED or a similar failure code gracefully, rather than continuing with an invalid, mismatched configuration.
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.