49e046b1a9fbd2729e709c7da5a227e849b397bb Validate numberOfFrames in SpeechRecognitionRemoteRealtimeMediaSourceManager::remoteAudioSamplesAvailable
Triage note: SpeechRecognitionRemoteRealtimeMediaSourceManager::remoteAudioSamplesAvailable adds MESSAGE_CHECK(frameCount) and MESSAGE_CHECK(numberOfFrames && numberOfFrames <= *frameCount), backed by a new CARingBuffer::frameCount()/audioBufferFrameCount(). Enforces an IPC bound on an attacker-controlled frame count before it indexes the shared ring buffer in the UIProcess; MESSAGE_CHECK rejects malformed IPC, making this a genuine OOB-prevention fix.
The bug at a glance
SpeechRecognitionRemoteRealtimeMediaSourceManager::remoteAudioSamplesAvailable() in the UIProcess forwarded an attacker-controlled numberOfFrames from a WebProcess IPC message straight into consumption of a shared CARingBuffer without checking it against the ring buffer’s actual frame count. A compromised/malicious WebContent process could therefore make the privileged UIProcess read past the end of the shared audio ring buffer. The diff cleanly supports an OOB-read across the WebProcess->UIProcess trust boundary (OOB-read/high is justified); the primitive is a bounded out-of-bounds read / info-disclosure or crash in the UIProcess, not a demonstrated write or RCE.
A frame count that crosses the sandbox boundary over IPC was trusted as-is to index a shared-memory ring buffer in the more-privileged UIProcess — the textbook place MESSAGE_CHECK exists to guard.
Root cause
The invariant is that any count or index arriving from a less-trusted WebProcess over IPC must be validated before it is used to address shared memory in the UIProcess. SpeechRecognitionRemoteRealtimeMediaSource wraps a ConsumerSharedCARingBuffer whose storage is shared with the WebProcess; consuming audio means reading numberOfFrames frames out of that CARingBuffer.
Pre-patch, the manager’s IPC entry point did if (auto source = m_sources.get(identifier).get()) source->remoteAudioSamplesAvailable(time, numberOfFrames); — numberOfFrames flowed unchecked from the wire into the per-source consume path. 1. WebProcess sends RemoteAudioSamplesAvailable(identifier, time, numberOfFrames) with numberOfFrames far larger than the ring buffer’s capacity; 2. the manager looks up the source and forwards numberOfFrames verbatim; 3. the source reads that many frames from the shared CARingBuffer, walking off the end of the mapped region. Because the ring buffer is shared memory sized at setStorage() time, an oversized frame count indexes UIProcess-reachable memory beyond the buffer.
The fix exposes the ring buffer’s true size via CARingBuffer::frameCount() and SpeechRecognitionRemoteRealtimeMediaSource::audioBufferFrameCount(), then guards the IPC handler with MESSAGE_CHECK(frameCount) and MESSAGE_CHECK(numberOfFrames && numberOfFrames <= *frameCount). MESSAGE_CHECK_BASE validates against the message sender’s connection and tears down the offending WebProcess connection on failure, so a malformed count rejects the IPC instead of indexing memory. The source lookup is also hardened to a RefPtr with an early return. This matches the triage (OOB-read): the diff adds a bound on an attacker-controlled frame count before shared-buffer indexing; it does not show a write primitive.
Key code
MESSAGE_CHECK the IPC frame count against the ring buffer size
RefPtr source = m_sources.get(identifier).get();
if (!source)
return;
auto frameCount = source->audioBufferFrameCount();
MESSAGE_CHECK(frameCount);
MESSAGE_CHECK(numberOfFrames && numberOfFrames <= *frameCount);
source->remoteAudioSamplesAvailable(time, numberOfFrames);
Fix approach
The patch introduces a trustworthy upper bound and enforces it at the IPC seam. CARingBuffer gains a frameCount() accessor returning m_frameCount; SpeechRecognitionRemoteRealtimeMediaSource exposes it as an optional via audioBufferFrameCount() (nullopt when there is no ring buffer / non-Cocoa). The manager’s handler then MESSAGE_CHECKs that a frame count exists and that the incoming numberOfFrames is non-zero and <= the ring buffer’s frame count; MESSAGE_CHECK (defined locally as MESSAGE_CHECK_BASE against *messageSenderConnection(), then #undef’d) kills the connection on violation. This converts an unbounded index into a validated one and makes a lying WebProcess fail closed.
Patch walkthrough
Source/WebCore/platform/audio/cocoa/CARingBuffer.h— Adds uint32_t frameCount() const { return m_frameCount; } so callers can learn the ring buffer’s true capacity.Source/WebKit/UIProcess/SpeechRecognitionRemoteRealtimeMediaSource.cpp— Adds audioBufferFrameCount() returning the ring buffer’s frameCount() (nullopt when no m_ringBuffer / non-Cocoa), exposing the bound to the manager.Source/WebKit/UIProcess/SpeechRecognitionRemoteRealtimeMediaSource.h— Declares audioBufferFrameCount() on the source.Source/WebKit/UIProcess/SpeechRecognitionRemoteRealtimeMediaSourceManager.cpp— Defines MESSAGE_CHECK against the sender connection, rewrites remoteAudioSamplesAvailable to a RefPtr lookup plus MESSAGE_CHECK(frameCount) and MESSAGE_CHECK(numberOfFrames && numberOfFrames <= *frameCount) before consuming samples, and #undefs the macro.
Background
CARingBuffer / ConsumerSharedCARingBuffer — A Core Audio ring buffer backed by memory shared between the WebProcess (producer) and UIProcess (consumer); its frame capacity is fixed at construction/setStorage().
MESSAGE_CHECK / MESSAGE_CHECK_BASE — WebKit IPC macros that validate an assertion on incoming messages and terminate the sender’s connection on failure, the standard way to reject malformed cross-process input.
WebProcess -> UIProcess trust boundary — The UIProcess is privileged and must treat every value from the sandboxed WebProcess as hostile; speech-recognition audio samples arrive over this boundary.
RealtimeMediaSourceIdentifier — An identifier mapping an IPC message to a specific media source instance in the manager’s m_sources table.
numberOfFrames — The attacker-controlled count of audio frames the message claims are available; used to decide how far into the ring buffer to read.
Vulnerability window
- Setup — A page starts speech recognition, establishing a SpeechRecognitionRemoteRealtimeMediaSource with a shared CARingBuffer of fixed frame capacity.
- Compromise — The WebContent process is attacker-controlled (or buggy) and can craft arbitrary RemoteAudioSamplesAvailable IPC messages.
- Trigger — WebProcess sends remoteAudioSamplesAvailable with numberOfFrames > ring buffer frameCount.
- OOB read — Pre-patch, the UIProcess forwards the count and reads past the end of the shared ring buffer, disclosing adjacent memory or crashing.
Proof of concept
No regression test ships in the diff. Conceptual trigger (requires a compromised WebProcess, so the web path is inferred): from the WebContent side, after speech recognition storage is set up, send the RemoteAudioSamplesAvailable IPC with a numberOfFrames value larger than the ring buffer’s frame count; pre-patch the UIProcess consumes that many frames and reads out of bounds. Post-patch the MESSAGE_CHECK rejects the message and terminates the connection.
Exploitation
- Reach — Requires code execution in / control of a WebContent process able to send the speech-recognition IPC to the UIProcess.
- Trigger — Send remoteAudioSamplesAvailable with an oversized numberOfFrames for a live source identifier.
- Primitive (primary) — Bounded OOB read in the UIProcess: frames beyond the ring buffer are read from UIProcess-reachable memory.
- Primitive (secondary) — Depending on how consumed samples are surfaced, the leaked bytes could become observable (info disclosure) or simply crash the UIProcess (DoS); a write primitive is not shown.
- Open question (gating) — Whether the read data is ever returned to the WebProcess or merely processed internally determines info-leak vs crash; the diff does not establish an exfiltration path (inferred).
Detection & hunting
For defenders and detection engineers:
- Unchecked IPC counts indexing shared buffers — Audit UIProcess IPC handlers that take frame/sample/length counts and feed CARingBuffer or other shared-memory consumers without a MESSAGE_CHECK.
- WebProcess connection terminations — Monitor for MESSAGE_CHECK-driven connection teardowns on the speech-recognition message, indicating malformed frame counts.
- OOB reads near ring buffers — ASan in the UIProcess flagging reads past CARingBuffer storage during sample consumption.
Remaining risk in the patch
- Non-Cocoa / no-ring-buffer paths — audioBufferFrameCount() returns nullopt off PLATFORM(COCOA) or before m_ringBuffer is set; MESSAGE_CHECK(frameCount) then rejects, which is safe, but it also means the frame-count bound only exists on Cocoa — non-Cocoa consumption of numberOfFrames (if any) is not bounded by this patch.
- time parameter still untrusted — Only numberOfFrames is validated; the MediaTime ’time’ argument from the same IPC is still forwarded unchecked, and the diff does not show how downstream remoteAudioSamplesAvailable uses it.
Audit directions
- Other CARingBuffer consumers — Review all UIProcess/GPU-process consumers of shared CARingBuffers (audio capture, media, WebRTC) for unchecked numberOfFrames against frameCount().
- SpeechRecognition IPC surface — Check every SpeechRecognitionRemoteRealtimeMediaSourceManager handler for identifier/time/count validation and source-lifetime (RefPtr) handling.
- Shared-memory length trust — Grep for setStorage()/shared handle consumers that derive read sizes from IPC rather than from the mapped region’s own size.