Commit 435feadaf4 for openssl.org

commit 435feadaf4f9efd9bd05eab9c89cf273c26b86b7
Author: herbenderbler <johnclaus@gmail.com>
Date:   Tue Mar 24 20:38:48 2026 -0600

    Fix record layer leak when swapping chained transport BIO

    tls_set1_bio() freed only the top BIO (BIO_free). Use BIO_free_all so
    a pushed transport chain is released when the record layer replaces
    its BIO.

    Add test_ssl_set_wbio_chain_no_leak in sslapitest (stacked BIO chain
    via SSL_set0_wbio) per reviewer feedback on GH openssl#30483. Drop the
    Perl s_client reconnect recipe and CHANGES entry (internal leak only).

    Fixes #30458

    Reviewed-by: Simo Sorce <simo@redhat.com>
    Reviewed-by: Matt Caswell <matt@openssl.foundation>
    MergeDate: Tue Apr 28 06:39:25 2026
    (Merged from https://github.com/openssl/openssl/pull/30483)

diff --git a/ssl/record/methods/tls_common.c b/ssl/record/methods/tls_common.c
index 0ffdfe500a..7de395f277 100644
--- a/ssl/record/methods/tls_common.c
+++ b/ssl/record/methods/tls_common.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022-2025 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
@@ -1944,7 +1944,7 @@ int tls_set1_bio(OSSL_RECORD_LAYER *rl, BIO *bio)
 {
     if (bio != NULL && !BIO_up_ref(bio))
         return 0;
-    BIO_free(rl->bio);
+    BIO_free_all(rl->bio);
     rl->bio = bio;

     return 1;
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 9e994062d5..38faa22bbf 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -3604,6 +3604,52 @@ static int test_ssl_bio_change_wbio(void)
     return execute_test_ssl_bio(0, CHANGE_WBIO);
 }

+/*
+ * Regression for GH #30458: tls_set1_bio() must BIO_free_all the old chain
+ * when the write BIO is replaced, not only the top BIO.
+ */
+static int test_ssl_set_wbio_chain_no_leak(void)
+{
+    SSL_CTX *ctx = NULL;
+    SSL *ssl = NULL;
+    BIO *bio = NULL, *filter = NULL, *chain1 = NULL;
+    int testresult = 0;
+
+    if (!TEST_ptr(ctx = SSL_CTX_new_ex(libctx, NULL, TLS_method())))
+        goto end;
+    if (!TEST_ptr(ssl = SSL_new(ctx)))
+        goto end;
+
+    if (!TEST_ptr(filter = BIO_new(BIO_f_nbio_test())))
+        goto end;
+    if (!TEST_ptr(bio = BIO_new(BIO_s_mem()))) {
+        BIO_free(filter);
+        filter = NULL;
+        goto end;
+    }
+    if (!TEST_ptr(chain1 = BIO_push(filter, bio))) {
+        BIO_free_all(filter);
+        filter = bio = NULL;
+        goto end;
+    }
+    filter = bio = NULL;
+
+    SSL_set0_wbio(ssl, chain1);
+    chain1 = NULL;
+    SSL_set0_wbio(ssl, NULL);
+
+    testresult = 1;
+
+end:
+    BIO_free(filter);
+    BIO_free(bio);
+    BIO_free(chain1);
+    SSL_free(ssl);
+    SSL_CTX_free(ctx);
+
+    return testresult;
+}
+
 #if !defined(OPENSSL_NO_TLS1_2) || defined(OSSL_NO_USABLE_TLS1_3)
 typedef struct {
     /* The list of sig algs */
@@ -14835,6 +14881,7 @@ int setup_tests(void)
     ADD_TEST(test_ssl_bio_pop_ssl_bio);
     ADD_TEST(test_ssl_bio_change_rbio);
     ADD_TEST(test_ssl_bio_change_wbio);
+    ADD_TEST(test_ssl_set_wbio_chain_no_leak);
 #if !defined(OPENSSL_NO_TLS1_2) || defined(OSSL_NO_USABLE_TLS1_3)
     ADD_ALL_TESTS(test_set_sigalgs, OSSL_NELEM(testsigalgs) * 2);
     ADD_TEST(test_keylog);