Medium CVSS 7.5 webkit UAF 🔧 Commit mapped

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing web content may lead to a denial-of-service
ComponentWebCore Platform/Audio
Bug ClassUAF
Tracker275117
Fix commite73dfba967ee (WebKit/WebKit) +19/-4
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CISA KEVNot listed
Creditedajajfxhj
Disclosed2024-07-29

Background

PlatformMediaSessionManager
WebCore singleton-like coordinator that tracks media sessions and schedules audio-session and now-playing state updates on the main thread.
callOnMainThread
A WTF helper that queues a lambda to run later on the main run loop; captured raw pointers are not lifetime-managed and can dangle if their target is freed first.
TaskCancellationGroup / CancellableTask
A WTF mechanism that associates queued tasks with a group so they can all be cancelled at once (e.g. in a destructor), turning pending tasks into no-ops.
Use-after-free (UAF)
A memory-safety error where memory is accessed after it has been freed, here via a stale captured ’this’ in a deferred task.
Deferred-task lifetime race
A bug pattern where an object schedules asynchronous work capturing itself and is destroyed before the work runs, leaving the callback holding a dangling reference.

Root Cause Analysis

PlatformMediaSessionManager (and its Cocoa subclass MediaSessionManagerCocoa) schedules deferred work on the main thread using callOnMainThread with lambdas that capture the raw ’this’ pointer (or a raw session pointer) — for example in sessionCanProduceAudioChanged, scheduleUpdateSessionState, MediaSessionManagerCocoa::scheduleSessionStatusUpdate, and the two callOnMainThread blocks in sessionWillEndPlayback. The invariant these captures assume is that the manager (or session) still exists when the queued task runs. That invariant can be violated: the task is posted, then the PlatformMediaSessionManager is destroyed before the run loop dispatches the task, so the lambda executes and dereferences a freed ’this’ — a classic use-after-free on a dangling captured pointer. Because these updates are driven by media/audio session state changes that web content can provoke and tear down, the lifetime race is web-reachable.

The fix introduces a TaskCancellationGroup member m_taskGroup and a helper enqueueTaskOnMainThread that wraps each task in a WTF CancellableTask bound to that group; all the raw-’this’-capturing callOnMainThread call sites are converted to enqueueTaskOnMainThread. A newly added ~PlatformMediaSessionManager destructor calls m_taskGroup.cancel(), which invalidates any still-pending CancellableTasks so they become no-ops instead of running against freed memory. The header changes add the CancellableTask include, declare the out-of-line destructor (replacing ‘= default’), declare enqueueTaskOnMainThread, and add the TaskCancellationGroup member. The net effect: tasks that outlive the manager are cancelled at destruction, restoring the ‘object alive when task runs’ invariant. Note the sessionWillEndPlayback conversion also wraps a task capturing a WeakPtr session; while WeakPtr already guards that particular deref, routing it through the cancellation group makes cancellation uniform. The precise object whose destruction triggers the freed access is not shown being deleted in the diff, so the exact teardown ordering is inferred from the fix shape (a cancellation-on-destruction pattern), which is the standard remedy for pending-task UAF.

Key insight
Deferred main-thread tasks captured a raw ’this’ with no tie to the manager’s lifetime, so destroying the manager before the task ran caused a use-after-free; the fix routes every such task through a TaskCancellationGroup cancelled in the destructor, restoring the object-alive-when-task-runs invariant.

Attack Path

  1. Start media producing state changes Web content creates media elements / audio sessions (e.g. WebAudio or <video>) that cause the PlatformMediaSessionManager to schedule session-state or now-playing updates via callOnMainThread capturing raw this.
  2. Queue a deferred task A state change (session can produce audio changed, session will end playback, status update) posts a main-thread task that holds a raw pointer to the manager or session.
  3. Destroy the manager before the task runs Tear down the associated page/document/media context so the PlatformMediaSessionManager (or session) is freed while the posted task is still pending in the run loop queue.
  4. Task dispatches against freed memory The run loop later executes the lambda, dereferencing the dangling this/session pointer (maybeActivateAudioSession/updateSessionState/updateNowPlayingInfo), a use-after-free.
  5. Observe the crash The stale access corrupts or reads freed heap, producing the unexpected process crash / denial-of-service described in the CVE.

Impact Assessment

The primitive is a use-after-free on a heap object (the media session manager or a session) accessed from a deferred main-thread task; the fix’s cancellation-on-destruction shape confirms a pending-task lifetime race. The CVE frames the realistic outcome as a denial-of-service / unexpected crash rather than a demonstrated controlled write, but UAF of a C++ object with virtual/state methods is in principle groomable toward more, though the diff shows nothing enabling that and such escalation would be inference. It executes in the WebContent (renderer) process where media sessions live, so impact is confined to that sandboxed process. Severity medium, consistent with a reliably reachable crash but no shown path to code execution.

Changed Functions

FunctionChangeNotes
PlatformMediaSessionManager::~PlatformMediaSessionManager
Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
added New destructor that calls m_taskGroup.cancel(), neutralizing any pending main-thread tasks so they cannot run after the manager is freed.
PlatformMediaSessionManager::enqueueTaskOnMainThread
Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
added Helper that wraps a task in CancellableTask(m_taskGroup, ...) before callOnMainThread, tying its lifetime to the cancellation group.
PlatformMediaSessionManager::sessionCanProduceAudioChanged
Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
modified Switches its callOnMainThread([this]{...}) to enqueueTaskOnMainThread so the deferred maybeActivateAudioSession/updateSessionState is cancellable.
PlatformMediaSessionManager::scheduleUpdateSessionState
Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
modified Converts its raw-this callOnMainThread to enqueueTaskOnMainThread.
MediaSessionManagerCocoa::scheduleSessionStatusUpdate
Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm
modified Converts its raw-this callOnMainThread (setSupportsSeeking/updateNowPlayingInfo) to enqueueTaskOnMainThread.
MediaSessionManagerCocoa::sessionWillEndPlayback
Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm
modified Converts both callOnMainThread blocks (weakSession updateMediaUsageIfChanged and raw-this updateNowPlayingInfo) to enqueueTaskOnMainThread for uniform cancellation.
PlatformMediaSessionManager (class declaration)
Source/WebCore/platform/audio/PlatformMediaSessionManager.h
modified Adds CancellableTask.h include, out-of-line ~ declaration, enqueueTaskOnMainThread declaration, and the TaskCancellationGroup m_taskGroup member.

Files Changed

  • Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
  • Source/WebCore/platform/audio/PlatformMediaSessionManager.h
  • Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm

Audit Directions

  • Remaining raw-this callOnMainThread in these files
    Verify no callOnMainThread capturing raw this or a raw session pointer survived the conversion; grep PlatformMediaSessionManager.cpp and MediaSessionManagerCocoa.mm for ‘callOnMainThread(’ and ‘[this’.
  • Other PlatformMediaSessionManager subclasses
    Check platform-specific managers (GLib/GStreamer, remote, Mac AV variants) for the same deferred-update pattern that now needs enqueueTaskOnMainThread; grep across platform/audio for subclasses overriding scheduleSessionStatusUpdate/updateNowPlayingInfo.
  • Objects that schedule work capturing this without cancellation
    Search WebCore media/audio classes for asynchronous dispatch capturing raw this without a WeakPtr or TaskCancellationGroup; grep for ‘callOnMainThread([this’ and ‘RunLoop::main().dispatch([this’.
  • Destructors missing task cancellation
    Find classes owning a TaskCancellationGroup or posting cancellable/deferred tasks whose destructor does not cancel them; grep for ‘TaskCancellationGroup’ members and confirm each has a ‘~…{ m_taskGroup.cancel(); }’ equivalent.
diff --git a/Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp b/Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
index 81dc8e026141..999b08a1e845 100644
--- a/Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
+++ b/Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
@@ -117,6 +117,11 @@ PlatformMediaSessionManager::PlatformMediaSessionManager()
 {
 }
 
+PlatformMediaSessionManager::~PlatformMediaSessionManager()
+{
+    m_taskGroup.cancel();
+}
+
 static inline unsigned indexFromMediaType(PlatformMediaSession::MediaType type)
 {
     return static_cast<unsigned>(type);
@@ -492,7 +497,7 @@ void PlatformMediaSessionManager::sessionCanProduceAudioChanged()
         return;
 
     m_alreadyScheduledSessionStatedUpdate = true;
-    callOnMainThread([this] {
+    enqueueTaskOnMainThread([this] {
         m_alreadyScheduledSessionStatedUpdate = false;
         maybeActivateAudioSession();
         updateSessionState();
@@ -656,7 +661,7 @@ void PlatformMediaSessionManager::scheduleUpdateSessionState()
         return;
 
     m_hasScheduledSessionStateUpdate = true;
-    callOnMainThread([this] {
+    enqueueTaskOnMainThread([this] {
         updateSessionState();
         m_hasScheduledSessionStateUpdate = false;
     });
@@ -903,6 +908,13 @@ bool PlatformMediaSessionManager::hasActiveNowPlayingSessionInGroup(MediaSession
     return hasActiveNowPlayingSession;
 }
 
+void PlatformMediaSessionManager::enqueueTaskOnMainThread(Function<void()>&& task)
+{
+    callOnMainThread(CancellableTask(m_taskGroup, [task = WTFMove(task)] () mutable {
+        task();
+    }));
+}
+
 #if !RELEASE_LOG_DISABLED
 WTFLogChannel& PlatformMediaSessionManager::logChannel() const
 {
diff --git a/Source/WebCore/platform/audio/PlatformMediaSessionManager.h b/Source/WebCore/platform/audio/PlatformMediaSessionManager.h
index 527d12fff117..e5dd5c0f9503 100644
--- a/Source/WebCore/platform/audio/PlatformMediaSessionManager.h
+++ b/Source/WebCore/platform/audio/PlatformMediaSessionManager.h
@@ -31,6 +31,7 @@
 #include "RemoteCommandListener.h"
 #include "Timer.h"
 #include <wtf/AggregateLogger.h>
+#include <wtf/CancellableTask.h>
 #include <wtf/Vector.h>
 #include <wtf/WeakHashSet.h>
 #include <wtf/WeakPtr.h>
@@ -83,7 +84,7 @@ class PlatformMediaSessionManager
     WEBCORE_EXPORT static void setMediaCapabilityGrantsEnabled(bool);
 #endif
 
-    virtual ~PlatformMediaSessionManager() = default;
+    virtual ~PlatformMediaSessionManager();
 
     virtual void scheduleSessionStatusUpdate() { }
 
@@ -227,6 +228,7 @@ class PlatformMediaSessionManager
     std::optional<bool> supportsSpatialAudioPlayback() { return m_supportsSpatialAudioPlayback; }
 
     void nowPlayingMetadataChanged(const NowPlayingMetadata&);
+    void enqueueTaskOnMainThread(Function<void()>&&);
 
 private:
     friend class Internals;
@@ -260,6 +262,7 @@ class PlatformMediaSessionManager
     bool m_hasScheduledSessionStateUpdate { false };
 
     WeakHashSet<NowPlayingMetadataObserver> m_nowPlayingMetadataObservers;
+    TaskCancellationGroup m_taskGroup;
 
 #if ENABLE(WEBM_FORMAT_READER)
     static bool m_webMFormatReaderEnabled;
diff --git a/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm b/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm
index 3c4000a571a1..d50c37334a7e 100644
--- a/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm
+++ b/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm
@@ -270,7 +270,7 @@
 
 void MediaSessionManagerCocoa::scheduleSessionStatusUpdate()
 {
-    callOnMainThread([this] () mutable {
+    enqueueTaskOnMainThread([this] () mutable {
         m_nowPlayingManager->setSupportsSeeking(computeSupportsSeeking());
         updateNowPlayingInfo();
 
@@ -329,7 +329,7 @@
 {
Loading diff…

Original Bug Report

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