Critical CVSS 8.8 webkit UAF 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing web content may lead to arbitrary code execution
ComponentWebCore
Sub-componentWebKit WebProcess
Bug ClassUAF
Tracker266619
Fix commit77a680990129 (WebKit/WebKit) +27/-20
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedWangtaiyu of Zhongfu info
Disclosed2024-01-22

Background

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.

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.

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.

Changed Functions

FunctionChangeNotes
if
Source/WebCore/Modules/encryptedmedia/NavigatorEME.cpp
modified
if
Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cpp
modified

Files Changed

  • Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.cpp
  • Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.h
  • Source/WebCore/Modules/encryptedmedia/NavigatorEME.cpp
  • Source/WebKit/UIProcess/MediaKeySystemPermissionRequestManagerProxy.cpp
  • Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cpp
  • Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.h
  • Source/WebKit/WebProcess/WebPage/WebPage.cpp
  • Source/WebKit/WebProcess/WebPage/WebPage.h
  • Source/WebKit/WebProcess/WebPage/WebPage.messages.in
  • Source/WebKitLegacy/mac/WebCoreSupport/WebMediaKeySystemClient.mm

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.
diff --git a/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.cpp b/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.cpp
index 61d2706b1d88..a3d0de621971 100644
--- a/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.cpp
+++ b/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.cpp
@@ -86,12 +86,14 @@ void MediaKeySystemRequest::start()
     controller->requestMediaKeySystem(*this);
 }
 
-void MediaKeySystemRequest::allow(CompletionHandler<void()>&& completionHandler)
+void MediaKeySystemRequest::allow()
 {
-    queueTaskKeepingObjectAlive(*this, TaskSource::UserInteraction, [this, handler = WTFMove(completionHandler)]() mutable {
-        auto completionHandler = WTFMove(m_allowCompletionHandler);
-        completionHandler(WTFMove(m_promise));
-        handler();
+    if (!scriptExecutionContext())
+        return;
+
+    queueTaskKeepingObjectAlive(*this, TaskSource::UserInteraction, [this] {
+        if (auto allowCompletionHandler = std::exchange(m_allowCompletionHandler, { }))
+            allowCompletionHandler(WTFMove(m_promise));
     });
 }
 
diff --git a/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.h b/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.h
index ca43f60542c3..a3247ff6a5ca 100644
--- a/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.h
+++ b/Source/WebCore/Modules/encryptedmedia/MediaKeySystemRequest.h
@@ -51,7 +51,7 @@ public:
     MediaKeySystemRequestIdentifier identifier() const { return m_identifier; }
     WEBCORE_EXPORT void start();
 
-    WEBCORE_EXPORT void allow(CompletionHandler<void()>&&);
+    WEBCORE_EXPORT void allow();
     WEBCORE_EXPORT void deny(const String& errorMessage = emptyString());
 
     WEBCORE_EXPORT SecurityOrigin* topLevelDocumentOrigin() const;
diff --git a/Source/WebCore/Modules/encryptedmedia/NavigatorEME.cpp b/Source/WebCore/Modules/encryptedmedia/NavigatorEME.cpp
index 01d686e304a7..719bbf6c6517 100644
--- a/Source/WebCore/Modules/encryptedmedia/NavigatorEME.cpp
+++ b/Source/WebCore/Modules/encryptedmedia/NavigatorEME.cpp
@@ -104,8 +104,14 @@ void NavigatorEME::requestMediaKeySystemAccess(Navigator& navigator, Document& d
     }
 
     auto request = MediaKeySystemRequest::create(document, keySystem, WTFMove(promise));
-    request->setAllowCallback([keySystem, supportedConfigurations = WTFMove(supportedConfigurations), &document, logger = WTFMove(logger), identifier = WTFMove(identifier)](Ref<DeferredPromise>&& promise) mutable {
-        document.postTask([promise = WTFMove(promise), &document, keySystem, logger = WTFMove(logger), identifier = WTFMove(identifier), supportedConfigurations = WTFMove(supportedConfigurations)] (ScriptExecutionContext&) mutable {
+    request->setAllowCallback([keySystem, supportedConfigurations = WTFMove(supportedConfigurations), weakDocument = WeakPtr { document }, logger = WTFMove(logger), identifier = WTFMove(identifier)](Ref<DeferredPromise>&& promise) mutable {
+        RefPtr document = weakDocument.get();
+        if (!document) {
+            promise->reject(ExceptionCode::InvalidStateError);
+            return;
+        }
+
+        document->postTask([promise = WTFMove(promise), keySystem, logger = WTFMove(logger), identifier = WTFMove(identifier), supportedConfigurations = WTFMove(supportedConfigurations)] (ScriptExecutionContext& context) mutable {
             // 3. Let document be the calling context's Document.
             // 4. Let origin be the origin of document.
             // 5. Let promise be a new promise.
@@ -119,6 +125,7 @@ void NavigatorEME::requestMediaKeySystemAccess(Navigator& navigator, Document& d
             }
 
             // 6.2. Let implementation be the implementation of keySystem.
+            auto& document = downcast<Document>(context);
             auto implementation = CDM::create(document, keySystem);
             tryNextSupportedConfiguration(document, WTFMove(implementation), WTFMove(supportedConfigurations), WTFMove(promise), WTFMove(logger), WTFMove(identifier));
         });
diff --git a/Source/WebKit/UIProcess/MediaKeySystemPermissionRequestManagerProxy.cpp b/Source/WebKit/UIProcess/MediaKeySystemPermissionRequestManagerProxy.cpp
index f38b57167305..fbb78b40d412 100644
--- a/Source/WebKit/UIProcess/MediaKeySystemPermissionRequestManagerProxy.cpp
+++ b/Source/WebKit/UIProcess/MediaKeySystemPermissionRequestManagerProxy.cpp
@@ -105,7 +105,7 @@ void MediaKeySystemPermissionRequestManagerProxy::grantRequest(MediaKeySystemPer
 #if ENABLE(ENCRYPTED_MEDIA)
     ALWAYS_LOG(LOGIDENTIFIER, request.mediaKeySystemID().toUInt64(), ", keySystem: ", request.keySystem());
 
-    m_page.sendWithAsyncReply(Messages::WebPage::MediaKeySystemWasGranted { request.mediaKeySystemID() }, [] { });
+    m_page.send(Messages::WebPage::MediaKeySystemWasGranted { request.mediaKeySystemID() });
 #else
     UNUSED_PARAM(request);
 #endif
diff --git a/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cpp b/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cpp
index 7512ba9b4835..1af0b7bd97f0 100644
--- a/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cpp
+++ b/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.cpp
@@ -122,15 +122,13 @@ void MediaKeySystemPermissionRequestManager::mediaCanStart(Document& document)
         sendMediaKeySystemRequest(pendingRequest);
 }
 
-void MediaKeySystemPermissionRequestManager::mediaKeySystemWasGranted(MediaKeySystemRequestIdentifier requestID, CompletionHandler<void()>&& completionHandler)
+void MediaKeySystemPermissionRequestManager::mediaKeySystemWasGranted(MediaKeySystemRequestIdentifier requestID)
 {
     auto request = m_ongoingMediaKeySystemRequests.take(requestID);
-    if (!request) {
-        completionHandler();
+    if (!request)
         return;
-    }
 
-    request->allow(WTFMove(completionHandler));
+    request->allow();
 }
 
 void MediaKeySystemPermissionRequestManager::mediaKeySystemWasDenied(MediaKeySystemRequestIdentifier requestID, String&& message)
diff --git a/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.h b/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.h
index c4f42d8a9e36..ebfc17f5051a 100644
--- a/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.h
+++ b/Source/WebKit/WebProcess/EncryptedMedia/MediaKeySystemPermissionRequestManager.h
@@ -47,7 +47,7 @@ public:
 
     void startMediaKeySystemRequest(WebCore::MediaKeySystemRequest&);
     void cancelMediaKeySystemRequest(WebCore::MediaKeySystemRequest&);
-    void mediaKeySystemWasGranted(WebCore::MediaKeySystemRequestIdentifier, CompletionHandler<void()>&&);
+    void mediaKeySystemWasGranted(WebCore::MediaKeySystemRequestIdentifier);
     void mediaKeySystemWasDenied(WebCore::MediaKeySystemRequestIdentifier, String&&);
 
 private:
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
index db36d63a7b92..dea1e2362020 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
@@ -5560,9 +5560,9 @@ void WebPage::setMockCaptureDevicesInterrupted(bool isCameraInterrupted, bool is
 #endif // ENABLE(MEDIA_STREAM)
 
 #if ENABLE(ENCRYPTED_MEDIA)
-void WebPage::mediaKeySystemWasGranted(MediaKeySystemRequestIdentifier mediaKeySystemID, CompletionHandler<void()>&& completionHandler)
+void WebPage::mediaKeySystemWasGranted(MediaKeySystemRequestIdentifier mediaKeySystemID)
 {
-    m_mediaKeySystemPermissionRequestManager->mediaKeySystemWasGranted(mediaKeySystemID, WTFMove(completionHandler));
+    m_mediaKeySystemPermissionRequestManager->mediaKeySystemWasGranted(mediaKeySystemID);
 }
 
 void WebPage::mediaKeySystemWasDenied(MediaKeySystemRequestIdentifier mediaKeySystemID, String&& message)
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h
index f9a2937a4609..9088069682f4 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.h
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.h
@@ -2005,7 +2005,7 @@ private:
 #endif
 
 #if ENABLE(ENCRYPTED_MEDIA)
-    void mediaKeySystemWasGranted(WebCore::MediaKeySystemRequestIdentifier, CompletionHandler<void()>&&);
+    void mediaKeySystemWasGranted(WebCore::MediaKeySystemRequestIdentifier);
     void mediaKeySystemWasDenied(WebCore::MediaKeySystemRequestIdentifier, String&& message);
 #endif
 
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
index 09ba66333885..98027f0b171e 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
@@ -430,7 +430,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType
 #endif
 
 #if ENABLE(ENCRYPTED_MEDIA)
-    MediaKeySystemWasGranted(WebCore::MediaKeySystemRequestIdentifier mediaKeySystemID) -> ()
+    MediaKeySystemWasGranted(WebCore::MediaKeySystemRequestIdentifier mediaKeySystemID)
     MediaKeySystemWasDenied(WebCore::MediaKeySystemRequestIdentifier mediaKeySystemID, String message)
 #endif
 
diff --git a/Source/WebKitLegacy/mac/WebCoreSupport/WebMediaKeySystemClient.mm b/Source/WebKitLegacy/mac/WebCoreSupport/WebMediaKeySystemClient.mm
index cb611e9e8f15..95203caf1c6c 100644
--- a/Source/WebKitLegacy/mac/WebCoreSupport/WebMediaKeySystemClient.mm
+++ b/Source/WebKitLegacy/mac/WebCoreSupport/WebMediaKeySystemClient.mm
@@ -43,7 +43,7 @@ void WebMediaKeySystemClient::requestMediaKeySystem(MediaKeySystemRequest& reque
 {
     BEGIN_BLOCK_OBJC_EXCEPTIONS
 
-    request.allow([] { });
+    request.allow();
 
     END_BLOCK_OBJC_EXCEPTIONS
 }
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.