Signal RingRTC zero-day flaw exposes ambient audio after hang-up (22 Oct 2026)
A critical flaw in Signal's RingRTC engine (CVE-2026-113380, CVSS 9.3) allows an active peer to saturate finite-state machine queues with synthetic events. When a victim taps hang-up, the UI dismisses but unserviced queues leave microphone hardware streaming audio. Patched in 2.72.0.
By Parminder Kumar Sharma · · 8 min read

Signal RingRTC CVE-2026-113380: 256-slot queue starvation and post-hangup eavesdropping
A critical vulnerability in RingRTC, the open-source voice and video calling engine developed by Signal (CVE-2026-113380, CVSS v4.0 base score 9.3), allows an active call participant to silently keep a remote victim's microphone and camera hardware active and streaming after the victim presses the hang-up button. The flaw stems from an architectural race condition inside RingRTC's Rust finite-state machine (FSM), where a bounded 256-slot event queue can be saturated by synthetic peer signaling, preventing the local client from executing its media teardown sequence.
When the victim taps the red End Call button in the Signal user interface, the UI layer immediately dismisses the call screen and returns to the chat timeline, leading the user to believe the session has completely terminated. However, because the underlying hang-up event is queued sequentially behind hundreds of un-rate-limited signaling packets, RingRTC's worker thread fails to transition the call state to Terminated. As a result, native WebRTC audio capture continues running at the operating system hardware level, encoding ambient acoustic telemetry via Opus and transmitting continuous SRTP streams directly to the remote adversary.
What this establishes from the technical record. The flaw was identified by security researcher Imahian and patched by Signal developers in RingRTC version 2.72.0. The vulnerability operates entirely at the application protocol layer without requiring memory corruption, shellcode injection, or elevated operating system privileges. What this does not establish. The attack cannot remotely activate a victim's microphone on demand without an established call; the target user must either initiate a call to the attacker or accept an incoming call. Furthermore, Signal has indicated that their operational telemetry shows no evidence of active in-the-wild exploitation prior to public disclosure, though client-side end-to-end encryption inherently restricts server-side visibility into peer-to-peer signaling floods.
Architectural breakdown: FSM queue saturation and event loop starvation
RingRTC serves as the unified multi-platform calling layer for Signal Android (via JNI), Signal iOS (via Objective-C/Swift native bridges), and Signal Desktop (via Neon Node.js bindings). Built in Rust around Google's native libwebrtc C++ framework, RingRTC decouples UI event handling from native audio/video capture through an internal asynchronous finite-state machine (CallFsm):
Inside RingRTC versions prior to 2.72.0, all call events (including incoming ICE candidates, Session Description Protocol (SDP) renegotiation offers, mute toggles, and user actions) were funneled into a single bounded channel with a hard capacity of 256 entries. Under standard operating conditions, this buffer rarely exceeds 3 to 5 pending items during initial call setup. However, the channel lacked ingress rate-limiting for peer-originated signaling messages delivered over Signal's end-to-end encrypted signaling channel.
An adversary on an active call can transmit high-frequency bursts of synthetic ICE candidates or empty SDP update frames directly to the target. At an injection rate of 1,000 signaling messages per second (which requires negligible network bandwidth over an established connection), the 256-slot buffer reaches complete saturation in exactly 256 milliseconds (derived). When the victim taps Hang Up, the application UI generates CallEvent::Hangup and attempts to enqueue it. If the bounded channel is completely full, the enqueue operation either stalls or fails, leaving the hang-up signal trapped behind a backlog of unvalidated peer messages.
The fatal decoupling: UI dismissal vs native hardware persistence
The severity of CVE-2026-113380 arises from an asymmetry between how Signal's front-end user interface processes teardown and how RingRTC's backend engine executes media destruction. In legacy versions, the UI layer handled the hang-up button click optimistically. The application immediately tore down the calling view, removed in-call screen overlays, and updated local chat state to Idle. To the user looking at their device, the call appeared completely dead.
Behind the scenes, the native WebRTC media engine (AudioDeviceModule on Android, CoreAudio VoiceProcessingIO on macOS/iOS) was not governed directly by the front-end UI. Hardware recording is controlled exclusively by RingRTC's CallFsm worker thread upon transitioning into the Terminating state. Because the FSM worker was either blocked parsing malformed signaling packets or starved processing hundreds of synthetic ICE candidate evaluations, it never reached the teardown block. Consequently, AudioTrack::set_enabled(false) and AudioDeviceModule::StopRecording() were never called.
RingRTC component state comparison during normal call teardown versus CVE-2026-113380 exploit condition
| Subsystem / Component | Normal Call Hangup State | Exploited Ghost State (RingRTC < 2.72.0) | Patched State (RingRTC >= 2.72.0) |
|---|---|---|---|
| Signal Client UI | Call screen dismissed; Idle state | Call screen dismissed; Idle state (Deceptive) | Call screen dismissed; Idle state |
| FSM Event Queue | CallEvent::Hangup processed at Head | Starved at Slot 256+ behind synthetic flood | Bypassed via atomic termination token |
| Call State Machine | Transitions: Connected -> Terminated | Frozen in Connected state | Forced into Terminated via 5s hard deadline |
| AudioDeviceModule (ADM) | StopRecording() executed immediately | Microphone remains active and recording | StopRecording() executed synchronously at click |
| RTP/SRTP Egress | Zero packets; socket closed | Active Opus stream (50 packets/second) | Zero packets; hardware track destroyed |
Teardown lifecycle comparison: sequential FIFO vs fail-forward architecture
To resolve the vulnerability, Signal engineers re-architected RingRTC's teardown lifecycle in version 2.72.0, replacing the sequential single-lane queue with an out-of-band atomic cancellation mechanism and an unconditional fail-forward hardware destruction pipeline:
In the patched architecture, when the user initiates a hang-up action, two deterministic safeguards execute in parallel: First, an AtomicBool cancellation token (is_terminating) is set immediately. This flag is polled at the start of every internal worker iteration, causing the engine to discard any queued peer signaling packets without parsing them. Second, RingRTC synchronously disables the local MediaStreamTrack and commands the AudioDeviceModule to halt hardware sampling before entering the asynchronous FSM shutdown loop. Furthermore, the entire FSM teardown is wrapped inside a 5-second hard deadline; if peer socket closure stalls, the runtime forcefully destroys the WebRTC PeerConnection instance.
Deconstructed code: vulnerable sequential dispatch vs patched atomic shutdown
The structural difference between the vulnerable and patched implementations can be illustrated by examining the FSM event dispatch loop in Rust:
// VULNERABLE PATTERN (RingRTC < 2.72.0)
// Queue is bounded; teardown relies strictly on FIFO queue consumption.
pub struct CallFsm {
event_rx: sync::mpsc::Receiver<CallEvent>, // Bounded at 256 slots
state: CallState,
peer_connection: Option<Arc<PeerConnection>>,
audio_track: Option<MediaStreamTrack>,
}
impl CallFsm {
pub fn run(&mut self) {
// Worker thread sequentially pulls from bounded queue
while let Ok(event) = self.event_rx.recv() {
match event {
CallEvent::PeerSignaling(msg) => {
// Heavy parsing of SDP or ICE candidate
// If adversary floods 1000s of msgs, loop never catches up
self.handle_signaling(msg);
}
CallEvent::Hangup => {
// Trapped at tail of saturated queue
self.terminate_session();
break;
}
}
}
}
fn terminate_session(&mut self) {
if let Some(track) = self.audio_track.take() {
track.set_enabled(false); // Hardware only halted here!
}
if let Some(pc) = self.peer_connection.take() {
pc.close();
}
self.state = CallState::Terminated;
}
}
// PATCHED IMPLEMENTATION (RingRTC 2.72.0)
// Out-of-band atomic flag, synchronous hardware mute, and 5.0s hard timeout.
pub struct CallFsm {
event_rx: tokio::sync::mpsc::Receiver<CallEvent>,
is_terminating: Arc<AtomicBool>, // Out-of-band cancellation token
state: CallState,
peer_connection: Option<Arc<PeerConnection>>,
audio_track: Option<MediaStreamTrack>,
}
impl CallFsm {
// Invoked directly from UI thread upon Hangup click
pub fn trigger_immediate_hangup(&self) {
// 1. Set atomic termination token out-of-band (bypasses event queue)
self.is_terminating.store(true, Ordering::SeqCst);
// 2. Synchronously stop hardware audio capture (0ms delay)
if let Some(ref track) = self.audio_track {
track.set_enabled(false);
// Native ADM StopRecording() invoked immediately
}
}
pub async fn run_with_timeout(&mut self) {
// Wrap graceful closure in a strict 5-second hard deadline
let shutdown_result = tokio::time::timeout(Duration::from_secs(5), async {
while let Some(event) = self.event_rx.recv().await {
if self.is_terminating.load(Ordering::SeqCst) {
// Discard any backlogged peer signaling junk
break;
}
self.process_event(event).await;
}
}).await;
if shutdown_result.is_err() {
// Hard timeout expired: forcefully tear down WebRTC connection
eprintln!("FSM shutdown deadline exceeded; forcing PeerConnection destroy");
}
self.force_cleanup();
}
}
Acoustic surveillance exposure and bandwidth leakage modeling
To understand the quantitative risk posed by an unserviced audio stream, we modeled the volume of acoustic and visual telemetry leaked across multiple durations during a ghost call session. Signal utilizes the Opus speech codec with standard 20-millisecond frame intervals (transmitting 50 packets per second over UDP). On video calls where the camera was engaged, 720p H.264 or VP8 video feeds compound the data leak:
Modeled media leakage volume across ghost call durations (derived via calc.py)
| Media Channel & Codec | Nominal Bitrate | 1 Minute Exposure | 10 Minute Exposure | 60 Minute Exposure |
|---|---|---|---|---|
| Opus Voice (Standard Speech) | 32 kbps (4,000 B/s) | 234.38 KB | 2.29 MB | 13.73 MB |
| Opus Voice (Wideband Music) | 64 kbps (8,000 B/s) | 468.75 KB | 4.58 MB | 27.47 MB |
| 720p Video (VP8/H.264 Baseline) | 1.5 Mbps (187.5 KB/s) | 10.73 MB | 107.29 MB | 643.74 MB |
| 720p Video (High Quality Stream) | 2.5 Mbps (312.5 KB/s) | 17.88 MB | 178.81 MB | 1.07 GB |
A 10-minute ghost call leaks 2.29 megabytes of uncompressed Opus acoustic data. In an executive or confidential environment, this provides adversary operators with an unbroken ambient recording of any subsequent room conversation. Because modern mobile operating systems (Android and iOS) grant background microphone permissions to active calling services, the OS microphone indicator may remain illuminated or be masked by system foreground-service persistence routines depending on platform build.
Forensic verification and defensive inspection script
System administrators, mobile forensic analysts, and enterprise device managers can verify whether installed client packages contain the patched RingRTC binary by extracting the embedded native shared object (libringrtc.so) and inspecting the internal version string:
#!/usr/bin/env python3
"""
verify_ringrtc.py - Forensic utility to inspect RingRTC version in Signal binaries
Validates compliance against CVE-2026-113380 (Requires RingRTC >= 2.72.0)
"""
import re
import sys
import zipfile
MIN_SAFE_VERSION = (2, 72, 0)
def parse_version(version_str):
match = re.search(r'(\d+)\.(\d+)\.(\d+)', version_str)
if match:
return tuple(map(int, match.groups()))
return None
def inspect_apk(apk_path):
print(f"[*] Scanning APK archive: {apk_path}")
target_lib = "lib/arm64-v8a/libringrtc.so"
try:
with zipfile.ZipFile(apk_path, 'r') as z:
if target_lib not in z.namelist():
print(f"[-] Error: {target_lib} not found in APK")
return False
content = z.read(target_lib)
# Scan for RingRTC embedded semantic version pattern
matches = re.findall(b'ringrtc[_-]v?(\d+\.\d+\.\d+)', content, re.IGNORECASE)
if not matches:
matches = re.findall(b'(\d+\.\d+\.\d+)', content)
found_versions = set(m.decode('ascii', errors='ignore') for m in matches)
print(f"[*] Detected version strings in binary: {found_versions}")
for ver_str in found_versions:
parsed = parse_version(ver_str)
if parsed and parsed >= (2, 50, 0): # Filter for plausible RingRTC semver
if parsed < MIN_SAFE_VERSION:
print(f"[!] VULNERABLE: RingRTC {ver_str} < 2.72.0 detected!")
print(" Exposed to CVE-2026-113380 post-hangup audio streaming")
return False
else:
print(f"[+] PATCHED: RingRTC {ver_str} >= 2.72.0 verified safe")
return True
except Exception as e:
print(f"[-] Failed to parse APK: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 verify_ringrtc.py <path_to_signal.apk>")
sys.exit(1)
inspect_apk(sys.argv[1])
On managed corporate enterprise Wi-Fi networks, network detection engineers can hunt for ghost call streams by inspecting egress UDP NetFlow traces. When a Signal client transmits a signaling BYE frame over TLS port 443, any continued UDP traffic over ephemeral WebRTC ports exceeding 10 seconds indicates an abnormal un-teardown condition that warrants immediate client endpoint isolation.
Epistemic audit: stated metrics, derived facts, and architectural lessons
In keeping with rigorous technical analysis, we audit the operational and threat claims surrounding CVE-2026-113380:
-
Stated by MITRE and vulnerability disclosure: The CVSS v4.0 base score is 9.3, classified under CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization) and CWE-400 (Uncontrolled Resource Consumption). The vulnerability affects RingRTC versions prior to 2.72.0.
-
Stated by Signal: Signal confirmed that RingRTC 2.72.0 resolves the issue by separating termination triggers from the event queue and enforcing strict shutdown deadlines. Signal states that no in-the-wild exploitation was observed.
-
Derived technical facts: The 256-slot queue can be completely filled in 256 milliseconds at 1,000 signaling messages per second. At 32 kbps, Opus audio leaks approximately 2.29 MB per 10 minutes of ghost state. Audio capture occurs at the native OS layer via WebRTC C++ and cannot be halted by the UI without synchronous driver calls.
-
Architectural takeaway: Decoupling visual UI dismissal from hardware media pipeline destruction is a critical vulnerability anti-pattern in communications software. Applications handling sensitive ambient audio must enforce synchronous, out-of-band hardware mute tokens before declaring a call terminated to the user.
Sources
- PrimaryCVE-2026-113380: Signal RingRTC Media Pipeline Teardown Race ConditionMITRE Corporationaccessed 2026-10-22
- PrimaryRingRTC Release v2.72.0: FSM Teardown and Media Pipeline Destruction FixesSignal Foundationaccessed 2026-10-22
- PrimaryRingRTC: Voice and Video Calling Engine for Signal (Rust / WebRTC C++)Signal Messenger Open Sourceaccessed 2026-10-22


