From: Mounir IDRASSI Date: Fri, 17 Apr 2026 12:27:07 +0000 (+0900) Subject: Add property method cache failure tests X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=31286c03513ac2b2b5ccc353d46fc134ff688e6f;p=thirdparty%2Fopenssl.git Add property method cache failure tests Add coverage for duplicate property cache insertion and allocation-failure handling in the property method cache. The memfail exerciser covers cache set, providerless cache deletion, providerless cache rebuild, and cleanup of method references when cache insertion fails. Reviewed-by: Paul Dale Reviewed-by: Neil Horman MergeDate: Tue Apr 28 06:33:15 2026 (Merged from https://github.com/openssl/openssl/pull/30891) --- diff --git a/test/build.info b/test/build.info index f599b3aff8c..322a24a1c61 100644 --- a/test/build.info +++ b/test/build.info @@ -82,7 +82,7 @@ IF[{- !$disabled{tests} -}] ENDIF IF[{- !$disabled{'allocfail-tests'} -}] - PROGRAMS{noinst}=handshake-memfail x509-memfail load_key_certs_crls_memfail + PROGRAMS{noinst}=handshake-memfail x509-memfail load_key_certs_crls_memfail property-memfail ENDIF IF[{- !$disabled{quic} -}] @@ -633,6 +633,10 @@ IF[{- !$disabled{tests} -}] INCLUDE[load_key_certs_crls_memfail]=.. ../include ../apps/include DEPEND[load_key_certs_crls_memfail]=libtestutil.a ../libcrypto.a ../libssl.a + SOURCE[property-memfail]=property_memfail.c + INCLUDE[property-memfail]=../include ../apps/include + DEPEND[property-memfail]=../libcrypto.a + SOURCE[ssl_handshake_rtt_test]=ssl_handshake_rtt_test.c helpers/ssltestlib.c INCLUDE[ssl_handshake_rtt_test]=../include ../apps/include .. DEPEND[ssl_handshake_rtt_test]=../libcrypto.a ../libssl.a libtestutil.a diff --git a/test/property_memfail.c b/test/property_memfail.c new file mode 100644 index 00000000000..caedc51c830 --- /dev/null +++ b/test/property_memfail.c @@ -0,0 +1,196 @@ +/* + * 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 +#include +#include + +#include +#include "internal/hashtable.h" +#include "internal/property.h" +#include "internal/refcount.h" + +#define TEST_NID 1024 + +/* + * TEST_NID maps to shard zero with the property cache's current power-of-two + * shard count, so this partial view is enough to reach the cache table. + */ +typedef struct { + void *algs; + HT *cache; +} TEST_STORED_ALGORITHMS; + +struct ossl_method_store_st { + OSSL_LIB_CTX *ctx; + TEST_STORED_ALGORITHMS *algs; + CRYPTO_RWLOCK *biglock; +}; + +typedef struct { + HT_KEY key_header; +} QUERY_KEY; + +/* + * We make our OSSL_PROVIDER for testing purposes. The property cache only + * uses the provider pointer as a key, except when tracing asks for its name. + */ +struct ossl_provider_st { + unsigned int flag_initialized : 1; + unsigned int flag_activated : 1; + CRYPTO_RWLOCK *flag_lock; + CRYPTO_REF_COUNT refcnt; + CRYPTO_RWLOCK *activatecnt_lock; + int activatecnt; + char *name; +}; + +static long alloc_count; +static long fail_at; +static int fail_enabled; +static int method_refs; + +static void *test_malloc(size_t num, const char *file, int line) +{ + (void)file; + (void)line; + + if (fail_enabled && ++alloc_count == fail_at) + return NULL; + + return malloc(num); +} + +static void *test_realloc(void *ptr, size_t num, const char *file, int line) +{ + (void)file; + (void)line; + + if (fail_enabled && ++alloc_count == fail_at) + return NULL; + + return realloc(ptr, num); +} + +static void test_free(void *ptr, const char *file, int line) +{ + (void)file; + (void)line; + + free(ptr); +} + +static int up_ref(void *p) +{ + (void)p; + + method_refs++; + return 1; +} + +static void down_ref(void *p) +{ + (void)p; + + method_refs--; +} + +static int delete_providerless_cache_entry(OSSL_METHOD_STORE *store) +{ + QUERY_KEY key; + uint8_t keybuf[sizeof(int)]; + size_t keylen = 0; + int nid = TEST_NID; + + memcpy(&keybuf[keylen], &nid, sizeof(nid)); + keylen += sizeof(nid); + HT_INIT_KEY_EXTERNAL(&key, keybuf, keylen); + + return ossl_ht_delete(store->algs->cache, TO_HT_KEY(&key)); +} + +static int property_cache_workload(int expect_success) +{ + static struct ossl_provider_st prov = { + .flag_initialized = 1, + .flag_activated = 1, + .name = "property-memfail" + }; + OSSL_METHOD_STORE *store = NULL; + int method = 1; + void *result = NULL; + int ret = 0; + + method_refs = 0; + + if ((store = ossl_method_store_new(NULL)) == NULL) + goto end; + if (!ossl_method_store_add(store, (OSSL_PROVIDER *)&prov, TEST_NID, "", + &method, up_ref, down_ref)) + goto end; + /* + * Restrict failure injection to the cache paths. Store setup exercises + * unrelated global initialization and platform lock allocation. + */ + alloc_count = 0; + fail_enabled = 1; + if (!ossl_method_store_cache_set(store, (OSSL_PROVIDER *)&prov, TEST_NID, + "", &method, up_ref, down_ref)) + goto end; + if (!delete_providerless_cache_entry(store)) + goto end; + if (!ossl_method_store_cache_get(store, NULL, TEST_NID, "", &result) + || result != &method) + goto end; + ret = 1; + +end: + fail_enabled = 0; + if (result != NULL) + down_ref(result); + ossl_method_store_free(store); + + if (method_refs != 0) { + fprintf(stderr, "method reference leak: %d\n", method_refs); + return 0; + } + + return expect_success ? ret : 1; +} + +int main(int argc, char **argv) +{ + int ret = EXIT_FAILURE; + + if (argc < 2) { + fprintf(stderr, "usage: %s count | run \n", argv[0]); + return EXIT_FAILURE; + } + + if (!CRYPTO_set_mem_functions(test_malloc, test_realloc, test_free)) { + fprintf(stderr, "failed to set memory functions\n"); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "count") == 0) { + if (property_cache_workload(1)) { + fprintf(stderr, "skip: 0 count %ld\n", alloc_count); + ret = EXIT_SUCCESS; + } + } else if (strcmp(argv[1], "run") == 0 && argc == 3) { + fail_at = strtol(argv[2], NULL, 10); + if (fail_at > 0 && property_cache_workload(0)) + ret = EXIT_SUCCESS; + } else { + fprintf(stderr, "usage: %s count | run \n", argv[0]); + } + + OPENSSL_cleanup(); + return ret; +} diff --git a/test/property_test.c b/test/property_test.c index 8c757111321..a945c892964 100644 --- a/test/property_test.c +++ b/test/property_test.c @@ -60,6 +60,21 @@ static void down_ref(void *p) { } +static int counted_up_ref(void *p) +{ + int *refs = p; + + (*refs)++; + return 1; +} + +static void counted_down_ref(void *p) +{ + int *refs = p; + + (*refs)--; +} + static int test_property_string(void) { OSSL_LIB_CTX *ctx; @@ -627,6 +642,51 @@ err: return res; } +static int test_query_cache_set_duplicate(void) +{ + OSSL_METHOD_STORE *store = NULL; + int res = 0; + int refs = 0; + void *result = NULL; + OSSL_PROVIDER prov = { + .flag_initialized = 1, + .flag_activated = 1, + .name = "dummy-test-provider" + }; + + if (!TEST_ptr(store = ossl_method_store_new(NULL)) + || !TEST_true(ossl_method_store_add(store, &prov, 1, "", &refs, + counted_up_ref, counted_down_ref)) + || !TEST_true(ossl_method_store_cache_set(store, &prov, 1, "", &refs, + counted_up_ref, + counted_down_ref)) + || !TEST_int_eq(refs, 3)) + goto err; + + /* + * Re-adding the same cache key exercises cleanup for a temporary generic + * QUERY that cannot be inserted because a providerless entry already + * exists. + */ + ossl_method_store_cache_set(store, &prov, 1, "", &refs, counted_up_ref, + counted_down_ref); + if (!TEST_int_eq(refs, 3) + || !TEST_true(ossl_method_store_cache_get(store, &prov, 1, "", + &result)) + || !TEST_ptr_eq(result, &refs)) + goto err; + + counted_down_ref(result); + result = NULL; + res = 1; + +err: + ossl_method_store_free(store); + if (!TEST_int_eq(refs, 0)) + res = 0; + return res; +} + static int test_fips_mode(void) { int ret = 0; @@ -739,6 +799,7 @@ int setup_tests(void) ADD_TEST(test_register_deregister); ADD_TEST(test_property); ADD_TEST(test_query_cache_stochastic); + ADD_TEST(test_query_cache_set_duplicate); ADD_TEST(test_fips_mode); ADD_ALL_TESTS(test_property_list_to_string, OSSL_NELEM(to_string_tests)); ADD_TEST(test_property_list_to_string_bounds); diff --git a/test/recipes/90-test_memfail.t b/test/recipes/90-test_memfail.t index 01ee4925fa0..8ca6b85c550 100644 --- a/test/recipes/90-test_memfail.t +++ b/test/recipes/90-test_memfail.t @@ -32,6 +32,8 @@ run(test(["x509-memfail", "count", srctop_file("test", "certs", "servercert.pem" run(test(["load_key_certs_crls_memfail", "count", srctop_file("test", "certs", "servercert.pem")], stderr => "$resultdir/load_key_certs_crls_countinfo.txt")); +run(test(["property-memfail", "count"], stderr => "$resultdir/propertycountinfo.txt")); + sub get_count_info { my ($infile) = @_; my ($skipcount, $malloccount) = (0, 0); @@ -58,7 +60,10 @@ my ($x509skipcount, $x509malloccount) = get_count_info("$resultdir/x509countinfo my ($load_key_certs_crls_skipcount, $load_key_certs_crls_malloccount) = get_count_info("$resultdir/load_key_certs_crls_countinfo.txt"); -my $total_malloccount = $hsmalloccount + $x509malloccount + $load_key_certs_crls_malloccount; +my (undef, $propertymalloccount) = get_count_info("$resultdir/propertycountinfo.txt"); + +my $total_malloccount = $hsmalloccount + $x509malloccount + + $load_key_certs_crls_malloccount + $propertymalloccount; plan skip_all => "could not get malloc counts (one or more count runs failed or output format changed)" if $total_malloccount == 0; @@ -95,3 +100,6 @@ run_memfail_test($x509skipcount, $x509malloccount, ["x509-memfail", "run", srcto run_memfail_test($load_key_certs_crls_skipcount, $load_key_certs_crls_malloccount, ["load_key_certs_crls_memfail", "run", srctop_file("test", "certs", "servercert.pem")]); +for my $idx (1..$propertymalloccount) { + ok(run(test(["property-memfail", "run", $idx]))); +}