]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Preserve errno in our malloc() and free() wrappers
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 26 Feb 2026 10:29:12 +0000 (12:29 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 26 Feb 2026 10:32:15 +0000 (12:32 +0200)
Various places assume that e.g. t_strdup_printf() calls and such don't
modify errno. But because they internally call malloc() or calloc(), this
isn't actually guaranteed now and it can happen at least with newer glibc
versions. Explicitly preserve the errno for these calls where it might
be a problem.

src/lib/data-stack.c
src/lib/mempool-allocfree.c
src/lib/mempool-alloconly.c
src/lib/mempool-system.c

index 9205c6fba68ec60e0e3e5824f899832183d4873c..09766b804c29978ceedc29ce88af079c8b84e819 100644 (file)
@@ -214,6 +214,7 @@ static void block_canary_check(struct stack_block *block)
 static void free_blocks(struct stack_block *block)
 {
        struct stack_block *next;
+       int old_errno = errno;
 
        /* free all the blocks, except if any of them is bigger than
           unused_block, replace it */
@@ -237,6 +238,7 @@ static void free_blocks(struct stack_block *block)
 
                block = next;
        }
+       errno = old_errno;
 }
 
 #ifdef DEBUG
@@ -367,6 +369,7 @@ static struct stack_block *mem_block_alloc(size_t min_size)
 {
        struct stack_block *block;
        size_t prev_size, alloc_size;
+       int old_errno = errno;
 
        prev_size = current_block == NULL ? 0 : current_block->size;
        /* Use INITIAL_STACK_SIZE without growing it to nearest power. */
@@ -386,6 +389,7 @@ static struct stack_block *mem_block_alloc(size_t min_size)
                i_panic("data stack: Out of memory when allocating %zu bytes",
                        alloc_size + SIZEOF_MEMBLOCK);
        }
+       errno = old_errno;
        block->size = alloc_size;
        block->canary = BLOCK_CANARY;
        mem_block_reset(block);
@@ -457,9 +461,7 @@ static void *t_malloc_real(size_t size, bool permanent)
        void *ret;
        size_t alloc_size;
        bool warn = FALSE;
-#ifdef DEBUG
        int old_errno = errno;
-#endif
 
        if (unlikely(size == 0 || size > SSIZE_T_MAX))
                i_panic("Trying to allocate %zu bytes", size);
@@ -519,17 +521,9 @@ static void *t_malloc_real(size_t size, bool permanent)
                current_block->left -= alloc_size;
 
        if (warn) T_BEGIN {
-               /* sending event can cause errno changes. */
-#ifdef DEBUG
-               i_assert(errno == old_errno);
-#else
-               int old_errno = errno;
-#endif
                /* warn after allocation, so if e_debug() wants to
                   allocate more memory we don't go to infinite loop */
                data_stack_send_grow_event(alloc_size);
-               /* reset errno back to what it was */
-               errno = old_errno;
        } T_END;
 #ifdef DEBUG
        memcpy(ret, &size, sizeof(size));
@@ -538,10 +532,8 @@ static void *t_malloc_real(size_t size, bool permanent)
           had used t_buffer_get(). */
        memset(PTR_OFFSET(ret, size), CLEAR_CHR,
               MEM_ALIGN(size + SENTRY_COUNT) - size);
-
-       /* we rely on errno not changing. it shouldn't. */
-       i_assert(errno == old_errno);
 #endif
+       errno = old_errno;
        return ret;
 }
 
@@ -739,8 +731,10 @@ size_t data_stack_get_used_size(void)
 
 void data_stack_free_unused(void)
 {
+       int old_errno = errno;
        free(unused_block);
        unused_block = NULL;
+       errno = old_errno;
 }
 
 void data_stack_init(void)
index 239db03e0e89713f3cfcc2cac2c67ee2a5ecb228..b2f6f277e7ef1c6572f6b0fd2cbe5df7c5908bb3 100644 (file)
@@ -146,6 +146,7 @@ static const struct pool static_allocfree_pool = {
 pool_t pool_allocfree_create(const char *name ATTR_UNUSED)
 {
        struct allocfree_pool *pool;
+       int old_errno = errno;
 
        (void) COMPILE_ERROR_IF_TRUE(SIZEOF_POOLBLOCK >
                                     (SSIZE_T_MAX - POOL_MAX_ALLOC_SIZE));
@@ -154,6 +155,7 @@ pool_t pool_allocfree_create(const char *name ATTR_UNUSED)
        if (pool == NULL)
                i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory",
                               SIZEOF_ALLOCFREE_POOL);
+       errno = old_errno;
 #ifdef DEBUG
        pool->name = strdup(name);
 #endif
@@ -175,6 +177,8 @@ pool_t pool_allocfree_create_clean(const char *name)
 
 static void pool_allocfree_destroy(struct allocfree_pool *apool)
 {
+       int old_errno = errno;
+
        pool_allocfree_clear(&apool->pool);
        if (apool->clean_frees)
                safe_memset(apool, 0, SIZEOF_ALLOCFREE_POOL);
@@ -182,6 +186,7 @@ static void pool_allocfree_destroy(struct allocfree_pool *apool)
        free(apool->name);
 #endif
        free(apool);
+       errno = old_errno;
 }
 
 static const char *pool_allocfree_get_name(pool_t pool ATTR_UNUSED)
@@ -256,11 +261,13 @@ static void *pool_allocfree_malloc(pool_t pool, size_t size)
 {
        struct allocfree_pool *apool =
                container_of(pool, struct allocfree_pool, pool);
+       int old_errno = errno;
 
        struct pool_block *block = calloc(1, SIZEOF_POOLBLOCK + size);
        if (block == NULL)
                i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory",
                               SIZEOF_POOLBLOCK + size);
+       errno = old_errno;
        block->size = size;
        return pool_block_attach(apool, block);
 }
@@ -269,10 +276,12 @@ static void pool_allocfree_free(pool_t pool, void *mem)
 {
        struct allocfree_pool *apool =
                container_of(pool, struct allocfree_pool, pool);
+       int old_errno = errno;
        struct pool_block *block = pool_block_detach(apool, mem);
        if (apool->clean_frees)
                safe_memset(block, 0, SIZEOF_POOLBLOCK+block->size);
        free(block);
+       errno = old_errno;
 }
 
 static void *pool_allocfree_realloc(pool_t pool, void *mem,
@@ -281,6 +290,7 @@ static void *pool_allocfree_realloc(pool_t pool, void *mem,
        struct allocfree_pool *apool =
                container_of(pool, struct allocfree_pool, pool);
        unsigned char *new_mem;
+       int old_errno = errno;
 
        struct pool_block *block = pool_block_detach(apool, mem);
        if (old_size == SIZE_MAX)
@@ -288,6 +298,7 @@ static void *pool_allocfree_realloc(pool_t pool, void *mem,
        if ((new_mem = realloc(block, SIZEOF_POOLBLOCK+new_size)) == NULL)
                i_fatal_status(FATAL_OUTOFMEM, "realloc(block, %zu)",
                               SIZEOF_POOLBLOCK+new_size);
+       errno = old_errno;
 
        /* zero out new memory */
        if (new_size > old_size)
index 79823dc2040750408b97f6b6e525aa5eb91df507..e1b0925bfc41d523dffc5aad421b0e8ec1d449f5 100644 (file)
@@ -281,6 +281,8 @@ pool_t pool_alloconly_create_clean(const char *name, size_t size)
 static void pool_alloconly_free_block(struct alloconly_pool *apool ATTR_UNUSED,
                                      struct pool_block *block)
 {
+       int old_errno = errno;
+
 #ifdef DEBUG
        safe_memset(block, CLEAR_CHR, SIZEOF_POOLBLOCK + block->size);
 #else
@@ -290,6 +292,7 @@ static void pool_alloconly_free_block(struct alloconly_pool *apool ATTR_UNUSED,
        }
 #endif
        free(block);
+       errno = old_errno;
 }
 
 static void
@@ -381,11 +384,13 @@ static void block_alloc(struct alloconly_pool *apool, size_t size)
 #endif
        }
 
+       int old_errno = errno;
        block = calloc(size, 1);
        if (unlikely(block == NULL)) {
                i_fatal_status(FATAL_OUTOFMEM, "block_alloc(%zu"
                               "): Out of memory", size);
        }
+       errno = old_errno;
        block->prev = apool->block;
        apool->block = block;
 
index fc936a158970ee3aa9c568c7b4e0a7f3d1d1e30d..2235d2eeddd8258124f1d68fa63aa0be53230843 100644 (file)
@@ -97,36 +97,28 @@ static void pool_system_unref(pool_t *pool ATTR_UNUSED)
 
 static void *pool_system_malloc(pool_t pool ATTR_UNUSED, size_t size)
 {
-       void *mem;
-#ifdef DEBUG_FAST
        int old_errno = errno;
-#endif
+       void *mem;
 
        mem = calloc(size, 1);
        if (unlikely(mem == NULL)) {
                i_fatal_status(FATAL_OUTOFMEM, "pool_system_malloc(%zu): "
                               "Out of memory", size);
        }
-#ifdef DEBUG_FAST
-       /* we rely on errno not changing. it shouldn't. */
-       i_assert(errno == old_errno);
-#endif
+       errno = old_errno;
        return mem;
 }
 
-void pool_system_free(pool_t pool ATTR_UNUSED, void *mem ATTR_UNUSED)
+void pool_system_free(pool_t pool ATTR_UNUSED, void *mem)
 {
-#ifdef DEBUG_FAST
        int old_errno = errno;
-#endif
+
 #if defined(HAVE_MALLOC_USABLE_SIZE) && defined(DEBUG)
-       safe_memset(mem, CLEAR_CHR, malloc_usable_size(mem));
+       if (mem != NULL)
+               safe_memset(mem, CLEAR_CHR, malloc_usable_size(mem));
 #endif
        free(mem);
-#ifdef DEBUG_FAST
-       /* we rely on errno not changing. it shouldn't. */
-       i_assert(errno == old_errno);
-#endif
+       errno = old_errno;
 }
 
 static void *pool_system_realloc(pool_t pool ATTR_UNUSED, void *mem,
@@ -137,11 +129,13 @@ static void *pool_system_realloc(pool_t pool ATTR_UNUSED, void *mem,
                 old_size <= malloc_usable_size(mem));
 #endif
 
+       int old_errno = errno;
        mem = realloc(mem, new_size);
        if (unlikely(mem == NULL)) {
                i_fatal_status(FATAL_OUTOFMEM, "pool_system_realloc(%zu): "
                               "Out of memory", new_size);
        }
+       errno = old_errno;
 
        if (old_size < new_size) {
                /* clear new data */