@@ -2103,6 +2103,86 @@ def test_unwrap(self):
21032103 c_in .write (s_out .read ())
21042104 client .unwrap ()
21052105
2106+ def test_sni_callback_context_released_and_callback_raises (self ):
2107+ # Variant of the test below without a HelloRetryRequest: the callback
2108+ # switches the connection to another context, drops the last
2109+ # references to the context that carries it, and raises. The C
2110+ # callback must not touch that context after the Python callback
2111+ # returned.
2112+ client_ctx , server_ctx , hostname = testing_context ()
2113+ leaf_ctx = server_ctx
2114+
2115+ def sni_cb (sslobj , server_name , ctx ):
2116+ sslobj .context = leaf_ctx
2117+ del ctx
2118+ raise LookupError ("no certificate for " + repr (server_name ))
2119+
2120+ def make_server ():
2121+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
2122+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
2123+ dispatch_ctx .sni_callback = sni_cb
2124+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2125+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
2126+ return server , s_in , s_out
2127+
2128+ server , s_in , s_out = make_server ()
2129+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2130+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
2131+ with self .assertRaises (ssl .SSLWantReadError ):
2132+ client .do_handshake ()
2133+ s_in .write (c_out .read ())
2134+ with support .catch_unraisable_exception () as cm :
2135+ with self .assertRaises (ssl .SSLError ):
2136+ server .do_handshake ()
2137+ self .assertIsInstance (cm .unraisable .exc_value , LookupError )
2138+ self .assertIs (server .context , leaf_ctx )
2139+
2140+ def test_sni_callback_context_released_before_second_client_hello (self ):
2141+ # The SSLContext carrying sni_callback may be released by the
2142+ # application once the callback has switched the connection over to
2143+ # another context. If the server then sends a HelloRetryRequest, the
2144+ # second ClientHello makes OpenSSL consult the original SSL_CTX's
2145+ # servername callback again; that must not use the deallocated
2146+ # SSLContext object.
2147+ client_ctx , leaf_ctx , hostname = testing_context ()
2148+ calls = []
2149+
2150+ def make_server ():
2151+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
2152+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
2153+ # Force a HelloRetryRequest: the client offers an X25519 key
2154+ # share first, the server only accepts P-384.
2155+ dispatch_ctx .set_ecdh_curve ("secp384r1" )
2156+ def sni_cb (sslobj , server_name , ctx ):
2157+ calls .append (server_name )
2158+ sslobj .context = leaf_ctx
2159+ dispatch_ctx .sni_callback = sni_cb
2160+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2161+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
2162+ return server , s_in , s_out , weakref .ref (dispatch_ctx )
2163+
2164+ # After this only the C-level SSL object references dispatch_ctx.
2165+ server , s_in , s_out , dispatch_ref = make_server ()
2166+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2167+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
2168+ for _ in range (10 ):
2169+ for obj , out , peer_in in ((client , c_out , s_in ),
2170+ (server , s_out , c_in )):
2171+ try :
2172+ obj .do_handshake ()
2173+ except ssl .SSLWantReadError :
2174+ pass
2175+ if out .pending :
2176+ peer_in .write (out .read ())
2177+ client .do_handshake ()
2178+ server .do_handshake ()
2179+ support .gc_collect ()
2180+ self .assertIsNone (dispatch_ref ())
2181+ self .assertGreaterEqual (len (calls ), 1 )
2182+ self .assertEqual (calls [0 ], hostname )
2183+ self .assertIs (server .context , leaf_ctx )
2184+ self .assertIsNotNone (client .cipher ())
2185+
21062186class SimpleBackgroundTests (unittest .TestCase ):
21072187 """Tests that connect to a simple server running in the background"""
21082188
@@ -5082,15 +5162,27 @@ def non_linux_skip_if_other_okay_error(self, err):
50825162 return # Expect the full test setup to always work on Linux.
50835163 if (isinstance (err , ConnectionResetError ) or
50845164 (isinstance (err , OSError ) and err .errno == errno .EINVAL ) or
5085- re .search ('wrong.version.number' , getattr (err , "reason" , "" ), re .I )):
5165+ re .search (
5166+ # Matches the following error messages:
5167+ # '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)'
5168+ # '[SSL: RECORD_LAYER_FAILURE] record layer failure (_ssl.c:1109)'
5169+ # '[SSL: HTTP_REQUEST] http request (_ssl.c:1143)'
5170+ r'wrong.version.number|record.layer.failure|http.request' ,
5171+ str (getattr (err , "reason" , "" )),
5172+ re .IGNORECASE ,
5173+ )
5174+ ):
50865175 # On Windows the TCP RST leads to a ConnectionResetError
50875176 # (ECONNRESET) which Linux doesn't appear to surface to userspace.
50885177 # If wrap_socket() winds up on the "if connected:" path and doing
5089- # the actual wrapping... we get an SSLError from OpenSSL. Typically
5090- # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario
5091- # we're specifically trying to test. The way this test is written
5092- # is known to work on Linux. We'll skip it anywhere else that it
5093- # does not present as doing so.
5178+ # the actual wrapping... we get an SSLError from OpenSSL. This is
5179+ # typically WRONG_VERSION_NUMBER. The same happens on iOS, but
5180+ # RECORD_LAYER_FAILURE or HTTP_REQUEST is the error.
5181+ #
5182+ # While appropriate, these scenarios aren't what we're specifically
5183+ # trying to test. The way this test is written is known to work on
5184+ # Linux. We'll skip it anywhere else that it does not present as
5185+ # doing so.
50945186 try :
50955187 self .skipTest (f"Could not recreate conditions on { sys .platform } :"
50965188 f" { err = } " )
0 commit comments