CVE-2026-49840
Description
FreeSWITCH versions prior to 1.11.1 are vulnerable to a heap corruption or crash via a negative Content-Length in ESL frames before authentication.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
FreeSWITCH versions prior to 1.11.1 are vulnerable to a heap corruption or crash via a negative Content-Length in ESL frames before authentication.
Vulnerability
FreeSWITCH versions prior to 1.11.1 are vulnerable to a heap corruption or crash due to improper parsing of the Content-Length header in ESL frames by the esl_recv_event() function. The function uses atol() to parse the length and passes the result directly to malloc(len + 1) without checking for negative values or excessive magnitude. This affects fs_cli, mod_esl, mod_hash, and any third-party application linked against libesl. The outbound socket dialplan application in mod_event_socket is not affected as it uses its own ESL implementation [1].
Exploitation
An attacker or man-in-the-middle attacker can send a specially crafted ESL frame with a negative Content-Length value to a connecting libesl client. This occurs before the client has authenticated to the ESL peer. The malicious frame can be the first event the client reads, requiring no credentials or prior interaction. If Content-Length is -1, a heap buffer overflow occurs. If Content-Length is -2 or lower, a NULL pointer dereference leads to a crash [1].
Impact
Successful exploitation can lead to heap corruption or a crash of the affected process. A heap buffer overflow with attacker-controlled length and contents is possible, though remote code execution has not been demonstrated. In cases of a negative Content-Length less than -1, the process will crash due to dereferencing a NULL pointer returned by malloc [1].
Mitigation
This issue has been fixed in FreeSWITCH version 1.11.1, released on 2026-06-09 [2]. Operators who cannot upgrade immediately should restrict which hosts libesl clients connect to and ensure the control-plane network is unreachable from less-trusted hosts. No reliable workaround is available [1].
AI Insight generated on Jun 9, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)range: <1.11.1
Patches
222de26cc7ca5Merge commit from fork
7 files changed · +134 −6
configure.ac+1 −0 modified@@ -2145,6 +2145,7 @@ AC_CONFIG_FILES([Makefile build/standalone_module/freeswitch.pc build/modmake.rules libs/esl/Makefile + libs/esl/tests/Makefile libs/esl/perl/Makefile libs/esl/php/Makefile libs/xmlrpc-c/include/xmlrpc-c/config.h
.github/workflows/unit-test.yml+7 −0 modified@@ -107,6 +107,13 @@ jobs: run: | ./run-tests.sh ${{ inputs.total-groups }} ${{ inputs.current-group }} --output-dir logs || exit 1 + - name: Run libesl tests + if: ${{ inputs.current-group == 1 }} + shell: bash + working-directory: ${{ inputs.working-directory }}/../../libs/esl + run: | + make check + - name: Collect unit test logs if: always() shell: bash
libs/esl/Makefile.am+1 −1 modified@@ -1,5 +1,5 @@ AUTOMAKE_OPTIONS = foreign subdir-objects -SUBDIRS = . perl +SUBDIRS = . perl tests MYLIB=./.libs/libesl.a LIBS=-lncurses -lpthread -lm LDFLAGS=-L. $(SYSTEM_LDFLAGS)
libs/esl/src/esl.c+16 −5 modified@@ -1349,12 +1349,22 @@ ESL_DECLARE(esl_status_t) esl_recv_event(esl_handle_t *handle, int check_q, esl_ if ((cl = esl_event_get_header(revent, "content-length"))) { char *body; esl_ssize_t sofar = 0; - + len = atol(cl); - body = malloc(len+1); - esl_assert(body); - *(body + len) = '\0'; - + + if (len < 0 || len > ESL_MAX_CONTENT_LENGTH) { + esl_event_destroy(&revent); + goto fail; + } + + body = malloc(len + 1); + if (!body) { + esl_event_destroy(&revent); + goto fail; + } + + body[len] = '\0'; + do { esl_ssize_t r,s = esl_buffer_inuse(handle->packet_buf); @@ -1367,6 +1377,7 @@ ESL_DECLARE(esl_status_t) esl_recv_event(esl_handle_t *handle, int check_q, esl_ if (!(strerror_r(handle->errnum, handle->err, sizeof(handle->err)))) *(handle->err)=0; free(body); + esl_event_destroy(&revent); goto fail; } else if (r == 0) { continue;
libs/esl/src/include/esl.h+2 −0 modified@@ -217,6 +217,8 @@ typedef enum { #define esl_strlen_zero_buf(s) (*(s) == '\0') #define end_of(_s) *(*_s == '\0' ? _s : _s + strlen(_s) - 1) +#define ESL_MAX_CONTENT_LENGTH (16 * 1024 * 1024) + #ifdef WIN32 #include <winsock2.h> #include <windows.h>
libs/esl/tests/Makefile.am+11 −0 added@@ -0,0 +1,11 @@ +AUTOMAKE_OPTIONS = foreign + +if BUILD_TESTS +noinst_PROGRAMS = test_recv_event +TESTS = $(noinst_PROGRAMS) + +test_recv_event_SOURCES = test_recv_event.c +test_recv_event_CFLAGS = $(AM_CFLAGS) -I$(switch_srcdir)/libs/esl/src/include +test_recv_event_LDADD = $(top_builddir)/libs/esl/libesl.la +test_recv_event_LDFLAGS = $(AM_LDFLAGS) -lpthread -lm +endif
libs/esl/tests/test_recv_event.c+96 −0 added@@ -0,0 +1,96 @@ +/* + * test_recv_event.c + * + * Verifies that esl_recv_event() rejects out-of-range Content-Length + * values: negative numbers and values above ESL_MAX_CONTENT_LENGTH must + * cause the function to return ESL_FAIL and mark the handle as + * disconnected, leaving no allocated state behind. + * + * POSIX-only: uses socketpair(2). Returns 77 on Windows so automake + * marks the test as skipped. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef _WIN32 + +int main(void) +{ + return 77; +} + +#else + +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <esl.h> + +#define TEST_ASSERT(cond) do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", \ + __FILE__, __LINE__, #cond); \ + exit(1); \ + } \ +} while (0) + +static void prepare_handle(esl_handle_t *h, esl_socket_t s) +{ + memset(h, 0, sizeof(*h)); + h->sock = s; + h->connected = 1; + TEST_ASSERT(esl_mutex_create(&h->mutex) == ESL_SUCCESS); + TEST_ASSERT(esl_buffer_create(&h->packet_buf, + BUF_CHUNK, BUF_START, 0) == ESL_SUCCESS); +} + +static void expect_rejected(const char *frame, const char *desc) +{ + int sv[2]; + esl_handle_t h; + size_t n = strlen(frame); + ssize_t w; + + fprintf(stderr, " case: %s\n", desc); + + TEST_ASSERT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0); + + prepare_handle(&h, sv[0]); + + w = write(sv[1], frame, n); + TEST_ASSERT(w == (ssize_t) n); + close(sv[1]); + + TEST_ASSERT(esl_recv_event(&h, 0, NULL) == ESL_FAIL); + TEST_ASSERT(h.connected == 0); + + esl_disconnect(&h); +} + +int main(void) +{ + fprintf(stderr, "test_recv_event: invalid Content-Length is rejected\n"); + + expect_rejected( + "Content-Type: text/event-plain\n" + "Content-Length: -1\n\n", + "negative Content-Length: -1"); + + expect_rejected( + "Content-Type: text/event-plain\n" + "Content-Length: -2\n\n", + "negative Content-Length: -2"); + + expect_rejected( + "Content-Type: text/event-plain\n" + "Content-Length: 99999999999\n\n", + "Content-Length above ESL_MAX_CONTENT_LENGTH"); + + fprintf(stderr, "OK\n"); + return 0; +} + +#endif
67b62fb969a6Merge commit from fork
1 file changed · +14 −3
src/mod/endpoints/mod_verto/mod_verto.c+14 −3 modified@@ -43,6 +43,7 @@ SWITCH_MODULE_DEFINITION(mod_verto, mod_verto_load, mod_verto_shutdown, mod_vert #define HTTP_CHUNK_SIZE 1024 * 32 #define HTTP_POST_MAX_BODY (10 * 1024 * 1024) /* max accepted Content-Length for form-urlencoded POST */ #define EP_NAME "verto.rtc" +#define VERTO_SPEED_TEST_MAX_SIZE (10 * 1024 * 1024) //#define WSS_STANDALONE 1 #include "libks/ks.h" @@ -2112,24 +2113,34 @@ static void client_run(jsock_t *jsock) char repl[2048] = ""; switch_time_t a, b; + if (!switch_test_flag(jsock, JPFLAG_AUTHED)) { + die("%s Speed-test request before authentication\n", jsock->name); + } + + if (bytes < 4) { + continue; + } + if (s[1] == 'S' && s[2] == 'P') { if (s[3] == 'U') { - int i, size = 0; + int i; + long size; char *p = s+4; int loops = 0; int rem = 0; int dur = 0, j = 0; - if ((size = atoi(p)) <= 0) { + size = strtol(p, NULL, 10); + if (size <= 0 || size > VERTO_SPEED_TEST_MAX_SIZE) { continue; } a = switch_time_now(); do { bytes = kws_read_frame(jsock->ws, &oc, &data); s = (char *) data; - } while (bytes && data && s[0] == '#' && s[3] == 'B'); + } while (bytes >= 4 && data && s[0] == '#' && s[3] == 'B'); b = switch_time_now(); if (!bytes || !data) continue;
Vulnerability mechanics
Root cause
"The esl_recv_event function mishandles the Content-Length header, allowing negative or excessively large values."
Attack vector
A malicious or man-in-the-middle ESL peer can send a specially crafted frame with a negative or oversized Content-Length header to a process linked against libesl. This occurs before the client has authenticated to the peer. The function `esl_recv_event` parses this value using `atol()` and then attempts to allocate memory based on it, leading to heap corruption or a crash [patch_id=5390373].
Affected code
The vulnerability exists in the `esl_recv_event` function within `libs/esl/src/esl.c`. Specifically, the parsing of the `content-length` header and subsequent memory allocation using `malloc(len+1)` are affected.
What the fix does
The patch modifies `esl_recv_event` to validate the `Content-Length` header. It now rejects negative values and values exceeding `ESL_MAX_CONTENT_LENGTH` (16 MiB). Additionally, it checks for `malloc` failures instead of relying on asserts and caps the maximum accepted length [patch_id=5390373]. This prevents heap corruption and crashes by ensuring memory allocation is safe.
Preconditions
- inputThe attacker must be able to send a malformed ESL frame with an invalid Content-Length header.
- networkThe attacker must be able to act as an ESL peer or a man-in-the-middle.
Generated on Jun 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.