From 8800b76d8a33b7ae8ec462005b791fe8c265470c Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Thu, 18 Jun 2026 00:34:11 +0200 Subject: [PATCH] net.openssl: fix double-free on shutdown() The issue can be reliably reproduced by configuring a SOCKS5 proxy server, as follows. import net.http import rand import time proxy_user := rand.string(16) proxy_pass := rand.string(16) proxy_url := 'socks5://${proxy_user}:${proxy_pass}@127.0.0.1:9050' mut config := http.FetchConfig{ url: 'https://vlang.io' proxy: http.new_http_proxy(proxy_url) or { panic(err) } } mut resp := http.Response{} for { resp = http.fetch(config) or { println('Failed to fetch data from the server!') return } if resp.status_code != 200 { println('Bad status code: ${resp.status_code}\n') time.sleep(time.second * 10) continue } break } Backtrace: X509_VERIFY_PARAM_free (param=0xfeff) at ../crypto/x509/x509_vpm.c:100 0x00007ffff7da37fc in SSL_CTX_free (a=0x555555626a00) at ../ssl/ssl_lib.c:4405 0x00007ffff7da4aed in SSL_free (s=0x55555562dfb0) at ../ssl/ssl_lib.c:1446 0x000055555558b144 in net__openssl__SSLConn_shutdown () 0x00005555555966b1 in net__http__HttpProxy_http_do () 0x0000555555598b08 in net__http__Request_method_and_url_to_response () 0x0000555555594b06 in net__http__Request_do () 0x00005555555947e4 in net.http.fetch () 0x000055555559bfb9 in main.main () 0x000055555559c997 in main () --- vlib/net/openssl/ssl_connection.c.v | 51 ++++++++++++++++++----------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/vlib/net/openssl/ssl_connection.c.v b/vlib/net/openssl/ssl_connection.c.v index 5fa43a3535ea17..bef373f3213031 100644 --- a/vlib/net/openssl/ssl_connection.c.v +++ b/vlib/net/openssl/ssl_connection.c.v @@ -98,37 +98,48 @@ pub fn (mut s SSLConn) shutdown() ! { eprintln(@METHOD) } - if s.ssl != 0 { + if s.ssl != unsafe { nil } { deadline := ssl_timeout_deadline(s.duration) - for { - mut res := C.SSL_shutdown(voidptr(s.ssl)) + mut shutdown_done := false + + for !shutdown_done { + res := C.SSL_shutdown(voidptr(s.ssl)) if res == 1 { + shutdown_done = true break } - - err_res := ssl_error(res, s.ssl) or { - break // We break to free rest of resources - } - if err_res == .ssl_error_want_read { - s.wait_for_read(ssl_remaining_timeout(deadline))! - continue - } else if err_res == .ssl_error_want_write { - s.wait_for_write(ssl_remaining_timeout(deadline))! + if res == 0 { + // Second SSL_shutdown() needed for full bidirectional shutdown. continue } - if s.ssl != 0 { - unsafe { C.SSL_free(voidptr(s.ssl)) } - } - if s.sslctx != 0 { - C.SSL_CTX_free(s.sslctx) + + err_res := ssl_error(res, s.ssl) or { break } + + match err_res { + .ssl_error_want_read { + s.wait_for_read(ssl_remaining_timeout(deadline)) or { break } + } + .ssl_error_want_write { + s.wait_for_write(ssl_remaining_timeout(deadline)) or { break } + } + else { + break + } } - return error('net.openssl Could not connect using SSL. (${err_res}),err') } - C.SSL_free(voidptr(s.ssl)) + + // Always free SSL object first. + if s.ssl != unsafe { nil } { + unsafe { C.SSL_free(voidptr(s.ssl)) } + s.ssl = unsafe { nil } + } } - if s.sslctx != 0 { + + if s.sslctx != unsafe { nil } { C.SSL_CTX_free(s.sslctx) + s.sslctx = unsafe { nil } } + if s.owns_socket { net.shutdown(s.handle) net.close(s.handle)!