Commit 9ac29bc857 for openssl.org
commit 9ac29bc8579dd9632bd50900675ff7900f5f7ef1
Author: herbenderbler <johnclaus@gmail.com>
Date: Mon Mar 16 11:14:52 2026 -0600
Fix memory leak in load_key_certs_crls() when add/push fails
When X509_add_cert() or sk_X509_CRL_push() failed, the cert or CRL from
OSSL_STORE was not freed. Free on failure to avoid a leak.
Fix 90-test_memfail.t parsing of count output so the memfail suite runs
correctly: parse 'skip: N count M' with a regex (handles '# ' prefix),
return (0,0) if the count file cannot be opened, and skip with a clear
message when total malloc count is 0 instead of planning 0 tests.
Apply clang-format to test/load_key_certs_crls_memfail.c.
- apps/lib/apps.c: free cert/CRL on add/push failure
- test/build.info: add load_key_certs_crls_memfail (allocfail-tests)
- test/load_key_certs_crls_memfail.c: regression test for issue #30364
- test/recipes/90-test_memfail.t: fix get_count_info parsing and plan
Issue #30364
Fixes: 6d382c74b375 "Use OSSL_STORE for load_{,pub}key() and load_cert() in apps/lib/apps.c"
Fixes: d7fcee3b3b5fa "OSSL_HTTP_parse_url(): add optional port number return parameter and strengthen documentation"
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
MergeDate: Tue Apr 21 08:50:18 2026
(Merged from https://github.com/openssl/openssl/pull/30428)
diff --git a/apps/lib/apps.c b/apps/lib/apps.c
index 986f5954f4..ce4fa9d383 100644
--- a/apps/lib/apps.c
+++ b/apps/lib/apps.c
@@ -1057,9 +1057,12 @@ int load_key_certs_crls(const char *uri, int format, int maybe_stdin,
if (ok)
pcert = NULL;
} else if (pcerts != NULL) {
- ok = X509_add_cert(*pcerts,
- OSSL_STORE_INFO_get1_CERT(info),
- X509_ADD_FLAG_DEFAULT);
+ X509 *cert = OSSL_STORE_INFO_get1_CERT(info);
+
+ ok = cert != NULL
+ && X509_add_cert(*pcerts, cert, X509_ADD_FLAG_DEFAULT);
+ if (!ok)
+ X509_free(cert);
}
ncerts += ok;
break;
@@ -1069,7 +1072,11 @@ int load_key_certs_crls(const char *uri, int format, int maybe_stdin,
if (ok)
pcrl = NULL;
} else if (pcrls != NULL) {
- ok = sk_X509_CRL_push(*pcrls, OSSL_STORE_INFO_get1_CRL(info));
+ X509_CRL *crl = OSSL_STORE_INFO_get1_CRL(info);
+
+ ok = crl != NULL && sk_X509_CRL_push(*pcrls, crl);
+ if (!ok)
+ X509_CRL_free(crl);
}
ncrls += ok;
break;
diff --git a/test/build.info b/test/build.info
index 742737d873..6255b93e40 100644
--- a/test/build.info
+++ b/test/build.info
@@ -82,7 +82,7 @@ IF[{- !$disabled{tests} -}]
ENDIF
IF[{- !$disabled{'allocfail-tests'} -}]
- PROGRAMS{noninst}=handshake-memfail x509-memfail
+ PROGRAMS{noinst}=handshake-memfail x509-memfail load_key_certs_crls_memfail
ENDIF
IF[{- !$disabled{quic} -}]
@@ -621,6 +621,13 @@ IF[{- !$disabled{tests} -}]
INCLUDE[x509-memfail]=../include ../apps/include
DEPEND[x509-memfail]=../libcrypto.a libtestutil.a
+ SOURCE[load_key_certs_crls_memfail]=load_key_certs_crls_memfail.c ../apps/lib/apps.c \
+ ../apps/lib/app_rand.c ../apps/lib/app_provider.c ../apps/lib/app_libctx.c \
+ ../apps/lib/fmt.c ../apps/lib/apps_ui.c ../apps/lib/app_x509.c \
+ ../crypto/asn1/a_time.c ../crypto/ctype.c
+ INCLUDE[load_key_certs_crls_memfail]=.. ../include ../apps/include
+ DEPEND[load_key_certs_crls_memfail]=libtestutil.a ../libcrypto.a ../libssl.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/load_key_certs_crls_memfail.c b/test/load_key_certs_crls_memfail.c
new file mode 100644
index 0000000000..94297c0019
--- /dev/null
+++ b/test/load_key_certs_crls_memfail.c
@@ -0,0 +1,95 @@
+/*
+ * 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 may obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ *
+ * Regression test for issue #30364: memory leak in load_key_certs_crls()
+ * when X509_add_cert() or sk_X509_CRL_push() fails. Exercises the add/push
+ * path under OPENSSL_MALLOC_FAILURES so that with the fix the cert/CRL is
+ * freed on failure (memory_sanitizer would report a leak without the fix).
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <openssl/x509.h>
+#include <openssl/crypto.h>
+#include "apps.h"
+#include "app_libctx.h"
+#include "testutil.h"
+
+char *default_config_file = NULL;
+
+static char *certfile = NULL;
+static int mcount, rcount, fcount, scount;
+
+static int do_load_key_certs_crls(int allow_failure)
+{
+ STACK_OF(X509) *certs = NULL;
+ int ret = (allow_failure == 1) ? 0 : 1;
+ char uri[1024];
+
+ if (certfile == NULL)
+ return 0;
+
+ (void)snprintf(uri, sizeof(uri), "file:%s", certfile);
+ if (!TEST_true(load_key_certs_crls(uri, FORMAT_UNDEF, 0, NULL, "cert",
+ 1, NULL, NULL, NULL, NULL, &certs,
+ NULL, NULL, NULL)))
+ goto err;
+
+ ret = 1;
+err:
+ sk_X509_pop_free(certs, X509_free);
+ return ret;
+}
+
+static int test_record_alloc_counts(void)
+{
+ return do_load_key_certs_crls(1);
+}
+
+static int test_alloc_failures(void)
+{
+ return do_load_key_certs_crls(0);
+}
+
+static int test_report_alloc_counts(void)
+{
+ CRYPTO_get_alloc_counts(&mcount, &rcount, &fcount);
+ TEST_info("skip: %d count %d\n", scount, mcount - scount);
+ return 1;
+}
+
+int setup_tests(void)
+{
+ int ret = 0;
+ char *opmode = NULL;
+
+ if (app_create_libctx() == NULL)
+ return 0;
+
+ if (!TEST_ptr(opmode = test_get_argument(0)))
+ goto err;
+
+ if (!TEST_ptr(certfile = test_get_argument(1)))
+ goto err;
+
+ if (strcmp(opmode, "count") == 0) {
+ CRYPTO_get_alloc_counts(&scount, &rcount, &fcount);
+ ADD_TEST(test_record_alloc_counts);
+ ADD_TEST(test_report_alloc_counts);
+ } else {
+ ADD_TEST(test_alloc_failures);
+ }
+ ret = 1;
+err:
+ return ret;
+}
+
+void cleanup_tests(void)
+{
+}
diff --git a/test/recipes/90-test_memfail.t b/test/recipes/90-test_memfail.t
index fd168fe45a..d8bf1e7a4b 100644
--- a/test/recipes/90-test_memfail.t
+++ b/test/recipes/90-test_memfail.t
@@ -30,28 +30,25 @@ run(test(["handshake-memfail", "count", srctop_dir("test", "certs")], stderr =>
run(test(["x509-memfail", "count", srctop_file("test", "certs", "servercert.pem")], stderr => "$resultdir/x509countinfo.txt"));
+run(test(["load_key_certs_crls_memfail", "count", srctop_file("test", "certs", "servercert.pem")], stderr => "$resultdir/load_key_certs_crls_countinfo.txt"));
+
sub get_count_info {
my ($infile) = @_;
- my @vals;
+ my ($skipcount, $malloccount) = (0, 0);
- # Read in our input file
- open my $handle, '<', "$infile";
+ open my $handle, '<', "$infile" or return (0, 0);
chomp(my @lines = <$handle>);
close $handle;
- # parse the input file
- foreach(@lines) {
- if ($_ =~/skip:/) {
- @vals = split ' ', $_;
+ # Match the test program output: "skip: <number> count <number>"
+ # Stderr may be captured with a "# " prefix per line (TAP-style).
+ foreach (@lines) {
+ if (/\bskip:\s*(\d+)\s+count\s+(\d+)/) {
+ $skipcount = $1;
+ $malloccount = $2;
last;
}
}
- #
- #The number of allocations we skip is in argument 2
- #The number of mallocs we should test is in argument 4
- #
- my $skipcount = $vals[2];
- my $malloccount = $vals[4];
return ($skipcount, $malloccount);
}
@@ -59,11 +56,17 @@ my ($hsskipcount, $hsmalloccount) = get_count_info("$resultdir/hscountinfo.txt")
my ($x509skipcount, $x509malloccount) = get_count_info("$resultdir/x509countinfo.txt");
+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;
+plan skip_all => "could not get malloc counts (one or more count runs failed or output format changed)"
+ if $total_malloccount == 0;
+
#
# Now we can plan our tests. We plan to run malloccount iterations of this
# test
#
-plan tests => $hsmalloccount + $x509malloccount;
+plan tests => $total_malloccount;
sub run_memfail_test {
my $skipcount = $_[0];
@@ -88,3 +91,5 @@ run_memfail_test($hsskipcount, $hsmalloccount, ["handshake-memfail", "run", srct
run_memfail_test($x509skipcount, $x509malloccount, ["x509-memfail", "run", srctop_file("test", "certs", "servercert.pem")]);
+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")]);
+