Commit 5f61ae8154 for openssl.org

commit 5f61ae81549a6872869469bcb2784cdc08be9271
Author: Greensi7 <adam.tabak04@gmail.com>
Date:   Thu Aug 13 17:58:42 2026 +0200

    Fix memory leak in PKCS12_parse

    If appending an additional certificate to the CA stack fails
    PKCS12_parse() leaves certificates appended earlier during the call
    in the output stack. If PKCS12_parse() allocated the stack
    then the whole stack might be leaked if caller assumes failure.

    Fix:
    Record the original stack and its size then on failure
    remove certificates appended during the failed call.
    Free the stack if it was allocated by PKCS12_parse().

    Found by : store fuzzer (MFAIL-test)

    Assisted-by: ChatGPT:gpt-5.6
    Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
    Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
    Merge-date: Thu Aug 27 16:49:42 2026
    Merged-from: https://github.com/openssl/openssl/pull/32407

diff --git a/crypto/pkcs12/p12_kiss.c b/crypto/pkcs12/p12_kiss.c
index 1c2e49a57f..20fd100cf3 100644
--- a/crypto/pkcs12/p12_kiss.c
+++ b/crypto/pkcs12/p12_kiss.c
@@ -37,6 +37,9 @@ int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
 {
     STACK_OF(X509) *ocerts = NULL;
     X509 *x = NULL;
+    STACK_OF(X509) *initial_ca = ca != NULL ? *ca : NULL;
+    int initial_ca_count = initial_ca == NULL ? 0 : sk_X509_num(initial_ca);
+    int ca_added_count = 0;

     if (pkey != NULL)
         *pkey = NULL;
@@ -127,6 +130,19 @@ err:
         X509_free(*cert);
         *cert = NULL;
     }
+
+    ca_added_count = ca == NULL || *ca == NULL ? 0 : sk_X509_num(*ca) - initial_ca_count;
+    while (ca_added_count > 0) {
+        X509 *t_cert = sk_X509_pop(*ca);
+        X509_free(t_cert);
+        ca_added_count--;
+    }
+
+    if (initial_ca == NULL && ca != NULL) {
+        sk_X509_free(*ca);
+        *ca = NULL;
+    }
+
     X509_free(x);
     OSSL_STACK_OF_X509_free(ocerts);
     return 0;
diff --git a/doc/man3/PKCS12_parse.pod b/doc/man3/PKCS12_parse.pod
index f16600147b..834af687a4 100644
--- a/doc/man3/PKCS12_parse.pod
+++ b/doc/man3/PKCS12_parse.pod
@@ -34,6 +34,9 @@ The B<friendlyName> and B<localKeyID> attributes (if present) on each
 certificate will be stored in the B<alias> and B<keyid> attributes of the
 B<X509> structure.

+If parsing fails B<*pkey>, B<*cert> are set to NULL and if B<ca> is non-NULL
+then B<*ca> is left unchanged.
+
 The parameter B<pass> is interpreted as a string in the UTF-8 encoding. If it
 is not valid UTF-8, then it is assumed to be ISO8859-1 instead.

@@ -67,7 +70,7 @@ L<passphrase-encoding(7)>

 =head1 COPYRIGHT

-Copyright 2002-2020 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2002-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
diff --git a/test/pkcs12_api_test.c b/test/pkcs12_api_test.c
index 150a3cf29d..b615c9d7a3 100644
--- a/test/pkcs12_api_test.c
+++ b/test/pkcs12_api_test.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2022-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
@@ -134,6 +134,81 @@ err:
     return TEST_true(ret);
 }

+/*
+ * If appending an additional certificate to the CA stack fails,
+ * PKCS12_parse() should free its own allocated CA stack.
+ */
+static int pkcs12_parse_mfail_test(void)
+{
+    int ret;
+    PKCS12 *p12 = NULL;
+    STACK_OF(X509) *ca = NULL;
+
+    if (!TEST_ptr(p12 = PKCS12_load(in_file)))
+        return -1;
+
+    MFAIL_start();
+    ret = PKCS12_parse(p12, in_pass, NULL, NULL, &ca);
+    MFAIL_end();
+
+    if (ret == 0 && !TEST_ptr_null(ca))
+        ret = -1;
+
+    /*
+     * Only free the stack on success, since PKCS12_parse()
+     * should free its own allocated stack on failure.
+     */
+    if (ret == 1)
+        OSSL_STACK_OF_X509_free(ca);
+
+    PKCS12_free(p12);
+    return ret;
+}
+
+/*
+ * If appending an additional certificate to the CA stack fails,
+ * PKCS12_parse() should leave the original CA stack unmodified.
+ */
+static int pkcs12_parse_existing_ca_mfail_test(void)
+{
+    int i, ret = -1;
+    PKCS12 *p12 = NULL;
+    STACK_OF(X509) *ca = NULL, *initial_ca = NULL;
+    X509 *initial_certs[3] = { NULL };
+
+    if (!TEST_ptr(p12 = PKCS12_load(in_file))
+        || !TEST_ptr(ca = sk_X509_new_null()))
+        goto err;
+
+    for (i = 0; i < 3; i++) {
+        if (!TEST_ptr(initial_certs[i] = X509_new()))
+            goto err;
+
+        if (!TEST_true(sk_X509_push(ca, initial_certs[i]))) {
+            X509_free(initial_certs[i]);
+            goto err;
+        }
+    }
+    initial_ca = ca;
+
+    MFAIL_start();
+    ret = PKCS12_parse(p12, in_pass, NULL, NULL, &ca);
+    MFAIL_end();
+
+    if (ret == 0
+        && (!TEST_ptr_eq(ca, initial_ca)
+            || !TEST_int_eq(sk_X509_num(ca), 3)
+            || !TEST_ptr_eq(sk_X509_value(ca, 0), initial_certs[0])
+            || !TEST_ptr_eq(sk_X509_value(ca, 1), initial_certs[1])
+            || !TEST_ptr_eq(sk_X509_value(ca, 2), initial_certs[2])))
+        ret = -1;
+
+err:
+    PKCS12_free(p12);
+    OSSL_STACK_OF_X509_free(ca);
+    return ret;
+}
+
 static int pkcs12_create_cb(PKCS12_SAFEBAG *bag, void *cbarg)
 {
     int cb_ret = *((int *)cbarg);
@@ -347,6 +422,8 @@ int setup_tests(void)

     ADD_TEST(test_null_args);
     ADD_TEST(pkcs12_parse_test);
+    ADD_MFAIL_NO_CHECK_TEST(pkcs12_parse_mfail_test);
+    ADD_MFAIL_NO_CHECK_TEST(pkcs12_parse_existing_ca_mfail_test);
     ADD_ALL_TESTS(pkcs12_create_ex2_test, 3);
     ADD_TEST(test_PKCS12_set_pbmac1_pbkdf2_saltlen_zero);
     ADD_TEST(test_PKCS12_set_pbmac1_pbkdf2_invalid_saltlen);