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;
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;
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} -}]
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
--- /dev/null
+/*
+ * 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)
+{
+}
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);
}
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];
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")]);
+