Chrome · ANGLE › Metal
CVE-2026-118322
Memory Corruption in ANGLE (Metal)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
AtomicCommandBufferErrorsrc/libANGLE/renderer/metal/mtl_command_buffer.h |
modified |
Files Changed
src/libANGLE/renderer/metal/BufferMtl.mmsrc/libANGLE/renderer/metal/FrameBufferMtl.mmsrc/libANGLE/renderer/metal/ContextMtl.hsrc/libANGLE/renderer/metal/ContextMtl.mmsrc/libANGLE/renderer/metal/mtl_command_buffer.mmsrc/libANGLE/renderer/metal/mtl_command_buffer.h
Patch
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.
References
On This Page