High chrome Memory Corruption 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in ANGLE
DescriptionMemory corruption in ANGLE
ComponentANGLE
Sub-componentMetal
Bug ClassMemory Corruption
Tracker568895029
Fix commit64b22df8fc3b (angle/angle) +49/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
AtomicCommandBufferError
src/libANGLE/renderer/metal/mtl_command_buffer.h
modified

Files Changed

  • src/libANGLE/renderer/metal/BufferMtl.mm
  • src/libANGLE/renderer/metal/FrameBufferMtl.mm
  • src/libANGLE/renderer/metal/ContextMtl.h
  • src/libANGLE/renderer/metal/ContextMtl.mm
  • src/libANGLE/renderer/metal/mtl_command_buffer.mm
  • src/libANGLE/renderer/metal/mtl_command_buffer.h
From 64b22df8fc3b8aa194e738ad292ea8da14358f67 Mon Sep 17 00:00:00 2001
From: Le Hoang Quyen <[email protected]>
Date: Fri, 09 Oct 2026 01:42:05 +0800
Subject: [PATCH] Metal: Handle non-OOM command buffer errors

Previously, ContextMtl::checkCommandBufferError() only raised an error
for MTLCommandBufferErrorOutOfMemory and silently discarded all other
MTLCommandBufferError codes (such as Timeout, PageFault, NotPermitted,
and DeviceRemoved), while ContextMtl::getResetStatus() unconditionally
returned NoError.

- Prevent AtomicCommandBufferError::store() from overwriting non-OOM
  GPU fault errors before they are popped.
- Handle non-OOM errors in ContextMtl::checkCommandBufferError() by
  setting mContextLost and raising an internal error.
- Return UnknownContextReset from ContextMtl::getResetStatus() when
  mContextLost is set.
- Check command buffer errors after GPU synchronization in
  FramebufferMtl::readPixelsImpl() and BufferMtl::mapRange().

Bug: chromium:568895029
Change-Id: I0e296243291b1bd698d2bc56ba715cab1c3ee3fa
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8542590
Commit-Queue: Quyen Le <[email protected]>
Reviewed-by: Kenneth Russell <[email protected]>
---
diff --git a/src/libANGLE/renderer/metal/BufferMtl.mm b/src/libANGLE/renderer/metal/BufferMtl.mm
index ae1f151..7aff37f 100644
--- a/src/libANGLE/renderer/metal/BufferMtl.mm
+++ b/src/libANGLE/renderer/metal/BufferMtl.mm
@@ -246,17 +246,20 @@
     if (mapPtr)
     {
         ContextMtl *contextMtl = mtl::GetImpl(context);
+        uint8_t *ptr           = nullptr;
         if (mShadowCopy.size() == 0)
         {
-            *mapPtr = mBuffer
-                          ->mapWithOpt(contextMtl, (access & GL_MAP_WRITE_BIT) == 0,
-                                       access & GL_MAP_UNSYNCHRONIZED_BIT, offset, length)
-                          .data();
+            ptr = mBuffer
+                      ->mapWithOpt(contextMtl, (access & GL_MAP_WRITE_BIT) == 0,
+                                   access & GL_MAP_UNSYNCHRONIZED_BIT, offset, length)
+                      .data();
         }
         else
         {
-            *mapPtr = syncAndObtainShadowCopy(contextMtl, offset, length).data();
+            ptr = syncAndObtainShadowCopy(contextMtl, offset, length).data();
         }
+        ANGLE_TRY(contextMtl->checkCommandBufferError());
+        *mapPtr = ptr;
     }
 
     return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/metal/ContextMtl.h b/src/libANGLE/renderer/metal/ContextMtl.h
index af77f6e..2159fb2 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.h
+++ b/src/libANGLE/renderer/metal/ContextMtl.h
@@ -347,6 +347,7 @@
     void flushCommandBuffer(mtl::CommandBufferFinishOperation operation);
     void present(const gl::Context *context, id<CAMetalDrawable> presentationDrawable);
     angle::Result finishCommandBuffer();
+    angle::Result checkCommandBufferError();
 
     void addCommandBufferScheduledCallback(std::function<void()> callback);
 
@@ -531,8 +532,6 @@
 
     angle::Result startOcclusionQueryInRenderPass(QueryMtl *query, bool clearOldValue);
 
-    angle::Result checkCommandBufferError();
-
     // Dirty bits.
     enum DirtyBitType : size_t
     {
@@ -649,6 +648,8 @@
     ProvokingVertexHelper mProvokingVertexHelper;
 
     mtl::ContextDevice mContextDevice;
+
+    bool mContextLost = false;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/metal/ContextMtl.mm b/src/libANGLE/renderer/metal/ContextMtl.mm
index 53231b2..c485753 100644
--- a/src/libANGLE/renderer/metal/ContextMtl.mm
+++ b/src/libANGLE/renderer/metal/ContextMtl.mm
@@ -1060,6 +1060,10 @@
 // Device loss
 gl::GraphicsResetStatus ContextMtl::getResetStatus()
 {
+    if (mContextLost)
+    {
+        return gl::GraphicsResetStatus::UnknownContextReset;
+    }
     return gl::GraphicsResetStatus::NoError;
 }
 
@@ -2937,9 +2941,19 @@
 
 angle::Result ContextMtl::checkCommandBufferError()
 {
-    ANGLE_CHECK_GL_ALLOC(
-        this, mCmdBuffer.cmdQueue().popCmdBufferError() != MTLCommandBufferErrorOutOfMemory);
-    return angle::Result::Continue;
+    switch (mCmdBuffer.cmdQueue().popCmdBufferError())
+    {
+        case MTLCommandBufferErrorNone:
+            return angle::Result::Continue;
+        case MTLCommandBufferErrorOutOfMemory:
+            ANGLE_CHECK_GL_ALLOC(this, false);
+            return angle::Result::Stop;
+        default:
+            mContextLost = true;
+            ANGLE_CHECK(this, false, "Metal command buffer execution failed.",
+                        GL_INVALID_OPERATION);
+            return angle::Result::Stop;
+    }
 }
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/metal/FrameBufferMtl.mm b/src/libANGLE/renderer/metal/FrameBufferMtl.mm
index 79033ba..5489f53 100644
--- a/src/libANGLE/renderer/metal/FrameBufferMtl.mm
+++ b/src/libANGLE/renderer/metal/FrameBufferMtl.mm
@@ -1698,6 +1698,9 @@
 
         buffer->syncContent(contextMtl, contextMtl->getBlitCommandEncoder());
         const uint8_t *bufferData = buffer->mapReadOnly(contextMtl).data();
+        // Verify that the command buffer (including any pending clears/draws and the readback
+        // blit) executed without GPU faults before reading back the buffer contents.
+        ANGLE_TRY(contextMtl->checkCommandBufferError());
 
         angle::Result result = readPixelsCopyImpl(
             context, area, packPixelsParams, renderTarget,
@@ -1717,6 +1720,8 @@
     {
         contextMtl->flushCommandBuffer(mtl::WaitUntilFinished);
     }
+    // Verify that prior command buffers affecting this texture completed without GPU faults.
+    ANGLE_TRY(contextMtl->checkCommandBufferError());
 
     angle::MemoryBuffer readPixelRowBuffer;
     auto checkedRowPitch = angle::CheckedNumeric<size_t>(area.width) * readAngleFormat.pixelBytes;
diff --git a/src/libANGLE/renderer/metal/mtl_command_buffer.h b/src/libANGLE/renderer/metal/mtl_command_buffer.h
index 2221ba0..9da3d60 100644
--- a/src/libANGLE/renderer/metal/mtl_command_buffer.h
+++ b/src/libANGLE/renderer/metal/mtl_command_buffer.h
@@ -59,7 +59,7 @@
 class AtomicCommandBufferError : angle::NonCopyable
 {
   public:
-    void store(MTLCommandBufferError value) { mValue.store(value, std::memory_order_release); }
+    void store(MTLCommandBufferError value);
     MTLCommandBufferError pop()
     {
         return mValue.exchange(MTLCommandBufferErrorNone, std::memory_order_acq_rel);
diff --git a/src/libANGLE/renderer/metal/mtl_command_buffer.mm b/src/libANGLE/renderer/metal/mtl_command_buffer.mm
index 8f3861e..bd09d5c 100644
--- a/src/libANGLE/renderer/metal/mtl_command_buffer.mm
+++ b/src/libANGLE/renderer/metal/mtl_command_buffer.mm
@@ -481,6 +481,21 @@
     }
 }
 
+// AtomicCommandBufferError implementation
+void AtomicCommandBufferError::store(MTLCommandBufferError value)
+{
+    // Do not overwrite an existing non-OOM GPU fault error (e.g. Timeout, PageFault,
+    // DeviceRemoved) with a subsequent error (such as NotPermitted from rejected follow-up
+    // submissions or OutOfMemory) so the root-cause fault is preserved until popped.
+    MTLCommandBufferError prevError = mValue.load(std::memory_order_relaxed);
+    while (
+        (prevError == MTLCommandBufferErrorNone || prevError == MTLCommandBufferErrorOutOfMemory) &&
+        !mValue.compare_exchange_weak(prevError, value, std::memory_order_release,
+                                      std::memory_order_consume))
+    {
+    }
+}
+
 // CommandQueue implementation
 void CommandQueue::reset()
 {
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.