From 435feadaf4f9efd9bd05eab9c89cf273c26b86b7 Mon Sep 17 00:00:00 2001 From: herbenderbler Date: Tue, 24 Mar 2026 20:38:48 -0600 Subject: [PATCH] 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 Reviewed-by: Matt Caswell MergeDate: Tue Apr 28 06:39:25 2026 (Merged from https://github.com/openssl/openssl/pull/30483) --- ssl/record/methods/tls_common.c | 4 +-- test/sslapitest.c | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/ssl/record/methods/tls_common.c b/ssl/record/methods/tls_common.c index 0ffdfe500a2..7de395f2775 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 9e994062d51..38faa22bbf0 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); -- 2.47.3