Skip to content

Commit db8885a

Browse files
committed
mod_webmail: Fix memory leak when using SORT/SEARCH.
When using our custom libetpan function for SORT/SEARCH, the clist of results was not being freed afterwards since neither of the conditions to free was true. These conditions were correct, since the corresponding free functions cannot be used, but we still need to manually free the list. Basic tests have also been added for mod_webmail that captured this memory leak (and now pass). Related fixes: * mod_http: Don't compare Connection header value case-sensitively. Per RFC 7230, this value is not case-sensitive. The mod_webmail test uses it in a different case intentionally to test this. * net_ws: Remove dependency on net_http. Although you would probably want this module loaded, it is not actually a hard dependency, i.e. net_http exports no symbols required by net_ws to load. This eliminates some otherwise confusing error messages about dependencies failing to load, since it's not actually one. * tests/Makefile: Use $^ instead of *.o as the inputs for the test binary. I'm not sure how it ever worked the old way, since the object files for the test modules shouldn't get linked in. LBBS-99 #close
1 parent b21a70c commit db8885a

File tree

13 files changed

+383
-43
lines changed

13 files changed

+383
-43
lines changed

bbs/module.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ struct bbs_module *__bbs_require_module(const char *module, void *refmod)
310310
bbs_debug(5, "Module dependency '%s' is satisfied (required by %s)\n", module, reffing_mod->name);
311311
__bbs_module_ref(mod, 1, refmod, __FILE__, __LINE__, __func__);
312312
} else {
313-
bbs_warning("Module %s dependency is not satisfied (required by %s)\n", module, reffing_mod->name);
313+
bbs_error("Module %s dependency is not satisfied (required by %s)\n", module, reffing_mod->name);
314314
}
315315
return mod;
316316
}
@@ -953,7 +953,7 @@ static void dec_refcounts(struct bbs_module *mod)
953953
if (m) {
954954
__bbs_unrequire_module(m, mod);
955955
} else {
956-
bbs_warning("Dependency %s not currently loaded?\n", dependency);
956+
bbs_error("Dependency %s not currently loaded?\n", dependency);
957957
}
958958
}
959959
}
@@ -1077,7 +1077,9 @@ static int on_module(const char *dir_name, const char *filename, void *obj)
10771077
UNUSED(dir_name);
10781078
UNUSED(obj);
10791079

1080+
#ifdef EXTRA_DEBUG
10801081
bbs_debug(7, "Detected dynamic module %s\n", filename);
1082+
#endif
10811083
a = calloc(1, sizeof(*a) + strlen(filename) + 1);
10821084
if (ALLOC_FAILURE(a)) {
10831085
return -1;

modules/mod_http.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@ int http_websocket_upgrade_requested(struct http_session *http)
882882
{
883883
const char *value;
884884
value = http_request_header(http, "Connection");
885-
if (!strlen_zero(value) && !strcmp(value, "Upgrade")) {
885+
if (!strlen_zero(value) && !strcasecmp(value, "Upgrade")) { /* Connection header value is case-insensitive (RFC 7230 6.1) */
886886
value = http_request_header(http, "Upgrade");
887887
if (!strlen_zero(value) && !strcasecmp(value, "WebSocket")) {
888888
return 1;

modules/mod_mail.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2014,7 +2014,7 @@ int imap_client_login(struct bbs_tcp_client *client, struct bbs_url *url, struct
20142014
IMAP_CLIENT_SEND(client, "");
20152015
}
20162016
/* Won't get it, but at least see what the server had to say */
2017-
bbs_tcp_client_expect(client, "\r\n", 1, 2000, "a1 OK"); /* Don't use IMAP_CLIENT_EXPECT, or we'll bypass the warning below when it fails */
2017+
bbs_tcp_client_expect(client, "\r\n", 1, SEC_MS(7), "a1 OK"); /* Don't use IMAP_CLIENT_EXPECT, or we'll bypass the warning below when it fails */
20182018
bbs_warning("Login failed, got '%s'\n", client->buf);
20192019
return -1;
20202020
}

modules/mod_webmail.c

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,7 +1639,7 @@ static int mailimap_search_sort_fuller(mailimap *session, const char *sortkey, c
16391639
for (cur = clist_begin(session->imap_response_info->rsp_extension_list); cur; cur = clist_next(cur)) {
16401640
struct mailimap_extension_data *ext_data = clist_content(cur);
16411641
if (ext_data->ext_extension->ext_id == MAILIMAP_EXTENSION_SORT) {
1642-
if (sortkey && !sort_result) {
1642+
if (sortkey && !sort_result) { /* Sort */
16431643
sort_result = ext_data->ext_data;
16441644
ext_data->ext_data = NULL;
16451645
ext_data->ext_type = -1;
@@ -1650,7 +1650,7 @@ static int mailimap_search_sort_fuller(mailimap *session, const char *sortkey, c
16501650
clist_free(session->imap_response_info->rsp_extension_list);
16511651
session->imap_response_info->rsp_extension_list = NULL;
16521652

1653-
if (!sort_result) {
1653+
if (!sort_result) { /* Search only */
16541654
sort_result = session->imap_response_info->rsp_search_result;
16551655
session->imap_response_info->rsp_search_result = NULL;
16561656
}
@@ -1672,11 +1672,11 @@ static int mailimap_search_sort_fuller(mailimap *session, const char *sortkey, c
16721672

16731673
static struct mailimap_search_key *mailimap_search_key_new_type(int sk_type)
16741674
{
1675-
return mailimap_search_key_new(sk_type, NULL, NULL, NULL, NULL, NULL,
1676-
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL);
1675+
return mailimap_search_key_new(sk_type, NULL, NULL, NULL, NULL, NULL,
1676+
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL);
16771677
}
16781678

1679-
static clist *sortall(struct imap_client *client, const char *sort, const char *search, int *searched, int *sorted)
1679+
static clist *sortall(struct imap_client *client, const char *sort, const char *search, int *restrict searched, int *restrict sorted)
16801680
{
16811681
int res = -1;
16821682
clist *list = NULL;
@@ -1721,7 +1721,7 @@ static clist *sortall(struct imap_client *client, const char *sort, const char *
17211721
bbs_warning("Unsupported search criteria: '%s'\n", search);
17221722
}
17231723
}
1724-
*sorted = *searched = 0;
1724+
*sorted = *searched = 0; /* Yes, this is correct, neither should be set when we return as list is not exactly the same */
17251725
res = mailimap_search_sort_fuller(client->imap, sort, search, &list);
17261726
} else {
17271727
struct mailimap_sort_key *sortkey = NULL;
@@ -2030,6 +2030,7 @@ static int fetchlist(struct ws_session *ws, struct imap_client *client, const ch
20302030
json_t *root = NULL, *arr;
20312031
int added = 0;
20322032
int fetch_attempts = 0;
2033+
clist *sorted = NULL;
20332034

20342035
/* start to end is inclusive */
20352036
expected = end - start + 1;
@@ -2043,22 +2044,39 @@ static int fetchlist(struct ws_session *ws, struct imap_client *client, const ch
20432044
client_set_status(ws, "Your IMAP server does not support SORT");
20442045
sort = NULL; /* If server doesn't support sort, we can't sort */
20452046
}
2047+
2048+
/* The else case is to handle frees when LIBETPAN_SEARCH_KEYS_BROKEN_ABI is true (which it currently is).
2049+
* In this case, neither searchlist or sortlist will be set to 1, because mailimap_sort_result_free
2050+
* and mailimap_search_result_free are not the correct functions to use.
2051+
* In that case, we can just manually free the list and everything in it. */
2052+
#define FREE_SORT_SEARCH_LISTS() \
2053+
if (sortlist) { \
2054+
mailimap_sort_result_free(sorted); \
2055+
sorted = NULL; \
2056+
} else if (searchlist) { \
2057+
mailimap_search_result_free(sorted); \
2058+
sorted = NULL; \
2059+
} else { \
2060+
for (cur = clist_begin(sorted); cur; cur = clist_next(cur)) { \
2061+
uint32_t *s = clist_content(cur); \
2062+
free(s); \
2063+
} \
2064+
clist_free(sorted); \
2065+
sorted = NULL; \
2066+
}
2067+
20462068
/* Thankfully, SEARCH is not an extension, it should be supported by all IMAP servers */
20472069
if (sort || filter) {
20482070
int index = 1;
20492071
int searchlist, sortlist;
20502072
/*! \todo Cache this between FETCHLIST calls */
2051-
clist *sorted = sortall(client, sort, filter, &searchlist, &sortlist); /* This could be somewhat slow, since we have sort the ENTIRE mailbox every time */
2073+
sorted = sortall(client, sort, filter, &searchlist, &sortlist); /* This could be somewhat slow, since we have sort the ENTIRE mailbox every time */
20522074
if (!sorted) {
20532075
return -1;
20542076
}
20552077
set = mailimap_set_new_empty();
20562078
if (!set) {
2057-
if (sortlist) {
2058-
mailimap_sort_result_free(sorted);
2059-
} else if (searchlist) {
2060-
mailimap_search_result_free(sorted);
2061-
}
2079+
FREE_SORT_SEARCH_LISTS();
20622080
return -1;
20632081
}
20642082
if (filter) {
@@ -2088,28 +2106,22 @@ static int fetchlist(struct ws_session *ws, struct imap_client *client, const ch
20882106
added++;
20892107
if (res != MAILIMAP_NO_ERROR) {
20902108
bbs_error("Failed to add sorted seqno to list: %u\n", *seqno);
2091-
if (sortlist) {
2092-
mailimap_sort_result_free(sorted);
2093-
} else if (searchlist) {
2094-
mailimap_search_result_free(sorted);
2095-
}
2109+
FREE_SORT_SEARCH_LISTS();
20962110
return -1;
20972111
}
20982112
if (index >= end) {
20992113
break;
21002114
}
21012115
}
21022116
bbs_debug(6, "SEARCH/SORT matched %d result%s\n", added, ESS(added));
2103-
if (sortlist) {
2104-
mailimap_sort_result_free(sorted);
2105-
} else if (searchlist) {
2106-
mailimap_search_result_free(sorted);
2107-
}
2117+
FREE_SORT_SEARCH_LISTS();
21082118
} else {
21092119
set = mailimap_set_new_interval((uint32_t) start, (uint32_t) end);
21102120
added = (end - start + 1);
21112121
}
21122122

2123+
bbs_assert(sorted == NULL); /* Should've been freed by now */
2124+
21132125
client->start = start;
21142126
client->end = end;
21152127

nets/net_ws.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ static char phpsessprefix[84] = "";
4949

5050
static void ws_log(int level, int len, const char *file, const char *function, int line, const char *buf)
5151
{
52-
/*! \todo should be a version of __bbs_log that doesn't call asprintf, for efficiency (accepts # bytes) */
5352
switch (level) {
5453
case WS_LOG_ERROR:
5554
__bbs_log(LOG_ERROR, 0, file, line, function, "%.*s", len, buf);
@@ -1307,4 +1306,4 @@ static int load_module(void)
13071306
return 0;
13081307
}
13091308

1310-
BBS_MODULE_INFO_FLAGS_DEPENDENT("WebSocket Server", MODFLAG_GLOBAL_SYMBOLS, "mod_http.so,net_http.so");
1309+
BBS_MODULE_INFO_FLAGS_DEPENDENT("WebSocket Server", MODFLAG_GLOBAL_SYMBOLS, "mod_http.so");

scripts/run_tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ install_valgrind() {
7575

7676
if [ "$TEST" = "" ]; then # run all tests
7777
# First, do one pass without -e, in case there's a failure, it'll be caught much more quickly
78-
tests/test -ddddddddd -DDDDDDDDDD -x || handle_failure
78+
tests/test -dddddddddd -DDDDDDDDDD -x || handle_failure
7979
# If all good so far, repeat but under valgrind
8080
install_valgrind
81-
tests/test -ddddddddd -DDDDDDDDDD -ex || handle_failure
81+
tests/test -dddddddddd -DDDDDDDDDD -ex || handle_failure
8282
else
8383
# If we are only running a specific test, don't bother with the first pass, just run directly with the -e option (valgrind)
8484
install_valgrind

tests/Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ INCLUDE_FILES := $(wildcard ../include/*.h)
99
# the include directory is in the parent
1010
INC = -I..
1111

12+
TEST_LDFLAGS = -shared -fPIC
13+
1214
# Older versions of valgrind do not have the --show-error-list option
1315
VALGRIND_VERSION_MM := $(shell valgrind --version 2> /dev/null | cut -d'-' -f2 | cut -d'.' -f1-2)
1416
ifdef VALGRIND_VERSION_MM
@@ -32,10 +34,14 @@ test.o : test.c
3234
$(CC) $(CFLAGS) -Wno-unused-result -DTEST_IN_CORE -DTEST_DIR=$(CURDIR) $(EXTRA_FLAGS) $(INC) -c $^
3335

3436
$(TEST_EXE) : test.o readline.o
35-
$(CC) $(CFLAGS) -Wl,--export-dynamic -o $(TEST_EXE) *.o -ldl -lpthread
37+
$(CC) $(CFLAGS) -Wl,--export-dynamic -o $(TEST_EXE) $^ -ldl -lpthread
3638

3739
%.so : %.o
3840
@echo " [LD] $^ -> $@"
39-
$(CC) -shared -fPIC -o $(basename $^).so $^
41+
$(CC) $(TEST_LDFLAGS) -o $(basename $^).so $^
42+
43+
test_webmail.so : test_webmail.o
44+
@echo " [LD] $^ -> $@"
45+
$(CC) $(TEST_LDFLAGS) -o $(basename $^).so $^ -lwss
4046

4147
.PHONY: all

tests/net_ws.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[general]
2+
3+
[origins]
4+
http://localhost = allow
5+
6+
[ws]
7+
port=8143

tests/test.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ int test_client_expect_eventually_buf(int fd, int ms, const char *restrict s, in
374374
}
375375
buf[bytes] = '\0'; /* Safe */
376376
/* Probably ends in LF, so skip one here */
377-
bbs_debug(10, "Analyzing output: %s", buf); /* Particularly under valgrind, we'll end up reading individual lines more than chunks, so using CLIENT_DRAIN is especially important */
377+
bbs_debug(10, "Analyzing output(%d): %s", line, buf); /* Particularly under valgrind, we'll end up reading individual lines more than chunks, so using CLIENT_DRAIN is especially important */
378378
/* XXX Should use bbs_readline_append for reliability */
379379
if (strstr(buf, s)) {
380380
return 0;
@@ -1000,7 +1000,7 @@ static int run_test(const char *filename, int multiple)
10001000
res = -1;
10011001
goto cleanup;
10021002
}
1003-
bbs_debug(3, "Spawned child process %d\n", childpid);
1003+
bbs_debug(3, "Spawned child process %d (%s)\n", childpid, option_errorcheck ? "valgrind" : option_strace ? "strace" : "lbbs");
10041004
/* Wait for the BBS to fully start */
10051005
/* XXX If we could receive this event outside of the BBS process, that would be more elegant */
10061006
res = test_bbs_expect("BBS is fully started", SEC_MS(STARTUP_TIMEOUT));

tests/test.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ struct test_module *TEST_MODULE_SELF_SYM(void);
5353
#define TEST_NEWS_DIR DIRCAT(TEST_ROOT_DIR, "newsdir")
5454
#define TEST_TRANSFER_DIR DIRCAT(TEST_ROOT_DIR, "ftp")
5555
#define TEST_HOME_DIR_ROOT DIRCAT(TEST_TRANSFER_DIR, "home")
56+
#define TEST_WWW_DIR DIRCAT(TEST_ROOT_DIR, "www")
5657

5758
/* Yuck, but why reinvent the wheel */
5859
#define TEST_ADD_CONFIG(filename) system("cp " filename " " TEST_CONFIG_DIR)
@@ -68,6 +69,7 @@ struct test_module *TEST_MODULE_SELF_SYM(void);
6869

6970
#define TEST_USER "testuser"
7071
#define TEST_PASS "P@ssw0rd"
72+
#define TEST_PASS_BASE64 "UEBzc3cwcmQ="
7173
#define TEST_HASH "$2y$10$1vtttulZgw5Sz.Ks8PePFumnPCztHfp0YzgHLnuIQ1vAb0mSQpv2q"
7274
#define TEST_SASL "dGVzdHVzZXIAdGVzdHVzZXIAUEBzc3cwcmQ="
7375
#define TEST_EMAIL TEST_USER "@" TEST_HOSTNAME

0 commit comments

Comments
 (0)