Commit 31286c0351 for openssl.org
commit 31286c03513ac2b2b5ccc353d46fc134ff688e6f
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Fri Apr 17 21:27:07 2026 +0900
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 <paul.dale@oracle.com>
Reviewed-by: Neil Horman <nhorman@openssl.org>
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 f599b3aff8..322a24a1c6 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 0000000000..caedc51c83
--- /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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/crypto.h>
+#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 <allocation-number>\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 <allocation-number>\n", argv[0]);
+ }
+
+ OPENSSL_cleanup();
+ return ret;
+}
diff --git a/test/property_test.c b/test/property_test.c
index 8c75711132..a945c89296 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 01ee4925fa..8ca6b85c55 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])));
+}