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;
}