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.
This commit is contained in:
Dmitry Verenitsin
2026-05-27 00:28:23 +05:00
committed by GitHub
parent 02ac36bb11
commit 22de26cc7c
7 changed files with 134 additions and 6 deletions
+7
View 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
+1
View 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
+1 -1
View 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)
+13 -2
View File
@@ -1351,9 +1351,19 @@ ESL_DECLARE(esl_status_t) esl_recv_event(esl_handle_t *handle, int check_q, esl_
esl_ssize_t sofar = 0;
len = atol(cl);
if (len < 0 || len > ESL_MAX_CONTENT_LENGTH) {
esl_event_destroy(&revent);
goto fail;
}
body = malloc(len + 1);
esl_assert(body);
*(body + len) = '\0';
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;
+2
View 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>
+11
View File
@@ -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
+96
View File
@@ -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