mirror of
https://github.com/openssl/openssl.git
synced 2026-05-07 20:12:39 +00:00
3cff7c2181
Introduce ADD_MFAIL_TEST for exhaustive testing of allocation failure handling in individual functions. The framework repeatedly calls the test function, each time failing one allocation later within the section bracketed by mfail_start() and mfail_end(), verifying that every failure path returns 0 without crashing or leaking. Custom allocators are installed once at startup via CRYPTO_set_mem_functions(). When not armed, they pass through to malloc/realloc/free. Installation can be disabled by setting OPENSSL_TEST_MFAIL_DISABLE for tests that need the default allocator (e.g. those using OPENSSL_MALLOC_FAILURES). Additional environment variables control test execution: OPENSSL_TEST_MFAIL_SKIP_ALL, OPENSSL_TEST_MFAIL_SKIP_SLOW, OPENSSL_TEST_MFAIL_POINT, and OPENSSL_TEST_MFAIL_START. Reviewed-by: Saša Nedvědický <sashan@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.foundation> MergeDate: Thu Apr 23 20:23:34 2026 (Merged from https://github.com/openssl/openssl/pull/30871)
197 lines
4.8 KiB
C
197 lines
4.8 KiB
C
/*
|
|
* Copyright 2026 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 "../testutil.h"
|
|
#include "tu_local.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <openssl/crypto.h>
|
|
|
|
static int mfail_fail_after = -1;
|
|
static int mfail_alloc_count = 0;
|
|
static int mfail_triggered = 0;
|
|
static int mfail_counting = 0;
|
|
static int mfail_do_skip_all = 0;
|
|
static int mfail_do_skip_slow = 0;
|
|
static int mfail_single_point = -1;
|
|
static int mfail_start_point = 0;
|
|
static int mfail_installed = 0;
|
|
|
|
static int should_fail(void)
|
|
{
|
|
if (mfail_fail_after < 0 || !mfail_counting || mfail_triggered)
|
|
return 0;
|
|
if (mfail_alloc_count++ == mfail_fail_after) {
|
|
mfail_triggered = 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void *mfail_malloc(size_t num, const char *file, int line)
|
|
{
|
|
if (num == 0)
|
|
return NULL;
|
|
if (should_fail())
|
|
return NULL;
|
|
return malloc(num);
|
|
}
|
|
|
|
static void *mfail_realloc(void *addr, size_t num, const char *file, int line)
|
|
{
|
|
if (addr == NULL)
|
|
return mfail_malloc(num, file, line);
|
|
if (num == 0) {
|
|
free(addr);
|
|
return NULL;
|
|
}
|
|
if (should_fail())
|
|
return NULL;
|
|
return realloc(addr, num);
|
|
}
|
|
|
|
static void mfail_free(void *addr, const char *file, int line)
|
|
{
|
|
free(addr);
|
|
}
|
|
|
|
static int env_is_true(const char *name)
|
|
{
|
|
const char *val = getenv(name);
|
|
|
|
return val != NULL && *val != '\0' && strcmp(val, "0") != 0;
|
|
}
|
|
|
|
void mfail_install(void)
|
|
{
|
|
if (env_is_true("OPENSSL_TEST_MFAIL_DISABLE"))
|
|
return;
|
|
if (!CRYPTO_set_mem_functions(mfail_malloc, mfail_realloc, mfail_free))
|
|
return;
|
|
mfail_installed = 1;
|
|
}
|
|
|
|
void mfail_start(void)
|
|
{
|
|
mfail_alloc_count = 0;
|
|
mfail_counting = 1;
|
|
}
|
|
|
|
void mfail_end(void)
|
|
{
|
|
mfail_counting = 0;
|
|
}
|
|
|
|
static void mfail_arm(int fail_after)
|
|
{
|
|
mfail_fail_after = fail_after;
|
|
mfail_alloc_count = 0;
|
|
mfail_triggered = 0;
|
|
mfail_counting = 0;
|
|
}
|
|
|
|
static void mfail_disarm(void)
|
|
{
|
|
mfail_fail_after = -1;
|
|
mfail_alloc_count = 0;
|
|
mfail_triggered = 0;
|
|
mfail_counting = 0;
|
|
}
|
|
|
|
static double elapsed_secs(clock_t start)
|
|
{
|
|
return (double)(clock() - start) / CLOCKS_PER_SEC;
|
|
}
|
|
|
|
void mfail_init(void)
|
|
{
|
|
const char *env;
|
|
|
|
mfail_do_skip_all = env_is_true("OPENSSL_TEST_MFAIL_SKIP_ALL");
|
|
mfail_do_skip_slow = env_is_true("OPENSSL_TEST_MFAIL_SKIP_SLOW");
|
|
|
|
env = getenv("OPENSSL_TEST_MFAIL_POINT");
|
|
if (env != NULL && *env != '\0')
|
|
mfail_single_point = atoi(env);
|
|
|
|
env = getenv("OPENSSL_TEST_MFAIL_START");
|
|
if (env != NULL && *env != '\0')
|
|
mfail_start_point = atoi(env);
|
|
}
|
|
|
|
int mfail_should_skip(int slow)
|
|
{
|
|
if (!mfail_installed)
|
|
return 1;
|
|
return mfail_do_skip_all || (slow && mfail_do_skip_slow);
|
|
}
|
|
|
|
int mfail_run_test(const char *test_case_name, int (*test_fn)(void))
|
|
{
|
|
int alloc_point, ret = 1;
|
|
clock_t start;
|
|
|
|
start = clock();
|
|
|
|
if (mfail_single_point >= 0) {
|
|
int rv, triggered;
|
|
|
|
ERR_clear_error();
|
|
mfail_arm(mfail_single_point);
|
|
rv = test_fn();
|
|
triggered = mfail_triggered;
|
|
mfail_disarm();
|
|
|
|
if (!triggered) {
|
|
TEST_info("mfail test '%s': point %d is beyond the last "
|
|
"allocation point, test %s",
|
|
test_case_name, mfail_single_point,
|
|
rv == 1 ? "succeeded" : "failed");
|
|
} else if (!TEST_int_eq(rv, 0)) {
|
|
TEST_error("mfail test '%s': allocation failure at point %d "
|
|
"not handled",
|
|
test_case_name, mfail_single_point);
|
|
ret = 0;
|
|
}
|
|
} else {
|
|
for (alloc_point = mfail_start_point;; alloc_point++) {
|
|
int rv, triggered;
|
|
|
|
ERR_clear_error();
|
|
mfail_arm(alloc_point);
|
|
rv = test_fn();
|
|
triggered = mfail_triggered;
|
|
mfail_disarm();
|
|
|
|
if (!triggered) {
|
|
if (!TEST_int_eq(rv, 1)) {
|
|
TEST_error("mfail test '%s': no injection but test failed",
|
|
test_case_name);
|
|
ret = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!TEST_int_eq(rv, 0)) {
|
|
TEST_error("mfail test '%s': allocation failure at point %d "
|
|
"not handled",
|
|
test_case_name, alloc_point);
|
|
ret = 0;
|
|
}
|
|
}
|
|
TEST_info("mfail test '%s': points %d..%d, %d iterations, %.6f seconds",
|
|
test_case_name, mfail_start_point, alloc_point,
|
|
alloc_point - mfail_start_point + 1, elapsed_secs(start));
|
|
}
|
|
|
|
return ret;
|
|
}
|