CVE-2024-23213
Overview
Impact:
Description: Processing web content may lead to arbitrary code execution
Researchers: Wangtaiyu of Zhongfu info
Key insight. The asynchronous EME grant continuation held the Document by raw reference (and ran its completion handler unconditionally), so processing a grant after the frame was torn down dereferenced a freed context; WeakPtr/context null-checks and a run-once handler restore safe lifetime.
Patch Details
diff --git a/Source/WebKit/CMakeLists.txt b/Source/WebKit/CMakeLists.txt
index 3b782a4b3d6f..bd39f0753235 100644
--- a/Source/WebKit/CMakeLists.txt
+++ b/Source/WebKit/CMakeLists.txt
@@ -40,6 +40,7 @@ set(WebKit_PRIVATE_INCLUDE_DIRECTORIES
"${WEBKIT_DIR}/Shared/CoreIPCSupport"
"${WEBKIT_DIR}/Shared/Databases"
"${WEBKIT_DIR}/Shared/Databases/IndexedDB"
+ "${WEBKIT_DIR}/Shared/Extensions"
"${WEBKIT_DIR}/Shared/FileAPI"
"${WEBKIT_DIR}/Shared/Gamepad"
"${WEBKIT_DIR}/Shared/Notifications"
Background
Advisory: Apple Security Advisory
| Attribute | Value |
|---|---|
| CVE | CVE-2024-23213 |
| Bugzilla | 266619 |
| Component | WebCore |
| Bug Class | UAF |
| Severity | critical |
| Commit | 77a6809901292311… |
| Advisory | Apple Advisory |
Terminology
- Encrypted Media Extensions (EME) — The DRM API (navigator.requestMediaKeySystemAccess) that negotiates a media key system, gated by an asynchronous user/UI-process permission grant.
- ScriptExecutionContext / Document lifetime — A frame’s Document/context can be destroyed by navigation or detach; async continuations must not dereference it after teardown.
- WeakPtr vs raw capture — Capturing a Document by WeakPtr lets a callback detect destruction and bail; a raw reference dereferences freed memory.
- queueTaskKeepingObjectAlive / std::exchange — Keeps the request object alive across the queued task and ensures the completion handler runs at most once, preventing use-after-free and double-invoke.
Root Cause Analysis
This fixes a document/execution-context lifetime bug (a use-after-free, rated critical / arbitrary code execution) in the Encrypted Media Extensions requestMediaKeySystemAccess permission flow. The EME grant is asynchronous and user-gated: NavigatorEME::requestMediaKeySystemAccess creates a MediaKeySystemRequest and installs an ‘allow’ callback; when the user/UI process approves, the WebProcess receives the MediaKeySystemWasGranted IPC and calls MediaKeySystemRequest::allow(), which resolves the JS promise and continues the algorithm on the document. Pre-patch, two lifetime assumptions were unsafe. First, NavigatorEME’s setAllowCallback lambda captured the Document by RAW reference (&document) and later did document.postTask(…); between the async request and the grant, the document/frame can be navigated away or detached and destroyed, so the callback dereferenced a freed Document. Second, MediaKeySystemRequest::allow() captured raw ’this’ and unconditionally moved and invoked m_allowCompletionHandler (and a second handler), with no check that the ScriptExecutionContext still existed and no guard against the handler being run twice/after teardown. The result is a use-after-free of the Document/ScriptExecutionContext (or the request state) when the EME permission grant is processed after the associated context has gone away — a strong, attacker-timable primitive consistent with the advisory’s arbitrary-code-execution rating. The fix makes the callback capture weakDocument = WeakPtr { document } and, on invocation, upgrade to RefPtr and reject with InvalidStateError if the document is gone (instead of touching a raw &document); the posted task now derives the document from the passed ScriptExecutionContext via downcast. allow() now early-returns if !scriptExecutionContext(), uses queueTaskKeepingObjectAlive to keep the request alive, and runs the completion handler at most once via std::exchange(m_allowCompletionHandler, {}). Supporting this, the MediaKeySystemWasGranted IPC is changed from a sync/async-reply message (-> ()) to a one-way send, and the CompletionHandler plumbing is removed from WebPage / MediaKeySystemPermissionRequestManager, eliminating the reply-handler lifetime chain that tied a UI-process reply to WebProcess object lifetime. The restored invariant is that the EME grant continuation never dereferences a Document/context/request that may have been destroyed between the (async, user-gated) permission decision and the continuation running.
Attack Path
1. Request media key system access
Web content calls navigator.requestMediaKeySystemAccess(), creating a MediaKeySystemRequest and prompting for permission (async, user-gated).
2. Tear down the context
Before the grant is processed, the page navigates away or detaches the frame so the Document/ScriptExecutionContext is destroyed.
3. Deliver the grant
The UI process approves and sends MediaKeySystemWasGranted; the WebProcess runs allow()/the allow-callback capturing the now-freed document by raw reference.
4. Use-after-free
The continuation dereferences the freed Document/context (and completion handler), a controllable UAF.
5. Escalate to code execution
Reclaim the freed allocation with controlled data to turn the UAF into arbitrary read/write and code execution in WebContent.
Impact Assessment
A critical use-after-free in the WebContent process on the EME permission-grant path, reachable by racing frame/navigation teardown against an approved requestMediaKeySystemAccess grant. Dereferencing a freed Document/ScriptExecutionContext is a strong, attacker-timable primitive; consistent with the advisory’s arbitrary-code-execution rating, it can be groomed into memory disclosure/corruption and full WebContent code execution.
Audit Directions
Same module: EME callbacks
Audit MediaKeySystemRequest/NavigatorEME and MediaKeySystemPermissionRequestManager for other raw Document/context captures or completion handlers invoked without a scriptExecutionContext() / WeakPtr guard.
Async permission grants
Review other user-gated async grant flows (getUserMedia, geolocation, notifications, WebXR) for callbacks capturing Document/frame by raw pointer/reference across the UI-process round trip.
IPC reply-handler lifetimes
Grep for sendWithAsyncReply grant/permission messages whose reply handler assumes WebProcess objects survive; prefer one-way sends plus keep-alive tasks.
Changed Functions
| Function | File | Change | Note |
|---|---|---|---|
MediaKeySystemRequest::allow |
Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.cpp |
modified | Drops the CompletionHandler parameter; early-returns if !scriptExecutionContext(); uses queueTaskKeepingObjectAlive and runs the completion handler at most once via std::exchange(m_allowCompletionHandler, {}). |
NavigatorEME::requestMediaKeySystemAccess (allow callback) |
Source/WebCore/Modules/encryptedmedia/NavigatorEME.cpp |
modified | Captures the document as a WeakPtr; on invocation upgrades to RefPtr and rejects with InvalidStateError if gone, and derives the Document from the posted task’s ScriptExecutionContext instead of a raw &document capture. |
MediaKeySystemPermissionRequestManager::mediaKeySystemWasGranted |
Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cpp |
modified | Drops the CompletionHandler; simply takes the pending request and calls request->allow(). |
MediaKeySystemPermissionRequestManagerProxy::grantRequest |
Source/WebKit/UIProcess/MediaKeySystemPermissionRequestManagerProxy.cpp |
modified | Sends MediaKeySystemWasGranted as a one-way message (m_page.send) instead of sendWithAsyncReply, removing the reply-handler lifetime chain. |
WebPage::mediaKeySystemWasGranted + WebPage.messages.in |
Source/WebKit/WebProcess/WebPage/WebPage.cpp |
modified | Removes the CompletionHandler and changes MediaKeySystemWasGranted from a reply message (-> ()) to a plain asynchronous message. |
WebMediaKeySystemClient::requestMediaKeySystem |
Source/WebKitLegacy/mac/WebCoreSupport/WebMediaKeySystemClient.mm |
modified | Updates the legacy caller to the no-argument allow(). |
Files Changed
Source Files
Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.cppSource/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.hSource/WebCore/Modules/encryptedmedia/NavigatorEME.cppSource/WebKit/UIProcess/MediaKeySystemPermissionRequestManagerProxy.cppSource/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cppSource/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.hSource/WebKit/WebProcess/WebPage/WebPage.cppSource/WebKit/WebProcess/WebPage/WebPage.hSource/WebKit/WebProcess/WebPage/WebPage.messages.inSource/WebKitLegacy/mac/WebCoreSupport/WebMediaKeySystemClient.mm