CVE-2026-11654
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/capture/video/apple/video_capture_device_avfoundation.mm |
modified |
Files Changed
media/capture/video/apple/video_capture_device_avfoundation.mm
Patch
From 81609cb7bca0b0f664a4d017c676c0a0123dea9e Mon Sep 17 00:00:00 2001 From: Guido Urdaneta <[email protected]> Date: Wed, 27 May 2026 07:57:28 -0700 Subject: [PATCH] [MediaCapture] Use Obj-C weak pointers for the takePhoto operation Previously it was using WeakPtrFactory, but GetWeakPtr was being called cross-thread in captureOutput, which is not allowed as WeakPtrs are not thread safe. This CL also updates the logic to avoid relying on InvalidateWeakPtr to cancel the stopPhotoOutput operation when the number of pending takePhoto operations changes from 0 to 1. Now stopPhotoOutput exits early if there are pending takePhoto operations. Fixed: 513362710 Change-Id: Id7e3e4fcd68ae8f9a51297c5612cec1f637639c6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7879144 Auto-Submit: Guido Urdaneta <[email protected]> Reviewed-by: Tony Herre <[email protected]> Reviewed-by: Avi Drissman <[email protected]> Commit-Queue: Tony Herre <[email protected]> Cr-Commit-Position: refs/heads/main@{#1636960} --- diff --git a/media/capture/video/apple/video_capture_device_avfoundation.mm b/media/capture/video/apple/video_capture_device_avfoundation.mm index f748a6e5..8a0ef2ff 100644 --- a/media/capture/video/apple/video_capture_device_avfoundation.mm +++ b/media/capture/video/apple/video_capture_device_avfoundation.mm @@ -269,7 +269,6 @@ // pending until we're ready to take another photo, which involves a PostTask // back to the main thread after the photo was taken. size_t _pendingTakePhotos; - SelfHolder _weakPtrHolderForTakePhoto; // For testing. base::RepeatingCallback<void()> _onPhotoOutputStopped; @@ -323,7 +322,6 @@ _useGPUMemoryBuffer = true; _capturedFirstFrame = false; _weakPtrHolderForStallCheck.the_self = self; - _weakPtrHolderForTakePhoto.the_self = self; [self setFrameReceiver:frameReceiver]; _captureSession = [[AVCaptureSession alloc] init]; _sampleBufferTransformer = media::SampleBufferTransformer::Create(); @@ -628,11 +626,6 @@ // the next takePhotoInternal(), so there is nothing more to do here. return; } - // `_pendingTakePhotos` just went from 0 to 1. In case the 60 second delayed - // task to perform stopPhotoOutput() is in-flight, invalidate weak ptrs to - // cancel any such operation. - _weakPtrHolderForTakePhoto.weak_ptr_factory.InvalidateWeakPtrs(); - // Ready to take a photo immediately? // Thread-safe because `_photoOutput` is only modified on the main thread. if (_photoOutput) { @@ -664,16 +657,17 @@ } // A delay is needed before taking the photo or else the photo may be dark. // 2 seconds was enough in manual testing; we delay by 3 for good measure. + __weak VideoCaptureDeviceAVFoundation* weakSelf = self; _mainThreadTaskRunner->PostDelayedTask( FROM_HERE, base::BindOnce( - [](base::WeakPtr<SelfHolder> weakSelf) { - if (!weakSelf.get()) { - return; + [](VideoCaptureDeviceAVFoundation* __weak wSelf) { + VideoCaptureDeviceAVFoundation* sSelf = wSelf; + if (sSelf) { + [sSelf takePhotoInternal]; } - [weakSelf.get()->the_self takePhotoInternal]; }, - _weakPtrHolderForTakePhoto.weak_ptr_factory.GetWeakPtr()), + weakSelf), base::Seconds(3)); } @@ -736,19 +730,23 @@ } // Whether we succeeded or failed, we need to resolve the pending // takePhoto() operation. + __weak VideoCaptureDeviceAVFoundation* weakSelf = self; _mainThreadTaskRunner->PostTask( FROM_HERE, base::BindOnce( - [](base::WeakPtr<SelfHolder> weakSelf) { - if (!weakSelf.get()) { - return; + [](VideoCaptureDeviceAVFoundation* __weak wSelf) { + VideoCaptureDeviceAVFoundation* sSelf = wSelf; + if (sSelf) { + [sSelf takePhotoResolved]; } - [weakSelf.get()->the_self takePhotoResolved]; }, - _weakPtrHolderForTakePhoto.weak_ptr_factory.GetWeakPtr())); + weakSelf)); } - (void)takePhotoResolved { DCHECK(_mainThreadTaskRunner->BelongsToCurrentThread()); + if (_pendingTakePhotos == 0) { + return; + } --_pendingTakePhotos; if (_pendingTakePhotos > 0u) { // Take another photo. @@ -758,28 +756,31 @@ // All pending takePhoto()s have completed. If no more photos are taken // within 60 seconds, stop photo output to avoid expensive MJPEG conversions // going forward. + __weak VideoCaptureDeviceAVFoundation* weakSelf = self; _mainThreadTaskRunner->PostDelayedTask( FROM_HERE, base::BindOnce( - [](base::WeakPtr<SelfHolder> weakSelf) { - if (!weakSelf.get()) { - return; + [](VideoCaptureDeviceAVFoundation* __weak wSelf) { + VideoCaptureDeviceAVFoundation* sSelf = wSelf; + if (sSelf) { + [sSelf stopPhotoOutput]; } - [weakSelf.get()->the_self stopPhotoOutput]; }, - _weakPtrHolderForTakePhoto.weak_ptr_factory.GetWeakPtr()), + weakSelf), base::Seconds(kTimeToWaitBeforeStoppingPhotoOutputInSeconds)); } - (void)stopPhotoOutput { DCHECK(_mainThreadTaskRunner->BelongsToCurrentThread()); + if (_pendingTakePhotos > 0u) { + return; + } // Already stopped? // Thread-safe because `_photoOutput` is only modified on the main thread. if (!_photoOutput) { return; } // Cancel all in-flight operations. - _weakPtrHolderForTakePhoto.weak_ptr_factory.InvalidateWeakPtrs(); { base::AutoLock lock(_lock); if (_captureSession) {
Original Bug Report
Race Condition and UAF in VideoCaptureDeviceAVFoundation on macOS and iOS
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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A race condition in VideoCaptureDeviceAVFoundation allows concurrent access to a base::WeakPtrFactory from the main thread and a background AVFoundation thread. This can result in a Use-After-Free (UAF) of internal memory safety structures, potentially leading to arbitrary code execution in privileged processes.
Affected files:
media/capture/video/apple/video_capture_device_avfoundation.mm
Estimated timestamp from git blame: 2023-01-04
Potential Vulnerability: Concurrent WeakPtrFactory Access and Use-After-Free
A potential race condition exists in media/capture/video/apple/video_capture_device_avfoundation.mm where _weakPtrHolderForTakePhoto.weak_ptr_factory is accessed concurrently from the main thread (Device Task Runner) and an internal AVFoundation dispatch queue.
Root Cause
In VideoCaptureDeviceAVFoundation, the WeakPtrFactory is invalidated on the main thread in stopPhotoOutput (at line 782) without holding the class lock _lock. Simultaneously, the AVFoundation background thread may call GetWeakPtr() within the captureOutput:didFinishProcessingPhoto:error: delegate callback (at line 747) while holding _lock.
Since base::WeakPtrFactory is not thread-safe and must be used on a single sequence, this concurrent access violates Chromium’s memory safety invariants.
Memory Corruption Mechanics
The race occurs within base::internal::WeakReferenceOwner. When InvalidateWeakPtrs() is called on Thread A (Main), it replaces the internal scoped_refptr<Flag> with a new one. Concurrently, GetWeakPtr() on Thread B (Background) reads the raw pointer of the old Flag to increment its reference count (AddRef).
If Thread A releases the old Flag and it is deallocated before Thread B performs the increment, a Use-After-Free write (atomic increment) occurs on the freed memory. This can result in a WeakPtr that erroneously appears valid even after the factory has been destroyed.
Impact
This issue is potentially reachable from web content via the ImageCapture.takePhoto() and MediaStreamTrack.stop() APIs, provided camera permissions are granted.
The impacted process is highly privileged:
- macOS: The Video Capture service runs in a Utility process with
kNoSandbox(equivalent to browser privileges). - iOS: The service runs directly in the Browser process.
Since internal pointers in scoped_refptr and WeakPtr are marked with RAW_PTR_EXCLUSION, they are not protected by MiraclePtr, increasing the likelihood of successful exploitation for remote code execution.
Suggested Potential Steps to Reproduce
- Grant camera permissions to a malicious origin.
- Call
ImageCapture.takePhoto()to initiate a capture operation. - Immediately call
MediaStreamTrack.stop()to triggerstopCaptureandInvalidateWeakPtrs()on the main thread. - If the camera callback arrives exactly during the invalidation, the race may trigger the UAF on the
WeakReference::Flag. - If successful, a corrupted task is posted to the main thread that may dereference a dangling pointer to the
VideoCaptureDeviceAVFoundationobject or its internalSelfHolderstruct.
Suggested Fix
Ensure that all calls to InvalidateWeakPtrs() on _weakPtrHolderForTakePhoto.weak_ptr_factory are performed while holding _lock, or ensure that GetWeakPtr() is only ever called from the main thread. A common pattern is to create the WeakPtr on the main thread when initiating the request and passing it to the background callback context.
Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e
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.