CVE-2026-43734
Overview
Background
- callOnMainThreadAndWait
- A WTF helper that dispatches a task to the main thread and blocks the calling thread until it finishes, used when a non-main thread needs a main-thread-only API.
- crossThreadCopy / isolatedCopy
- WTF facilities that produce a deep, self-owned copy of a value (including its reference-counted String/Vector buffers) so it can be safely handed to another thread without sharing state.
- MediaEngineSupportParameters
- A WebCore struct bundling the content type, URL, and allowed container/codec/caption type lists used to ask a media engine whether it can play a given type.
- Non-thread-safe String/Vector
- WTF::String and Vector use reference counting and shared backing stores that are cheap to copy on one thread but unsafe to share or mutate concurrently across threads.
- Lambda capture by reference ([&])
- Captures surrounding locals by reference so the closure accesses the originals; across a thread boundary this shares the very objects the other thread must not touch.
Root Cause Analysis
MediaSource::isTypeSupported runs on a worker/script thread but must query MediaPlayer::supportsType, which has to execute on the main thread. To do so it used callOnMainThreadAndWait with a lambda that captured the local MediaEngineSupportParameters by reference ([&]) and then called MediaPlayer::supportsType(parameters) on the main thread. MediaEngineSupportParameters is a struct containing String and Vector<String>/Vector<FourCC> members (type, url, contentTypesRequiringHardwareSupport, allowedMediaContainerTypes/CodecTypes, etc.). These WTF String/Vector types are reference-counted/heap-backed but are NOT thread-safe to share across threads by reference: passing them by reference means the main thread touches the same underlying buffers that the originating thread owns, violating the invariant that cross-thread work must operate on data that has been deep-copied (crossThreadCopy / isolatedCopy) into thread-owned storage. Although callOnMainThreadAndWait blocks the caller until the main-thread task completes, capturing by reference still lets the two threads concurrently reference the same non-thread-safe String/Vector internals during the hand-off, and any implicit ref/deref, copy, or internal mutation of those shared buffers from the main thread races with the worker thread’s ownership, leading to use-after-free / heap corruption of the string buffers.
The fix changes the capture to [&supported, parameters = crossThreadCopy(WTF::move(parameters))], deep-copying the parameters into a thread-safe isolated copy that the main-thread lambda solely owns, so no non-thread-safe buffer is shared across the boundary. To support that, a new MediaEngineSupportParameters::isolatedCopy() && rvalue-qualified method is added in MediaPlayer.cpp/.h that crossThreadCopy()s each string/vector member and moves the trivially-copyable/plain members, giving crossThreadCopy a correct per-field deep-copy for this struct.
The restored invariant is: values handed to another thread must be isolated copies with no shared reference-counted state.
Attack Path
- Run isTypeSupported off the main thread From maliciously crafted web content, invoke MediaSource.isTypeSupported(…) (or a code path reaching MediaSource::isTypeSupported) from a Worker or other non-main script thread so the callOnMainThreadAndWait hand-off is exercised.
- Supply complex type parameters Pass a content type / codec string that populates the String and Vector members of MediaEngineSupportParameters (type, allowed container/codec type lists), maximizing the reference-counted buffers shared by reference across the thread boundary.
- Race the shared buffers Cause the worker thread and the main thread to touch the same non-thread-safe String/Vector internals during the hand-off, so ref/deref or buffer access on one thread races the other’s ownership (inference: precise timing/interleaving needed to win the race is not shown by the diff).
- Trigger use-after-free The race frees or corrupts a String/Vector backing buffer while the other thread still references it, producing a use-after-free within WebCore’s media pipeline.
- Crash the process Per the advisory this yields an unexpected process crash; escalation beyond a crash would require additional heap-grooming and a separate corruption primitive not established by this commit.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
NullMediaPlayerPrivateSource/WebCore/platform/graphics/MediaPlayer.cpp |
modified |
Files Changed
Source/WebCore/Modules/mediasource/MediaSource.cppSource/WebCore/platform/graphics/MediaPlayer.cppSource/WebCore/platform/graphics/MediaPlayer.h
Audit Directions
- Other by-reference captures into callOnMainThread*Grep WebCore for callOnMainThreadAndWait and callOnMainThread lambdas that capture [&] or [&, …] and inspect whether any captured local holds String/Vector/RefPtr state shared across the boundary rather than a crossThreadCopy.
- Structs handed cross-thread without isolatedCopySearch for crossThreadCopy(…) usages and, conversely, structs with String/Vector members passed into cross-thread dispatch (dispatchToMainThread, Function, CrossThreadTask) that lack an isolatedCopy()&& overload; a missing isolatedCopy is the tell.
- Media parameter plumbing across threadsAudit MediaSource, SourceBuffer, and MediaPlayer paths that bridge worker/script threads to the main thread with MediaEngineSupportParameters or similar content-type structs, verifying each hand-off deep-copies rather than referencing caller-owned buffers.
Patch
diff --git a/Source/WebCore/Modules/mediasource/MediaSource.cpp b/Source/WebCore/Modules/mediasource/MediaSource.cpp
index 035b6ad444fb..140425a7964a 100644
--- a/Source/WebCore/Modules/mediasource/MediaSource.cpp
+++ b/Source/WebCore/Modules/mediasource/MediaSource.cpp
@@ -1188,7 +1188,7 @@ bool MediaSource::isTypeSupported(ScriptExecutionContext& context, const String&
}
MediaPlayer::SupportsType supported;
- callOnMainThreadAndWait([&] {
+ callOnMainThreadAndWait([&supported, parameters = crossThreadCopy(WTF::move(parameters))] {
supported = MediaPlayer::supportsType(parameters);
});
diff --git a/Source/WebCore/platform/graphics/MediaPlayer.cpp b/Source/WebCore/platform/graphics/MediaPlayer.cpp
index 352492014a02..21942ae9bd34 100644
--- a/Source/WebCore/platform/graphics/MediaPlayer.cpp
+++ b/Source/WebCore/platform/graphics/MediaPlayer.cpp
@@ -116,6 +116,24 @@ namespace WebCore {
WTF_MAKE_TZONE_ALLOCATED_IMPL(MediaPlayer);
WTF_MAKE_TZONE_ALLOCATED_IMPL(MediaPlayerFactory);
+MediaEngineSupportParameters MediaEngineSupportParameters::isolatedCopy() &&
+{
+ SUPPRESS_UNCOUNTED_ARG return {
+ crossThreadCopy(WTF::move(type)),
+ crossThreadCopy(WTF::move(url)),
+ isMediaSource,
+ isMediaStream,
+ requiresRemotePlayback,
+ supportsLimitedMatroska,
+ crossThreadCopy(WTF::move(contentTypesRequiringHardwareSupport)),
+ crossThreadCopy(WTF::move(allowedMediaContainerTypes)),
+ crossThreadCopy(WTF::move(allowedMediaCodecTypes)),
+ WTF::move(allowedMediaVideoCodecIDs),
+ WTF::move(allowedMediaAudioCodecIDs),
+ WTF::move(allowedMediaCaptionFormatTypes),
+ };
+}
+
// a null player to make MediaPlayer logic simpler
class NullMediaPlayerPrivate final : public MediaPlayerPrivateInterface, public ThreadSafeRefCounted<NullMediaPlayerPrivate, WTF::DestructionThread::Main> {
diff --git a/Source/WebCore/platform/graphics/MediaPlayer.h b/Source/WebCore/platform/graphics/MediaPlayer.h
index 5d8e3b632bdc..a634f3e95955 100644
--- a/Source/WebCore/platform/graphics/MediaPlayer.h
+++ b/Source/WebCore/platform/graphics/MediaPlayer.h
@@ -122,6 +122,8 @@ struct MediaEngineSupportParameters {
std::optional<Vector<FourCC>> allowedMediaVideoCodecIDs;
std::optional<Vector<FourCC>> allowedMediaAudioCodecIDs;
std::optional<Vector<FourCC>> allowedMediaCaptionFormatTypes;
+
+ WARN_UNUSED_RETURN WEBCORE_EXPORT MediaEngineSupportParameters isolatedCopy() &&;
};
struct SeekTarget {