]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] Fix allocator mismatches with jemalloc
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sun, 2 Nov 2025 19:29:38 +0000 (19:29 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sun, 2 Nov 2025 19:36:32 +0000 (19:36 +0000)
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.

src/libserver/cfg_utils.cxx
src/libserver/dns.c
src/libserver/url.c
src/libutil/str_util.c
src/libutil/str_util.h
src/rspamadm/lua_repl.c

index 7bfe13f69e9cf24191628aa491d2cb6efb3be17f..1b7d6609ad8dd794a831e16bda8b845914df365e 100644 (file)
@@ -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,
index f448b2a32028f34cd5ca891fb51bfd432ceef634..2c108c5679e6a55aa75b8a144ab305aa00a9a4f6 100644 (file)
@@ -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);
index 137e5b73ce241d132cb119287ad2523feb0d7f05..7027fc02d769e65a899794040ca41800c139fe0e 100644 (file)
@@ -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;
index 630e7550302348680a6ee9f4361858fb3a3f4485..040298bedaee0ab6ca3c0166ddf4626473146da3 100644 (file)
@@ -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);
+}
index a7252f3bfb2a03c092765caf8b065ff4abc0067f..5d59785f36af34f2a775542da1d722800a76daae 100644 (file)
@@ -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
index f9099d89595365d0e2ab3f9f10d7c309bc865d50..f95aadaa05530758ccac313ea887e27bbe018c0e 100644 (file)
@@ -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 {