]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
test: add a sanity test for memory allocation functions
authorEugene Syromiatnikov <esyr@openssl.org>
Fri, 25 Jul 2025 17:08:18 +0000 (19:08 +0200)
committerNeil Horman <nhorman@openssl.org>
Fri, 8 Aug 2025 16:22:10 +0000 (12:22 -0400)
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <ppzgs1@gmail.com>
Reviewed-by: Neil Horman <nhorman@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28059)

test/build.info
test/mem_alloc_custom_fns_test.c [new file with mode: 0644]
test/mem_alloc_test.c [new file with mode: 0644]
test/recipes/02-test_mem_alloc.t [new file with mode: 0644]
test/recipes/02-test_mem_alloc_custom_fns.t [new file with mode: 0644]

index e22c9005388286120568bf349bb2a5780dd8c5ef..d9cc4dae9ce4f13a01c0ae4a0549a8cd2c3725e3 100644 (file)
@@ -1238,6 +1238,16 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[bio_prefix_text]=.. ../include ../apps/include
   DEPEND[bio_prefix_text]=../libcrypto libtestutil.a
 
+  PROGRAMS{noinst}=mem_alloc_test
+  SOURCE[mem_alloc_test]=mem_alloc_test.c
+  INCLUDE[mem_alloc_test]=../include ../apps/include
+  DEPEND[mem_alloc_test]=../libcrypto libtestutil.a
+
+  PROGRAMS{noinst}=mem_alloc_custom_fns_test
+  SOURCE[mem_alloc_custom_fns_test]=mem_alloc_custom_fns_test.c
+  INCLUDE[mem_alloc_custom_fns_test]=../include ../apps/include
+  DEPEND[mem_alloc_custom_fns_test]=../libcrypto libtestutil.a
+
   IF[{- !$disabled{'deprecated-3.0'} -}]
     PROGRAMS{noinst}=pem_read_depr_test
     SOURCE[pem_read_depr_test]=pem_read_depr_test.c
diff --git a/test/mem_alloc_custom_fns_test.c b/test/mem_alloc_custom_fns_test.c
new file mode 100644 (file)
index 0000000..2b0a7a2
--- /dev/null
@@ -0,0 +1,2 @@
+#define USE_CUSTOM_ALLOC_FNS 1
+#include "mem_alloc_test.c"
diff --git a/test/mem_alloc_test.c b/test/mem_alloc_test.c
new file mode 100644 (file)
index 0000000..ae5895b
--- /dev/null
@@ -0,0 +1,869 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <openssl/err.h>
+#include <openssl/types.h>
+#include "testutil.h"
+
+#ifndef USE_CUSTOM_ALLOC_FNS
+# define USE_CUSTOM_ALLOC_FNS 0
+#endif
+
+/* Change to 1 to see every call of the custom allocator functions */
+#define CUSTOM_FN_PRINT_CALLS 0
+
+enum exp_ret_flags { EXP_FAIL = 0x10 };
+
+enum exp_ret {
+    /** Expecting success */
+    EXP_NONNULL,
+    /** Zero-size special case: can either return NULL or a special pointer */
+    EXP_ZERO_SIZE,
+    /** Expecting an error due to insufficient memory */
+    EXP_OOM = EXP_FAIL,
+    /** Expecting error due to invalid arguments */
+    EXP_INVAL,
+    /** Expecting error due to integer overflow */
+    EXP_INT_OF,
+};
+
+#define IS_FAIL(exp_) (!!((int) (exp) & (int) EXP_FAIL))
+
+static const char test_fn[] = "test_file_name";
+enum { test_line = 31415926 };
+
+#define SQRT_SIZE_T ((size_t) 1 << (sizeof(size_t) * (CHAR_BIT / 2)))
+#define SQSQRT_SIZE_T ((size_t) 1 << (sizeof(size_t) * (CHAR_BIT / 4)))
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#if !defined(OPENSSL_NO_CRYPTO_MDEBUG) || USE_CUSTOM_ALLOC_FNS
+struct call_counts {
+    int malloc;
+    int realloc;
+    int free;
+};
+#endif
+#if !defined(OPENSSL_NO_CRYPTO_MDEBUG)
+static struct call_counts mdebug_counts;
+#endif
+#if USE_CUSTOM_ALLOC_FNS
+static struct call_counts saved_custom_counts, cur_custom_counts;
+#endif
+
+static const struct array_alloc_vector {
+    size_t nmemb;
+    size_t size;
+    enum exp_ret exp_malloc;
+    enum exp_ret exp_calloc;
+} array_alloc_vectors[] = {
+    { 0,                0,                 EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0,                1,                 EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0,                SQRT_SIZE_T - 1,   EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0,                SQRT_SIZE_T,       EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0,                SIZE_MAX,          EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 1,                0,                 EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { SQRT_SIZE_T - 1,  0,                 EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { SIZE_MAX,         0,                 EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+
+    { 1,                1,                 EXP_NONNULL,   EXP_NONNULL   },
+
+    { SQRT_SIZE_T / 2,  SQRT_SIZE_T,       EXP_OOM,       EXP_OOM       },
+
+    { SQRT_SIZE_T,      SQRT_SIZE_T,       EXP_ZERO_SIZE, EXP_INT_OF    },
+
+    /* Some magic numbers */
+#if SIZE_MAX == 4294967295U
+    { 641, 6700417, EXP_NONNULL, EXP_INT_OF },
+#else /* Of course there are no archutectures other than 32- and 64-bit ones */
+    { 274177, 67280421310721LLU, EXP_NONNULL, EXP_INT_OF },
+#endif
+
+    { SIZE_MAX / 4 * 3, SIZE_MAX / 2, EXP_OOM, EXP_INT_OF },
+};
+
+static const struct array_realloc_vector {
+    size_t size;
+    size_t orig_nmemb;
+    size_t new_nmemb;
+    enum exp_ret exp_orig;
+    enum exp_ret exp_new;
+    enum exp_ret exp_orig_array;
+    enum exp_ret exp_new_array;
+} array_realloc_vectors[] = {
+    { 0, 0, 0,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 0, 1,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 0, SIZE_MAX,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 1, 0,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, SIZE_MAX, 0,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 1, SIZE_MAX,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, SIZE_MAX, 1,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, SIZE_MAX, SIZE_MAX,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+
+    { 1, 0, 0,
+      EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 1, 0, 1,
+      EXP_ZERO_SIZE, EXP_NONNULL,   EXP_ZERO_SIZE, EXP_NONNULL },
+    { 1, 0, SIZE_MAX,
+      EXP_ZERO_SIZE, EXP_OOM,       EXP_ZERO_SIZE, EXP_OOM },
+    { 1, 1, 0,
+      EXP_NONNULL,   EXP_ZERO_SIZE, EXP_NONNULL,   EXP_ZERO_SIZE },
+    { 1, SIZE_MAX, 0,
+      EXP_OOM,       EXP_ZERO_SIZE, EXP_OOM,       EXP_ZERO_SIZE },
+
+    { 1, 123, 345,
+      EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL },
+    { 1, 345, 123,
+      EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL },
+    { 12, 34, 56,
+      EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL },
+    { 12, 56, 34,
+      EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL },
+
+    { SQSQRT_SIZE_T, SIZE_MAX / SQSQRT_SIZE_T + 1, SIZE_MAX / SQSQRT_SIZE_T + 2,
+      EXP_ZERO_SIZE, EXP_NONNULL,   EXP_INT_OF,    EXP_INT_OF },
+    { SQSQRT_SIZE_T, SIZE_MAX / SQSQRT_SIZE_T + 2, SIZE_MAX / SQSQRT_SIZE_T + 1,
+      EXP_NONNULL,   EXP_ZERO_SIZE, EXP_INT_OF,    EXP_INT_OF },
+
+    { 123, 12, SIZE_MAX / 123 + 12,
+      EXP_NONNULL,   EXP_NONNULL,   EXP_NONNULL,   EXP_INT_OF },
+    { 123, SIZE_MAX / 123 + 12, 12,
+      EXP_NONNULL,   EXP_NONNULL,   EXP_INT_OF,    EXP_NONNULL },
+};
+
+static const struct array_aligned_alloc_vector {
+    size_t nmemb;
+    size_t size;
+    size_t align;
+    enum exp_ret exp;
+    enum exp_ret exp_array;
+} array_aligned_alloc_vectors[] = {
+    { 0, 0, 0, EXP_INVAL, EXP_INVAL },
+    { 0, 0, 1, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 0, 2, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 0, 3, EXP_INVAL, EXP_INVAL },
+    { 0, 0, 4, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 0, 64, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    { 0, 0, SQSQRT_SIZE_T, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+    /*
+     * This one gets mem_alloc_custom_fns_test killed with SIGKILL
+     * on the linux-arm64 github runner.
+     */
+    /* { 0, 0, SQRT_SIZE_T, EXP_ZERO_SIZE, EXP_ZERO_SIZE }, */
+
+    { 0, 0, 64, EXP_ZERO_SIZE, EXP_ZERO_SIZE },
+
+    { 8, 8, 63, EXP_INVAL, EXP_INVAL },
+    { 8, 8, 64, EXP_NONNULL, EXP_NONNULL },
+    { SIZE_MAX / 8 + 9, 8, 64, EXP_NONNULL, EXP_INT_OF },
+
+    /*
+     * posix_memalign expected to fail with ENOMEM, while the open-coded
+     * implementation tries to alloc size + alignment, which should fail
+     * on integer overflow.
+     */
+    { 1, SIZE_MAX / 2 + 2, SIZE_MAX / 2 + 1,
+#if (defined(_BSD_SOURCE) \
+      || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L)) \
+    && !USE_CUSTOM_ALLOC_FNS || defined(OPENSSL_SMALL_FOOTPRINT)
+      EXP_OOM, EXP_OOM
+#else
+      EXP_INT_OF, EXP_INT_OF
+#endif
+    },
+};
+
+static int secure_memory_is_secure;
+
+#if USE_CUSTOM_ALLOC_FNS
+static void *my_malloc(const size_t num,
+                       const char * const file, const int line)
+{
+    void * const p = malloc(num);
+
+# if CUSTOM_FN_PRINT_CALLS
+    if (file == test_fn || file == NULL
+        || (strcmp(file, OPENSSL_FILE) == 0 && file[0] != '\0'))
+        TEST_note("[%s:%d]: malloc(%#zx) -> %p", file, line, num, p);
+# endif
+
+    if (cur_custom_counts.malloc < INT_MAX)
+        cur_custom_counts.malloc++;
+
+    return p;
+}
+static void *my_realloc(void * const addr, const size_t num,
+                        const char * const file, const int line)
+{
+# if CUSTOM_FN_PRINT_CALLS
+    const uintptr_t old_addr = (uintptr_t) addr;
+# endif
+    void * const p = realloc(addr, num);
+
+# if CUSTOM_FN_PRINT_CALLS
+    if (file == test_fn || file == NULL
+        || (strcmp(file, OPENSSL_FILE) == 0 && file[0] != '\0'))
+        TEST_note("[%s:%d]: realloc(%#" PRIxPTR ", %#zx) -> %p",
+                  file, line, old_addr, num, p);
+# endif
+
+    if (cur_custom_counts.realloc < INT_MAX)
+        cur_custom_counts.realloc++;
+
+    return p;
+}
+
+static void my_free(void * const addr, const char * const file, const int line)
+{
+# if CUSTOM_FN_PRINT_CALLS
+    if (file == test_fn || file == NULL
+        || (strcmp(file, OPENSSL_FILE) == 0 && file[0] != '\0'))
+        TEST_note("[%s:%d]: free(%p)", file, line, addr);
+# endif
+
+    if (cur_custom_counts.free < INT_MAX)
+        cur_custom_counts.free++;
+
+    free(addr);
+}
+#endif /* USE_CUSTOM_ALLOC_FNS */
+
+static bool check_zero_mem(char *p, size_t sz)
+{
+    for (size_t i = 0; i < sz; i++) {
+        if (p[i] != 0) {
+            TEST_error("Non-zero byte %zu of %zu (%#04hhx)", i, sz, p[i]);
+
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static void save_counts(void)
+{
+#if !defined(OPENSSL_NO_CRYPTO_MDEBUG)
+    CRYPTO_get_alloc_counts(&mdebug_counts.malloc,
+                            &mdebug_counts.realloc,
+                            &mdebug_counts.free);
+#endif
+
+#if USE_CUSTOM_ALLOC_FNS
+    saved_custom_counts = cur_custom_counts;
+#endif
+}
+
+static void check_exp_prep(void)
+{
+    ERR_set_mark();
+
+    save_counts();
+}
+
+/*
+ * Retrieve fresh call counts and check against the expected ones,
+ * when the latter are no less than zero.
+ */
+static bool check_counts(int exp_mallocs, int exp_reallocs, int exp_frees)
+{
+    int test_result = 1;
+
+#if !defined(OPENSSL_NO_CRYPTO_MDEBUG)
+    {
+        struct call_counts cur;
+
+        CRYPTO_get_alloc_counts(&cur.malloc, &cur.realloc, &cur.free);
+        if (exp_mallocs >= 0
+            && !TEST_int_eq(cur.malloc - mdebug_counts.malloc, exp_mallocs))
+            test_result = 0;
+        if (exp_reallocs >= 0
+            && !TEST_int_eq(cur.realloc - mdebug_counts.realloc, exp_reallocs))
+            test_result = 0;
+        if (exp_frees >= 0
+            && !TEST_int_eq(cur.free - mdebug_counts.free, exp_frees))
+            test_result = 0;
+    }
+#endif
+
+#if USE_CUSTOM_ALLOC_FNS
+    if (exp_mallocs >= 0
+        && !TEST_int_eq(cur_custom_counts.malloc - saved_custom_counts.malloc,
+                        exp_mallocs))
+        test_result = 0;
+    if (exp_reallocs >= 0
+        && !TEST_int_eq(cur_custom_counts.realloc - saved_custom_counts.realloc,
+                        exp_reallocs))
+        test_result = 0;
+    if (exp_frees >= 0
+        && !TEST_int_eq(cur_custom_counts.free - saved_custom_counts.free,
+                        exp_frees))
+        test_result = 0;
+#endif
+
+    return test_result;
+}
+
+static int check_exp(const char * const fn, const int ln, const size_t sz,
+                     const bool secure, const bool zero, char * const ret,
+                     const enum exp_ret exp, int exp_mallocs, int exp_reallocs)
+{
+    int num_errs;
+    unsigned long err_code = 0;
+    const char *err_file = NULL;
+    int err_line = 0;
+    const char *err_func = NULL;
+    const char *err_data = NULL;
+    int err_flags = 0;
+    int test_result = 1;
+    unsigned long oom_err;
+
+    num_errs = ERR_count_to_mark();
+    if (num_errs > 0) {
+        err_code = ERR_peek_last_error_all(&err_file, &err_line, &err_func,
+                                           &err_data, &err_flags);
+    }
+
+    switch (exp) {
+    case EXP_OOM:
+        oom_err = secure ? CRYPTO_R_SECURE_MALLOC_FAILURE
+                         : ERR_R_MALLOC_FAILURE;
+        if (!TEST_ptr_null(ret)
+            || !TEST_int_eq(num_errs, 1)
+            || !TEST_ulong_eq(err_code, ERR_PACK(ERR_LIB_CRYPTO, 0, oom_err))
+            || !TEST_str_eq(err_file, fn)
+            || !TEST_int_eq(err_line, ln)
+            || !TEST_str_eq(err_func, "")
+            || !TEST_str_eq(err_data, "")
+            || !TEST_int_eq(err_flags, 0))
+            test_result = 0;
+
+        break;
+
+    case EXP_INVAL:
+        if (!TEST_ptr_null(ret)
+            || !TEST_int_eq(num_errs, 1)
+            || !TEST_ulong_eq(err_code, ERR_PACK(ERR_LIB_CRYPTO, 0,
+                                                 ERR_R_PASSED_INVALID_ARGUMENT))
+            || !TEST_str_eq(err_file, fn)
+            || !TEST_int_eq(err_line, ln)
+            || !TEST_str_eq(err_func, "")
+            || !TEST_str_eq(err_data, "")
+            || !TEST_int_eq(err_flags, 0))
+            test_result = 0;
+
+        break;
+
+    case EXP_INT_OF:
+        if (!TEST_ptr_null(ret)
+            || !TEST_int_eq(num_errs, 1)
+            || !TEST_ulong_eq(err_code, ERR_PACK(ERR_LIB_CRYPTO, 0,
+                                                 CRYPTO_R_INTEGER_OVERFLOW))
+            || !TEST_str_eq(err_file, fn)
+            || !TEST_int_eq(err_line, ln)
+            || !TEST_str_eq(err_func, "")
+            || !TEST_str_eq(err_data, "")
+            || !TEST_int_eq(err_flags, 0))
+            test_result = 0;
+
+        break;
+
+    case EXP_NONNULL:
+        if (!TEST_ptr(ret)
+            || !TEST_int_eq(num_errs, 0)) {
+            test_result = 0;
+        } else if (zero) {
+            if (!check_zero_mem(ret, sz))
+                test_result = 0;
+        }
+
+        break;
+
+    case EXP_ZERO_SIZE:
+        /*
+         * Since the pointer ca either be NULL or non-NULL, depending
+         * on implementation, we can only check for the absence of errors.
+         */
+        if (!TEST_int_eq(num_errs, 0))
+            test_result = 0;
+
+        break;
+
+    default:
+        TEST_error("Unexpected expected result");
+        test_result = 0;
+    }
+
+    ERR_pop_to_mark();
+
+    /*
+     * We don't check for frees here as there's a non-trivial amount
+     * of free calls in the error handling routines.
+     */
+    test_result &= check_counts(exp_mallocs, exp_reallocs, -1);
+
+    return test_result;
+}
+
+static int test_xalloc(const bool secure, const bool array, const bool zero,
+                       const bool macro, const struct array_alloc_vector *td)
+{
+    char *ret;
+    int ln = test_line;
+    size_t sz = td->nmemb * td->size;
+    enum exp_ret exp = array ? td->exp_calloc : td->exp_malloc;
+    bool really_secure = secure && secure_memory_is_secure;
+    int exp_cnt = 0;
+    int res;
+
+    check_exp_prep();
+
+    if (macro) {
+        if (secure) {
+            if (array) {
+                if (zero)
+                    ln = OPENSSL_LINE, ret = OPENSSL_secure_calloc(td->nmemb, td->size);
+                else
+                    ln = OPENSSL_LINE, ret = OPENSSL_secure_malloc_array(td->nmemb, td->size);
+            } else {
+                if (zero)
+                    ln = OPENSSL_LINE, ret = OPENSSL_secure_zalloc(sz);
+                else
+                    ln = OPENSSL_LINE, ret = OPENSSL_secure_malloc(sz);
+            }
+        } else {
+            if (array) {
+                if (zero)
+                    ln = OPENSSL_LINE, ret = OPENSSL_calloc(td->nmemb, td->size);
+                else
+                    ln = OPENSSL_LINE, ret = OPENSSL_malloc_array(td->nmemb, td->size);
+            } else {
+                if (zero)
+                    ln = OPENSSL_LINE, ret = OPENSSL_zalloc(sz);
+                else
+                    ln = OPENSSL_LINE, ret = OPENSSL_malloc(sz);
+            }
+        }
+    } else {
+        if (array) {
+            ret = (secure ? (zero ? CRYPTO_secure_calloc
+                                  : CRYPTO_secure_malloc_array)
+                          : (zero ? CRYPTO_calloc
+                                  : CRYPTO_malloc_array))(td->nmemb, td->size,
+                                                          test_fn, test_line);
+        } else {
+            ret = (secure ? (zero ? CRYPTO_secure_zalloc
+                                  : CRYPTO_secure_malloc)
+                          : (zero ? CRYPTO_zalloc
+                                  : CRYPTO_malloc))(sz, test_fn, test_line);
+        }
+    }
+
+    /*
+     * There is an OPENSSL_calloc in ERR_set_debug, triggered
+     * from ossl_report_alloc_err_ex.
+     */
+    exp_cnt += IS_FAIL(exp) && (!macro || (bool) OPENSSL_FILE[0]);
+    /*
+     * Secure allocations don't trigger alloc counting.
+     * EXP_OOM is special as it comes on return from the (called and counted)
+     * allocation function.
+     */
+    if (!really_secure)
+        exp_cnt += !!(exp == EXP_OOM || !IS_FAIL(exp));
+    res = check_exp(macro ? OPENSSL_FILE : test_fn, ln, sz, really_secure, zero,
+                    ret, exp, exp_cnt, 0);
+
+    if (really_secure)
+        OPENSSL_secure_free(ret);
+    else
+        OPENSSL_free(ret);
+
+    return res;
+}
+
+static int test_xrealloc(const bool clear, const bool array, const bool macro,
+                         const struct array_realloc_vector *td)
+{
+    char *ret = NULL;
+    char *old_ret = NULL;
+    int exp_malloc_cnt, exp_realloc_cnt;
+    int res = 1;
+    size_t i;
+
+    /*
+     * Do two passes, first with NULL ptr, then with the result of the first
+     * call.
+     */
+    for (i = 0; i < 2; i++) {
+        size_t nmemb = i ? td->new_nmemb : td->orig_nmemb;
+        size_t old_nmemb = i ? td->orig_nmemb : 0;
+        size_t sz = nmemb * td->size;
+        size_t old_sz = old_nmemb * td->size;
+        int ln = test_line;
+        enum exp_ret exp = i ? (array ? td->exp_new_array  : td->exp_new)
+                             : (array ? td->exp_orig_array : td->exp_orig);
+        enum exp_ret exp2 = !i ? (array ? td->exp_new_array  : td->exp_new)
+                               : (array ? td->exp_orig_array : td->exp_orig);
+
+        exp_malloc_cnt = exp_realloc_cnt = 0;
+
+        /* clear_realloc_array checks both new and old sizes */
+        if (clear && array && i && exp2 == EXP_INT_OF)
+            exp = EXP_INT_OF;
+
+        if (exp != EXP_INT_OF) {
+            if (clear) {
+                /*
+                 * clear_alloc just calls cleanse if contraction has been
+                 * requested.
+                 */
+                if (ret == NULL || sz > old_sz)
+                    exp_malloc_cnt++;
+            } else {
+                exp_realloc_cnt++;
+#if !USE_CUSTOM_ALLOC_FNS
+                /* CRYPTO_malloc() is called explicitly when p is NULL. */
+                if (ret == NULL)
+                    exp_malloc_cnt++;
+#endif
+            }
+        } else {
+            if (!macro || OPENSSL_FILE[0] != '\0')
+                exp_malloc_cnt++;
+        }
+
+        check_exp_prep();
+
+        if (macro) {
+            if (array) {
+                if (clear)
+                    ln = OPENSSL_LINE, ret = OPENSSL_clear_realloc_array(ret, old_nmemb, nmemb, td->size);
+                else
+                    ln = OPENSSL_LINE, ret = OPENSSL_realloc_array(ret, nmemb, td->size);
+            } else {
+                if (clear)
+                    ln = OPENSSL_LINE, ret = OPENSSL_clear_realloc(ret, old_sz, sz);
+                else
+                    ln = OPENSSL_LINE, ret = OPENSSL_realloc(ret, sz);
+            }
+        } else {
+            if (array) {
+                if (clear)
+                    ret = CRYPTO_clear_realloc_array(ret, old_nmemb, nmemb,
+                                                     td->size,
+                                                     test_fn, test_line);
+                else
+                    ret = CRYPTO_realloc_array(ret, nmemb, td->size,
+                                               test_fn, test_line);
+            } else {
+                if (clear)
+                    ret = CRYPTO_clear_realloc(ret, old_sz, sz,
+                                               test_fn, test_line);
+                else
+                    ret = CRYPTO_realloc(ret, sz, test_fn, test_line);
+            }
+        }
+
+        /*
+         * There is an OPENSSL_calloc in ERR_set_debug, triggered
+         * from ossl_report_alloc_err_ex.
+         */
+        exp_malloc_cnt += !!(exp == EXP_OOM
+                             && (!macro || (bool) OPENSSL_FILE[0]));
+
+        res = check_exp(macro ? OPENSSL_FILE : test_fn, ln, sz, false, false,
+                        ret, exp, exp_malloc_cnt, exp_realloc_cnt);
+        if (res == 0)
+            TEST_error("realloc return code check fail with i = %zu"
+                       ", old_ret = %p, ret = %p, old_nmemb = %#zx"
+                       ", nmemb = %#zx, size = %#zx",
+                       i, (void *) old_ret, (void *) ret, old_nmemb, nmemb,
+                       td->size);
+
+        /* Write data on the first pass and check it on the second */
+        if (res != 0 && exp == EXP_NONNULL && exp2 == EXP_NONNULL) {
+            size_t check_sz = MIN(td->orig_nmemb * td->size,
+                                  td->new_nmemb * td->size);
+            size_t j;
+            size_t num_err = 0;
+
+            if (i != 0) {
+                for (j = 0; j < check_sz; j++) {
+                    char exp_val = (uint8_t) ((uintptr_t) td * 253 + j * 17);
+
+                    if (ret[j] != exp_val) {
+                        if (!num_err)
+                            TEST_error("Memory mismatch at byte %zu of %zu: "
+                                       "%#04hhx != %#04hhx",
+                                       j, check_sz, ret[j], exp_val);
+
+                        res = 0;
+                        num_err++;
+                    }
+                }
+
+                if (num_err != 0)
+                    TEST_error("Total errors: %zu", num_err);
+            } else {
+                for (j = 0; j < check_sz; j++)
+                    ret[j] = (uint8_t) ((uintptr_t) td * 253 + j * 17);
+            }
+        }
+
+        /* Freeing the old allocation if realloc has failed */
+        if (old_ret != 0 && ret == 0 && exp != EXP_ZERO_SIZE)
+            OPENSSL_free(old_ret);
+
+        old_ret = ret;
+    }
+
+    OPENSSL_free(ret);
+
+    return res;
+}
+
+static int test_xaligned_alloc(const bool array, const bool macro,
+                               const struct array_aligned_alloc_vector *td)
+{
+    char *ret;
+    int ln = test_line;
+    size_t sz = td->nmemb * td->size;
+    enum exp_ret exp = array ? td->exp_array : td->exp;
+    int exp_cnt = 0;
+    void *freeptr = &freeptr;
+    int res = 1;
+
+    check_exp_prep();
+
+    if (macro) {
+        if (array) {
+            ln = OPENSSL_LINE, ret = OPENSSL_aligned_alloc_array(td->nmemb, td->size, td->align, &freeptr);
+        } else {
+            ln = OPENSSL_LINE, ret = OPENSSL_aligned_alloc(sz, td->align, &freeptr);
+        }
+    } else {
+        if (array)
+            ret = CRYPTO_aligned_alloc_array(td->nmemb, td->size, td->align,
+                                             &freeptr, test_fn, test_line);
+        else
+            ret = CRYPTO_aligned_alloc(sz, td->align, &freeptr,
+                                       test_fn, test_line);
+    }
+
+#if !defined(OPENSSL_SMALL_FOOTPRINT)
+    /*
+     * aligned_alloc doesn't increment the call counts by itself, and
+     * OPENSSL_malloc is only called when the open-coded implementation
+     * is used.
+     */
+# if USE_CUSTOM_ALLOC_FNS \
+    || !(defined(_BSD_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L))
+    exp_cnt += !!(exp != EXP_INT_OF && exp != EXP_INVAL);
+# endif
+#else /* OPENSSL_SMALL_FOOTPRINT */
+    exp = exp == EXP_INT_OF ? EXP_INT_OF : EXP_ZERO_SIZE;
+#endif /* !OPENSSL_SMALL_FOOTPRINT */
+
+    /*
+     * There is an OPENSSL_calloc in ERR_set_debug, triggered
+     * from ossl_report_alloc_err_ex.
+     */
+    exp_cnt += IS_FAIL(exp) && (!macro || (bool) OPENSSL_FILE[0]);
+    res &= check_exp(macro ? OPENSSL_FILE : test_fn, ln, sz, false, false,
+                     ret, exp, exp_cnt, 0);
+
+    /* Check the pointer's alignment */
+    if (exp == EXP_NONNULL) {
+        if (!TEST_uint64_t_eq((uintptr_t) ret & (td->align - 1), 0))
+            res = 0;
+    }
+
+#if !defined(OPENSSL_SMALL_FOOTPRINT)
+    if (IS_FAIL(exp) && !TEST_ptr_null(freeptr))
+        res = 0;
+    if ((exp = EXP_NONNULL) && !TEST_ptr(freeptr))
+        res = 0;
+#else /* OPENSSL_SMALL_FOOTPRINT */
+    if (!TEST_ptr_null(ret) || !TEST_ptr_null(freeptr))
+        res = 0;
+#endif /* !OPENSSL_SMALL_FOOTPRINT */
+
+    OPENSSL_free(freeptr);
+
+    return res;
+}
+
+static int test_malloc(const int i)
+{
+    return test_xalloc(false, false, false, false, array_alloc_vectors + i)
+        && test_xalloc(false, false, false, true,  array_alloc_vectors + i);
+}
+
+static int test_zalloc(const int i)
+{
+    return test_xalloc(false, false, true, false, array_alloc_vectors + i)
+        && test_xalloc(false, false, true, true,  array_alloc_vectors + i);
+}
+
+static int test_malloc_array(const int i)
+{
+    return test_xalloc(false, true, false, false, array_alloc_vectors + i)
+        && test_xalloc(false, true, false, true,  array_alloc_vectors + i);
+}
+
+static int test_calloc(const int i)
+{
+    return test_xalloc(false, true, true, false, array_alloc_vectors + i)
+        && test_xalloc(false, true, true, true,  array_alloc_vectors + i);
+}
+
+static int test_secure_malloc(const int i)
+{
+    return test_xalloc(true, false, false, false, array_alloc_vectors + i)
+        && test_xalloc(true, false, false, true,  array_alloc_vectors + i);
+}
+
+static int test_secure_zalloc(const int i)
+{
+    return test_xalloc(true, false, true, false, array_alloc_vectors + i)
+        && test_xalloc(true, false, true, true,  array_alloc_vectors + i);
+}
+
+static int test_secure_malloc_array(const int i)
+{
+    return test_xalloc(true, true, false, false, array_alloc_vectors + i)
+        && test_xalloc(true, true, false, true,  array_alloc_vectors + i);
+}
+
+static int test_secure_calloc(const int i)
+{
+    return test_xalloc(true, true, true, false, array_alloc_vectors + i)
+        && test_xalloc(true, true, true, true,  array_alloc_vectors + i);
+}
+
+static int test_realloc(const int i)
+{
+    return test_xrealloc(false, false, false, array_realloc_vectors + i)
+        && test_xrealloc(false, false, true,  array_realloc_vectors + i);
+}
+
+static int test_clear_realloc(const int i)
+{
+    return test_xrealloc(true, false, false, array_realloc_vectors + i)
+        && test_xrealloc(true, false, true,  array_realloc_vectors + i);
+}
+
+static int test_realloc_array(const int i)
+{
+    return test_xrealloc(false, true, false, array_realloc_vectors + i)
+        && test_xrealloc(false, true, true,  array_realloc_vectors + i);
+}
+
+static int test_clear_realloc_array(const int i)
+{
+    return test_xrealloc(true, true, false, array_realloc_vectors + i)
+        && test_xrealloc(true, true, true,  array_realloc_vectors + i);
+}
+
+static int test_aligned_alloc(const int i)
+{
+    return test_xaligned_alloc(false, false, array_aligned_alloc_vectors + i)
+        && test_xaligned_alloc(false, true,  array_aligned_alloc_vectors + i);
+}
+
+static int test_aligned_alloc_array(const int i)
+{
+    return test_xaligned_alloc(true, false, array_aligned_alloc_vectors + i)
+        && test_xaligned_alloc(true, true,  array_aligned_alloc_vectors + i);
+}
+
+static int test_free(void)
+{
+    int test_result = 1;
+    void *p;
+
+    save_counts();
+    OPENSSL_free(NULL);
+    if (!TEST_int_eq(check_counts(0, 0, 1), 1))
+        test_result = 0;
+
+    save_counts();
+    CRYPTO_free(NULL, test_fn, test_line);
+    if (!TEST_int_eq(check_counts(0, 0, 1), 1))
+        test_result = 0;
+
+    save_counts();
+    p = OPENSSL_malloc(42);
+    OPENSSL_free(p);
+    if (!TEST_int_eq(check_counts(1, 0, 1), 1))
+        test_result = 0;
+
+    save_counts();
+    p = CRYPTO_calloc(23, 69, test_fn, test_line);
+    CRYPTO_free(p, test_fn, test_line);
+    if (!TEST_int_eq(check_counts(1, 0, 1), 1))
+        test_result = 0;
+
+    return test_result;
+}
+
+int setup_tests(void)
+{
+    secure_memory_is_secure = CRYPTO_secure_malloc_init(65536, 4);
+    TEST_info("secure memory init: %d", secure_memory_is_secure);
+
+    ADD_ALL_TESTS(test_malloc, OSSL_NELEM(array_alloc_vectors));
+    ADD_ALL_TESTS(test_zalloc, OSSL_NELEM(array_alloc_vectors));
+    ADD_ALL_TESTS(test_malloc_array, OSSL_NELEM(array_alloc_vectors));
+    ADD_ALL_TESTS(test_calloc, OSSL_NELEM(array_alloc_vectors));
+
+    ADD_ALL_TESTS(test_secure_malloc, OSSL_NELEM(array_alloc_vectors));
+    ADD_ALL_TESTS(test_secure_zalloc, OSSL_NELEM(array_alloc_vectors));
+    ADD_ALL_TESTS(test_secure_malloc_array, OSSL_NELEM(array_alloc_vectors));
+    ADD_ALL_TESTS(test_secure_calloc, OSSL_NELEM(array_alloc_vectors));
+
+    ADD_ALL_TESTS(test_realloc, OSSL_NELEM(array_realloc_vectors));
+    ADD_ALL_TESTS(test_clear_realloc, OSSL_NELEM(array_realloc_vectors));
+    ADD_ALL_TESTS(test_realloc_array, OSSL_NELEM(array_realloc_vectors));
+    ADD_ALL_TESTS(test_clear_realloc_array, OSSL_NELEM(array_realloc_vectors));
+
+    ADD_ALL_TESTS(test_aligned_alloc, OSSL_NELEM(array_aligned_alloc_vectors));
+    ADD_ALL_TESTS(test_aligned_alloc_array,
+                  OSSL_NELEM(array_aligned_alloc_vectors));
+
+    ADD_TEST(test_free);
+
+    return 1;
+}
+
+#if USE_CUSTOM_ALLOC_FNS
+int global_init(void)
+{
+    if (!CRYPTO_set_mem_functions(my_malloc, my_realloc, my_free)) {
+        fprintf(stderr, "Failed to override allocator functions");
+
+        return 0;
+    }
+
+    return 1;
+}
+#endif
diff --git a/test/recipes/02-test_mem_alloc.t b/test/recipes/02-test_mem_alloc.t
new file mode 100644 (file)
index 0000000..89a3ece
--- /dev/null
@@ -0,0 +1,16 @@
+#! /usr/bin/env perl
+# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use OpenSSL::Test::Simple;
+
+{
+    local $ENV{"ASAN_OPTIONS"} = "allocator_may_return_null=true";
+    local $ENV{"MSAN_OPTIONS"} = "allocator_may_return_null=true";
+
+    simple_test("test_mem_alloc", "mem_alloc_test");
+}
diff --git a/test/recipes/02-test_mem_alloc_custom_fns.t b/test/recipes/02-test_mem_alloc_custom_fns.t
new file mode 100644 (file)
index 0000000..06667f0
--- /dev/null
@@ -0,0 +1,16 @@
+#! /usr/bin/env perl
+# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use OpenSSL::Test::Simple;
+
+{
+    local $ENV{"ASAN_OPTIONS"} = "allocator_may_return_null=true";
+    local $ENV{"MSAN_OPTIONS"} = "allocator_may_return_null=true";
+
+    simple_test("test_mem_alloc_custom_fns", "mem_alloc_custom_fns_test");
+}