Automated Threat Modeling

Brief Summary: FreeSWITCH CVE-2026-116784, an Out-of-Bounds Read in RTCP NACK Feedback Parsing

A brief summary of CVE-2026-116784, an out-of-bounds read in FreeSWITCH's RTCP NACK feedback parser that can crash an instance and terminate every active call. Includes PoC and patch details.

CVE Analysis

8 min read

Gaurav Sarraf
Gaurav Sarraf

2026-11-05

Brief Summary: FreeSWITCH CVE-2026-116784, an Out-of-Bounds Read in RTCP NACK Feedback Parsing
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A single malformed UDP datagram sent to the right port can knock an entire FreeSWITCH instance offline, taking down every call it is currently handling. CVE-2026-116784 is an out-of-bounds read in the RTCP NACK feedback parser that an attacker can reach without any SIP registration or credentials, provided a video call is active and its RTCP stream is reachable.

FreeSWITCH is an open source software defined telecom stack maintained by SignalWire, whose founders originally created the project. It performs call routing, video bridging, media transcoding, and protocol interoperability for carriers, contact centers, and communication products, and the vendor states it handles billions of calls annually across thousands of platforms. That broad deployment is why an unauthenticated, instance wide availability bug in its media path is worth understanding in detail.

Technical Information

The vulnerability lives in process_rtcp_report() in src/switch_rtp.c, in the branch that handles RTPFB NACK transport layer feedback.

An RTCP header carries a 16-bit length field that counts the packet size in 32-bit words, minus one. For an RTPFB NACK packet, the first three words are a fixed header: the common header, the sender SSRC, and the media SSRC. That leaves length - 2 words of Feedback Control Information (FCI) entries, with one entry per NACK.

The root cause is a boundary mismatch. The caller had already validated how many bytes actually arrived in the datagram, but the inner feedback loop ignored that validated count and trusted the attacker-controlled declared length instead. The vulnerable loop looked like this:

for (i = 0; i < ntohs(extp->header.length) - 2; i++) { handle_nack(rtp_session, nack[i]); }

Because extp->header.length comes straight from the packet, a short datagram can claim a very large value. A packet only 20 bytes long can declare length = 0xFFFF, and the loop would then read about 65,533 words, roughly 256 KB, from a fixed 16 KiB receive buffer. The advisory estimates this reads approximately 240 KB past the end of the buffer, typically landing in unmapped memory and crashing the process.

The crash is instance wide. When the FreeSWITCH process dies, it terminates every call it is handling, not just the call that received the malformed packet. The advisory is explicit that this is a read only primitive: there is no write capability, and the values read are consumed as retransmission lookup keys rather than echoed back to the sender. There is no documented memory disclosure channel or remote code execution path for this flaw. The weakness is classified as CWE-125, out-of-bounds read, with a CVSS v3.1 vector of CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.

Attack flow

The reachability conditions are what make this notable. An attacker does not need SIP credentials because the packet is consumed by the media stack, not the signaling layer. The steps are:

  1. A video call is active on the target FreeSWITCH instance, and its video RTCP port is negotiated.
  2. RTCP does not need to be explicitly enabled in the SIP profile. The default configuration leaves rtcp-audio-interval-msec and rtcp-video-interval-msec unset, but an a=rtcp-fb or a=rtcp: attribute in the remote SDP enables RTCP for that call. Every WebRTC endpoint sends the former attribute. NACK itself does not have to be negotiated.
  3. The attacker delivers a UDP datagram to the negotiated video RTCP port. The datagram is a short RTPFB NACK packet whose RTCP header declares an oversized length (for example 0xFFFF) while carrying only a couple of FCI words.
  4. process_rtcp_report() uses the declared length as its loop bound and reads far beyond the 16 KiB buffer, crashing the process and dropping every active call.

One operationally important point: signaling authentication and even encrypted media can coexist with this unauthenticated media parsing bug. The entry point is one video stream's RTCP, but the impact is every call served by the process.

Proof of Concept

A public, reproducible proof of concept exists, packaged as an in-tree regression test rather than a standalone weaponized exploit. The fixing commit e730a4e541b2cb49d54018a15f775728e19ce17f added test_rtcp_nack_oversized_length to tests/unit/switch_rtp_pcap.c. It builds and transmits a malformed 20 byte RTCP NACK datagram and routes it through the RTCP reader.

The crafted packet is:

unsigned char nack[20] = { 0x81, 0xcd, 0xff, 0xff, /* V/P/FMT=1, PT=205, length=0xFFFF */ 0xde, 0xad, 0xbe, 0xef, /* sender SSRC */ 0xca, 0xfe, 0xba, 0xbe, /* media SSRC */ 0x00, 0x01, 0x00, 0x00, /* FCI word 0: PID=1, BLP=0 */ 0x00, 0x02, 0x00, 0x00 /* FCI word 1: PID=2, BLP=0 */ };

The header declares length = 0xFFFF words while only two FCI words are actually present. The test starts a PCMU call, enables RTCP plus the AUTOADJ, VIDEO, and NACK flags, creates a UDP socket, and over rtcp-mux (so feedback arrives on the RTP socket) repeatedly sends the packet and calls switch_rtp_read() so the source address is adopted and the datagram reaches the NACK parser:

for (i = 0; i < 4; i++) { send_len = sizeof(nack); if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char *)nack, &send_len) != SWITCH_STATUS_SUCCESS) { fst_fail("failed to send NACK packet"); goto nack_oob_cleanup; } rcvd_datalen = sizeof(rpacket); status = switch_rtp_read(rtp_session, (void *)rpacket, &rcvd_datalen, &pt, &frameflags, io_flags); }

Per the test comments, with the clamp in place the parser stays in bounds. Without the fix the read trips an out-of-bounds access, detected under AddressSanitizer or Valgrind on the switch_rtp_read() call, or producing heap corruption. To weaponize against an unpatched instance, an attacker would send a datagram structured like nack[] above to the negotiated video RTCP port of an active video call. The test was reviewed from source, not executed in this analysis.

Patch Information

FreeSWITCH fixed this bug in upstream commit e730a4e541b2cb49d54018a15f775728e19ce17f, a merge from a private security fork dated 2026-08-08. It changes src/switch_rtp.c (+29/-3) and tests/unit/switch_rtp_pcap.c (+96/-1). The first release carrying the fix is v1.11.2, published 2026-08-09. GitHub's compare API shows the fix commit is the merge base of v1.11.2 (the tag is 38 commits ahead and 0 behind), so the tag contains the fix. Advisory GHSA-f3xg-63wg-hjw4 marks versions <= 1.11.1 as vulnerable and >= 1.11.2 as patched. The v1.11.2 release notes list the change as "[core] Bound RTCP NACK feedback loop to validated length."

The fix introduces a helper, rtcp_nack_fci_count(), which compares the entry count the header claims against the number of entries that fit in the validated block, and returns the smaller one:

static int rtcp_nack_fci_count(const rtcp_ext_msg_t *extp, switch_size_t block_len) { int claimed, fits; if (block_len <= sizeof(switch_rtcp_ext_hdr_t)) { return 0; } claimed = ntohs(extp->header.length) - 2; if (claimed <= 0) { return 0; } fits = (int) ((block_len - sizeof(switch_rtcp_ext_hdr_t)) / sizeof(uint32_t)); return claimed < fits ? claimed : fits; }

The helper handles two edge cases. If the block is no bigger than the RTPFB header, it returns 0 before any subtraction, so the unsigned block_len - sizeof(switch_rtcp_ext_hdr_t) cannot wrap to a huge value. If the claimed count is zero or negative (header length below 3), it also returns 0.

The NACK branch now calls this helper once, passing the validated bytes count, and uses the result as the loop bound:

- int i; + int i, nack_count = rtcp_nack_fci_count(extp, bytes); ... - for (i = 0; i < ntohs(extp->header.length) - 2; i++) { + for (i = 0; i < nack_count; i++) { handle_nack(rtp_session, nack[i]);

The key change is which value the loop trusts. The packet's own length field is now only an upper limit; the real limit is the number of bytes actually received. The advisory notes that well-formed feedback behaves exactly as before, since the clamp only engages when the declared length exceeds the data received, which a conformant sender never produces. Malformed packets are not rejected outright. FreeSWITCH processes the entries that are actually present and ignores the rest. The commit also updates the test helper rtp_test_end_call() to null out session and RTP session pointers after teardown so later tests cannot touch freed objects.

One upstream check also listed an NTPsec 1.2.4 fix for an NTS-KE parser bug, which is a different vulnerability (CVE-2026-121505, GHSA-rjr3-53fv-wjrf) and unrelated to this FreeSWITCH CVE.

Affected Systems and Versions

  • Affected product: SignalWire FreeSWITCH
  • Affected versions: all versions up to and including 1.11.1 (that is, before 1.11.2)
  • Fixed version: 1.11.2 and later
  • Vulnerable function: process_rtcp_report() in src/switch_rtp.c
  • Vulnerable configuration: any active video call where RTCP is enabled for that call. Because remote SDP (a=rtcp-fb or a=rtcp:) can enable RTCP independently of local SIP profile settings, the absence of rtcp-audio-interval-msec or rtcp-video-interval-msec does not mean the path is unreachable. NACK negotiation is not required.

Vendor Security History

FreeSWITCH has previously addressed distinct media and signaling flaws, each with its own mechanism and fix. These are separate findings, not a shared exploit chain:

  • CVE-2021-41157: unauthenticated SIP SUBSCRIBE requests allowed access to event notifications by default, a privacy concern. Tested vulnerable through 1.10.5; the advisory lists 1.10.7 as fixed and warns that older upgraded installations may also need configuration changes.
  • CVE-2023-51443: a DTLS ClientHello race could tear down media setup and deny calls. Tested vulnerable in 1.10.10 and fixed in 1.10.11 by dropping packets from addresses not validated through ICE. Enable Security reported and triaged this on September 27, 2023, received a fix for testing on September 29, and the vendor release followed on December 22.
  • CVE-2026-116784: the current issue, oversized RTCP NACK declarations causing reads beyond the buffer and an instance wide call outage, fixed in 1.11.2 by bounding feedback processing.

The 2023 DTLS source validation fix should not be conflated with the current RTCP length correction; they address different failure mechanisms within media handling. FreeSWITCH's published security policy directs reports to [email protected] and describes creating collaborative advisories, maintaining contact during remediation, and asking researchers to verify fixes.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization