Commit 22de26cc7c for freeswitch.com
commit 22de26cc7ca5e05c5300306a285024c221ba9d27
Author: Dmitry Verenitsin <morbit85@gmail.com>
Date: Wed May 27 00:28:23 2026 +0500
Merge commit from fork
* [libesl] Validate `Content-Length` in `esl_recv_event`.
`atol()` accepted negative values, allowing a remote ESL peer to cause
a one-byte heap underwrite (`Content-Length: -1`) or NULL-pointer
dereference (`Content-Length: -2`, since `esl_assert` compiles out
under `NDEBUG`). Reject negative and oversized values, and check
`malloc` failure instead of relying on `assert`.
Cap at `ESL_MAX_CONTENT_LENGTH` (16 MiB).
* [libesl] Add test_recv_event.
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index 32c1b2c580..5e414ab835 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -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
diff --git a/configure.ac b/configure.ac
index bf6421e4e7..1866ee5ca5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/libs/esl/Makefile.am b/libs/esl/Makefile.am
index 03f530d0bf..34ce19c53a 100644
--- a/libs/esl/Makefile.am
+++ b/libs/esl/Makefile.am
@@ -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)
diff --git a/libs/esl/src/esl.c b/libs/esl/src/esl.c
index 6f085e26db..383b981a08 100644
--- a/libs/esl/src/esl.c
+++ b/libs/esl/src/esl.c
@@ -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;
diff --git a/libs/esl/src/include/esl.h b/libs/esl/src/include/esl.h
index 4d2baac871..ab1742be65 100644
--- a/libs/esl/src/include/esl.h
+++ b/libs/esl/src/include/esl.h
@@ -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>
diff --git a/libs/esl/tests/Makefile.am b/libs/esl/tests/Makefile.am
new file mode 100644
index 0000000000..40b6c7b6fc
--- /dev/null
+++ b/libs/esl/tests/Makefile.am
@@ -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
diff --git a/libs/esl/tests/test_recv_event.c b/libs/esl/tests/test_recv_event.c
new file mode 100644
index 0000000000..568e2a467b
--- /dev/null
+++ b/libs/esl/tests/test_recv_event.c
@@ -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