From: Vsevolod Stakhov Date: Sun, 2 Nov 2025 19:29:38 +0000 (+0000) Subject: [Fix] Fix allocator mismatches with jemalloc X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7c1e555a3fbb42e1a6f37a1f0300ea864e4076cd;p=thirdparty%2Frspamd.git [Fix] Fix allocator mismatches with jemalloc Resolve crashes caused by mixing system malloc and jemalloc allocators. The issue occurred when getline() and hiredis used system malloc, but rspamd's free() used jemalloc, causing segmentation faults on macOS and potentially other platforms. Changes: - Add rspamd_getline() wrapper using g_malloc/g_realloc/g_free to avoid system malloc in getline() - Replace getline() with rspamd_getline() in url.c, dns.c, lua_repl.c - Fix memory leak in lua_repl.c by freeing input buffer on exit - Configure hiredis allocators to use glib functions (jemalloc) in rspamd_init_libs() This ensures all memory operations use the same allocator (jemalloc) throughout rspamd, preventing allocator mismatch crashes. --- diff --git a/src/libserver/cfg_utils.cxx b/src/libserver/cfg_utils.cxx index 7bfe13f69e..1b7d6609ad 100644 --- a/src/libserver/cfg_utils.cxx +++ b/src/libserver/cfg_utils.cxx @@ -2727,6 +2727,17 @@ rspamd_config_ev_backend_to_string(int ev_backend, gboolean *effective) #undef SET_EFFECTIVE } +extern "C" { +#include "../../contrib/hiredis/alloc.h" +} + +/* Wrapper for calloc with correct signature for hiredis */ +static void * +rspamd_hiredis_calloc(size_t nmemb, size_t size) +{ + return g_malloc0_n(nmemb, size); +} + struct rspamd_external_libs_ctx * rspamd_init_libs(void) { @@ -2741,6 +2752,16 @@ rspamd_init_libs(void) rspamd_openssl_maybe_init(ctx); + /* Configure hiredis allocators to use glib (jemalloc) */ + hiredisAllocFuncs hiredis_allocators = { + .mallocFn = g_malloc, + .callocFn = rspamd_hiredis_calloc, + .reallocFn = g_realloc, + .strdupFn = g_strdup, + .freeFn = g_free, + }; + hiredisSetAllocators(&hiredis_allocators); + /* Check if we have rdrand */ if ((ctx->crypto_ctx->cpu_config & CPUID_RDRAND) == 0) { ottery_config_disable_entropy_sources(ottery_cfg, diff --git a/src/libserver/dns.c b/src/libserver/dns.c index f448b2a320..2c108c5679 100644 --- a/src/libserver/dns.c +++ b/src/libserver/dns.c @@ -724,7 +724,7 @@ rspamd_dns_read_hosts_file(struct rspamd_config *cfg, return false; } - while ((r = getline(&linebuf, &buflen, fp)) > 0) { + while ((r = rspamd_getline(&linebuf, &buflen, fp)) > 0) { if (linebuf[0] == '#' || g_ascii_isspace(linebuf[0])) { /* Skip comment or empty line */ continue; @@ -794,7 +794,7 @@ rspamd_dns_read_hosts_file(struct rspamd_config *cfg, } if (linebuf) { - free(linebuf); + rspamd_getline_free(linebuf); } msg_info_config("processed host file %s; %d records added", fname, nadded); diff --git a/src/libserver/url.c b/src/libserver/url.c index 137e5b73ce..7027fc02d7 100644 --- a/src/libserver/url.c +++ b/src/libserver/url.c @@ -435,7 +435,7 @@ rspamd_url_parse_tld_file(const char *fname, m.start = url_tld_start; m.prefix = "http://"; - while ((r = getline(&linebuf, &buflen, f)) > 0) { + while ((r = rspamd_getline(&linebuf, &buflen, f)) > 0) { if (linebuf[0] == '/' || g_ascii_isspace(linebuf[0])) { /* Skip comment or empty line */ continue; @@ -474,7 +474,7 @@ rspamd_url_parse_tld_file(const char *fname, g_array_append_val(url_scanner->matchers_full, m); } - free(linebuf); + rspamd_getline_free(linebuf); fclose(f); return TRUE; diff --git a/src/libutil/str_util.c b/src/libutil/str_util.c index 630e755030..040298beda 100644 --- a/src/libutil/str_util.c +++ b/src/libutil/str_util.c @@ -3884,3 +3884,53 @@ rspamd_str_has_8bit(const unsigned char *beg, gsize len) return rspamd_str_has_8bit_u64(beg, len); } + +gssize +rspamd_getline(char **lineptr, gsize *n, FILE *stream) +{ + char *line = *lineptr; + gsize len = *n; + gssize nread = 0; + int c; + + if (line == NULL || len == 0) { + len = 120; + line = g_malloc(len); + if (line == NULL) { + return -1; + } + } + + while ((c = fgetc(stream)) != EOF) { + if ((gsize) nread >= len - 1) { + gsize new_len = len * 2; + char *new_line = g_realloc(line, new_len); + if (new_line == NULL) { + return -1; + } + line = new_line; + len = new_len; + } + + line[nread++] = c; + + if (c == '\n') { + break; + } + } + + if (nread == 0 && c == EOF) { + return -1; + } + + line[nread] = '\0'; + *lineptr = line; + *n = len; + + return nread; +} + +void rspamd_getline_free(char *lineptr) +{ + g_free(lineptr); +} diff --git a/src/libutil/str_util.h b/src/libutil/str_util.h index a7252f3bfb..5d59785f36 100644 --- a/src/libutil/str_util.h +++ b/src/libutil/str_util.h @@ -558,6 +558,25 @@ char **rspamd_string_len_split(const char *in, gsize len, #define RSPAMD_LEN_CHECK_STARTS_WITH(s, len, lit) \ ((len) >= sizeof(lit) - 1 && g_ascii_strncasecmp((s), (lit), sizeof(lit) - 1) == 0) +/** + * Wrapper around getline(3) that handles allocator mismatches when jemalloc is enabled. + * When jemalloc is used, getline() allocates memory with system malloc, but rspamd's + * free() uses jemalloc, causing crashes. This function ensures proper cleanup. + * + * @param lineptr pointer to buffer (will be allocated/reallocated as needed) + * @param n pointer to buffer size + * @param stream file stream to read from + * @return number of characters read, or -1 on error + */ +gssize rspamd_getline(char **lineptr, gsize *n, FILE *stream); + +/** + * Free memory allocated by rspamd_getline using the correct allocator. + * + * @param lineptr buffer to free + */ +void rspamd_getline_free(char *lineptr); + #ifdef __cplusplus } #endif diff --git a/src/rspamadm/lua_repl.c b/src/rspamadm/lua_repl.c index f9099d8959..f95aadaa05 100644 --- a/src/rspamadm/lua_repl.c +++ b/src/rspamadm/lua_repl.c @@ -24,6 +24,7 @@ #include "lua/lua_thread_pool.h" #include "message.h" #include "unix-std.h" +#include "str_util.h" #include "replxx.h" #include "worker_util.h" @@ -601,10 +602,10 @@ rspamadm_lua_run_repl(lua_State *L, bool is_batch) for (;;) { if (is_batch) { - size_t linecap = 0; - ssize_t linelen; + gsize linecap = 0; + gssize linelen; - linelen = getline(&input, &linecap, stdin); + linelen = rspamd_getline(&input, &linecap, stdin); if (linelen > 0) { if (input[linelen - 1] == '\n') { @@ -691,6 +692,10 @@ rspamadm_lua_run_repl(lua_State *L, bool is_batch) } } } + + if (is_batch && input) { + rspamd_getline_free(input); + } } struct rspamadm_lua_repl_context {