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