Commit ff542b6de2 for openssl.org

commit ff542b6de282b903d296e7a53c3bd3f495e3a7d3
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date:   Sat Jul 4 08:06:48 2026 +0900

    Fix EVP_CIPHER_CTX leak in BIO_f_cipher() on BIO_dup_chain()

    BIO_dup_chain() builds each duplicate BIO via BIO_new(), which for a cipher
    BIO runs enc_new() and allocates a fresh EVP_CIPHER_CTX. The BIO_CTRL_DUP
    handler then allocated a second context and overwrote the pointer, orphaning
    (and leaking) the enc_new() allocation, one leaked EVP_CIPHER_CTX per
    duplicated cipher BIO.

    Reuse the context that enc_new() already allocated by copying into it,
    mirroring the digest filter (bio_md.c). This also removes the redundant
    allocation and its failure path.

    Add a regression test that dup is an initialized cipher BIO and frees it,
    exercising the leak path so it is caught under a leak sanitizer.

    Fixes #31803

    Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Andrew Dinh <andrewd@openssl.org>
    MergeDate: Sat Aug 29 12:47:08 2026
    (Merged from https://github.com/openssl/openssl/pull/31854)

diff --git a/crypto/evp/bio_enc.c b/crypto/evp/bio_enc.c
index a56bc3b9e6..4b12e49359 100644
--- a/crypto/evp/bio_enc.c
+++ b/crypto/evp/bio_enc.c
@@ -396,9 +396,6 @@ static long enc_ctrl(BIO *b, int cmd, long num, void *ptr)
     case BIO_CTRL_DUP:
         dbio = (BIO *)ptr;
         dctx = BIO_get_data(dbio);
-        dctx->cipher = EVP_CIPHER_CTX_new();
-        if (dctx->cipher == NULL)
-            return 0;
         ret = EVP_CIPHER_CTX_copy(dctx->cipher, ctx->cipher);
         if (ret)
             BIO_set_init(dbio, 1);
diff --git a/test/bio_enc_test.c b/test/bio_enc_test.c
index bfc43af761..3590626f01 100644
--- a/test/bio_enc_test.c
+++ b/test/bio_enc_test.c
@@ -327,6 +327,28 @@ end:
     return ret;
 }

+static int test_bio_enc_dup(void)
+{
+    BIO *b = NULL, *dup = NULL;
+    EVP_CIPHER_CTX *ctx = NULL;
+    int ret = 0;
+
+    if (!TEST_ptr(b = BIO_new(BIO_f_cipher()))
+        || !TEST_int_gt(BIO_get_cipher_ctx(b, &ctx), 0)
+        || !TEST_true(EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL,
+            KEY, IV, ENCRYPT)))
+        goto err;
+
+    if (!TEST_ptr(dup = BIO_dup_chain(b)))
+        goto err;
+
+    ret = 1;
+err:
+    BIO_free_all(dup);
+    BIO_free_all(b);
+    return ret;
+}
+
 int setup_tests(void)
 {
     ADD_ALL_TESTS(test_bio_enc_aes_128_cbc, 2);
@@ -340,5 +362,6 @@ int setup_tests(void)
 #endif
 #endif
     ADD_TEST(test_bio_enc_eof_read_flush);
+    ADD_TEST(test_bio_enc_dup);
     return 1;
 }