]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
Merge commit from fork
authorDmitry Verenitsin <morbit85@gmail.com>
Tue, 26 May 2026 19:28:23 +0000 (00:28 +0500)
committerGitHub <noreply@github.com>
Tue, 26 May 2026 19:28:23 +0000 (22:28 +0300)
* [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.

.github/workflows/unit-test.yml
configure.ac
libs/esl/Makefile.am
libs/esl/src/esl.c
libs/esl/src/include/esl.h
libs/esl/tests/Makefile.am [new file with mode: 0644]
libs/esl/tests/test_recv_event.c [new file with mode: 0644]

index 32c1b2c580d136682a43c305d8d23ff2e696a560..5e414ab835827c721bfbdcb0eabb26601e08f9cf 100644 (file)
@@ -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
index bf6421e4e76482d9321df154411daa446f51b92c..1866ee5ca51f9d79263843e03cf4a56968238aea 100644 (file)
@@ -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
index 03f530d0bff2766473d6bbfc69c6b76be2cd7f9d..34ce19c53ad89edf84423ac8fccc7a56ba1297d8 100644 (file)
@@ -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)
index 6f085e26dba58674902116973eb24910b360e2cb..383b981a085d4392737bc3be285db7c1f1dd8f87 100644 (file)
@@ -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;
index 4d2baac871ad87c024f03df1e5354e1ba89d850a..ab1742be65c09bb843fb8ab9c6ff7a0bf664fe74 100644 (file)
@@ -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 (file)
index 0000000..40b6c7b
--- /dev/null
@@ -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 (file)
index 0000000..568e2a4
--- /dev/null
@@ -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