← WebKit — 2026-W37
fb20d4d63ba2a3a5c6618befe58aebaba605dcad PlatformSpeechSynthesizerClient should prevent use-after-free via weak back-reference to client
severity medium
class UAF
confidence 0.6
signal score 18.75
Source/WebCore/platform
patch-now
Primitive: Dangling-reference use of a destroyed PlatformSpeechSynthesizerClient from async platform speech callbacks
Triage note: m_speechSynthesizerClient is converted from a raw C++ reference (PlatformSpeechSynthesizerClient&) to WeakPtr, the client base now inherits AbstractRefCountedAndCanMakeWeakPtr, and every call site in async delegate/signal/timer callbacks (AVSpeechSynthesizer didStartSpeaking/didFinishSpeaking, GStreamer pause/resume, mock timers, voicesDidChange) now takes a RefPtr and null-checks before dispatch. The old raw reference could be dereferenced after the client was destroyed while platform speech callbacks were still pending, a classic async-callback UAF, reachable via the Web Speech Synthesis API.
Triage note: m_speechSynthesizerClient is converted from a raw C++ reference (PlatformSpeechSynthesizerClient&) to WeakPtr, the client base now inherits AbstractRefCountedAndCanMakeWeakPtr, and every call site in async delegate/signal/timer callbacks (AVSpeechSynthesizer didStartSpeaking/didFinishSpeaking, GStreamer pause/resume, mock timers, voicesDidChange) now takes a RefPtr and null-checks before dispatch. The old raw reference could be dereferenced after the client was destroyed while platform speech callbacks were still pending, a classic async-callback UAF, reachable via the Web Speech Synthesis API.
Structural signals: smartptr_member_added (+6.47), null_guard (+1.2), uaf_lifetime (+3.57), msg_strong (+3.0), msg_medium (+2.0), msg_fuzzer (+2.5)
Commit message
PlatformSpeechSynthesizerClient should prevent use-after-free via weak back-reference to client
https://bugs.webkit.org/show_bug.cgi?id=312793
rdar://172854014
Reviewed by Joshua Hoffman.
PlatformSpeechSynthesizer held a raw C++ reference
(PlatformSpeechSynthesizerClient&) back to its client
(SpeechSynthesis). When cancel() was made asynchronous via
callOnMainThread in 309349@main, the deferred lambda could fire after
SpeechSynthesis had been destroyed, dereferencing freed memory. ASan
caught this as a heap-use-after-free in 131 media/track layout tests.
This commit makes PlatformSpeechSynthesizerClient inherit from
AbstractRefCountedAndCanMakeWeakPtr so it supports both WeakPtr (for
the non-owning back-reference) and RefPtr (for strong promotion at
call sites). Convert the raw reference member to a WeakPtr, and update
all ~29 call sites across all platform implementations to promote to
RefPtr before calling through the client.
This also fixes two latent async bugs with the same pattern: the Cocoa
voicesDidChange async callback and the Spiel initializeVoiceList
lambda both accessed the client in a deferred context without lifetime
protection.
* Source/WebCore/platform/PlatformSpeechSynthesizer.cpp:
(WebCore::PlatformSpeechSynthesizer::voicesDidChange):
* Source/WebCore/platform/PlatformSpeechSynthesizer.h:
* Source/WebCore/platform/cocoa/PlatformSpeechSynthesizerCocoa.mm:
(-[WebSpeechSynthesisWrapper speakUtterance:]):
(-[WebSpeechSynthesisWrapper speechSynthesizer:didStartSpeechUtterance:]):
(-[WebSpeechSynthesisWrapper speechSynthesizer:didFinishSpeechUtterance:]):
(-[WebSpeechSynthesisWrapper speechSynthesizer:didPauseSpeechUtterance:]):
(-[WebSpeechSynthesisWrapper speechSynthesizer:didContinueSpeechUtterance:]):
(-[WebSpeechSynthesisWrapper speechSynthesizer:didCancelSpeechUtterance:]):
(-[WebSpeechSynthesisWrapper speechSynthesizer:willSpeakRangeOfSpeechString:utterance:]):
(WebCore::PlatformSpeechSynthesizer::initializeVoiceList):
* Source/WebCore/platform/gstreamer/PlatformSpeechSynthesizerGStreamer.cpp:
(WebCore::GstSpeechSynthesisWrapper::pause):
(WebCore::GstSpeechSynthesisWrapper::resume):
(WebCore::GstSpeechSynthesisWrapper::speakUtterance):
(WebCore::GstSpeechSynthesisWrapper::cancel):
* Source/WebCore/platform/mock/PlatformSpeechSynthesizerMock.cpp:
(WebCore::PlatformSpeechSynthesizerMock::speakingFinished):
(WebCore::PlatformSpeechSynthesizerMock::speak):
(WebCore::PlatformSpeechSynthesizerMock::cancel):
(WebCore::PlatformSpeechSynthesizerMock::pause):
(WebCore::PlatformSpeechSynthesizerMock::resume):
* Source/WebCore/platform/spiel/PlatformSpeechSynthesizerSpiel.cpp:
(WebCore::SpielSpeechWrapper::finishSpeakerInitialization):
(WebCore::SpielSpeechWrapper::speakUtterance):
(WebCore::PlatformSpeechSynthesizer::initializeVoiceList):
* Source/WebCore/testing/Internals.cpp:
(WebCore::Internals::simulateSpeechSynthesizerVoiceListChange):
Originally-landed-as: 305413.715@safari-7624-branch (71471a83ed2d). rdar://184744850
Canonical link: https://commits.webkit.org/320970@main
Before / after
Loading diff…
On This Page