From 22de26cc7ca5e05c5300306a285024c221ba9d27 Mon Sep 17 00:00:00 2001 From: Dmitry Verenitsin Date: Wed, 27 May 2026 00:28:23 +0500 Subject: [PATCH] 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. --- .github/workflows/unit-test.yml | 7 +++ configure.ac | 1 + libs/esl/Makefile.am | 2 +- libs/esl/src/esl.c | 21 +++++-- libs/esl/src/include/esl.h | 2 + libs/esl/tests/Makefile.am | 11 ++++ libs/esl/tests/test_recv_event.c | 96 ++++++++++++++++++++++++++++++++ 7 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 libs/esl/tests/Makefile.am create mode 100644 libs/esl/tests/test_recv_event.c 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 #include 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 +#include +#include + +#ifdef _WIN32 + +int main(void) +{ + return 77; +} + +#else + +#include +#include +#include + +#include + +#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