@@ -2096,6 +2096,86 @@ def test_unwrap(self):
20962096 c_in .write (s_out .read ())
20972097 client .unwrap ()
20982098
2099+ def test_sni_callback_context_released_and_callback_raises (self ):
2100+ # Variant of the test below without a HelloRetryRequest: the callback
2101+ # switches the connection to another context, drops the last
2102+ # references to the context that carries it, and raises. The C
2103+ # callback must not touch that context after the Python callback
2104+ # returned.
2105+ client_ctx , server_ctx , hostname = testing_context ()
2106+ leaf_ctx = server_ctx
2107+
2108+ def sni_cb (sslobj , server_name , ctx ):
2109+ sslobj .context = leaf_ctx
2110+ del ctx
2111+ raise LookupError ("no certificate for " + repr (server_name ))
2112+
2113+ def make_server ():
2114+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
2115+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
2116+ dispatch_ctx .sni_callback = sni_cb
2117+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2118+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
2119+ return server , s_in , s_out
2120+
2121+ server , s_in , s_out = make_server ()
2122+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2123+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
2124+ with self .assertRaises (ssl .SSLWantReadError ):
2125+ client .do_handshake ()
2126+ s_in .write (c_out .read ())
2127+ with support .catch_unraisable_exception () as cm :
2128+ with self .assertRaises (ssl .SSLError ):
2129+ server .do_handshake ()
2130+ self .assertIsInstance (cm .unraisable .exc_value , LookupError )
2131+ self .assertIs (server .context , leaf_ctx )
2132+
2133+ def test_sni_callback_context_released_before_second_client_hello (self ):
2134+ # The SSLContext carrying sni_callback may be released by the
2135+ # application once the callback has switched the connection over to
2136+ # another context. If the server then sends a HelloRetryRequest, the
2137+ # second ClientHello makes OpenSSL consult the original SSL_CTX's
2138+ # servername callback again; that must not use the deallocated
2139+ # SSLContext object.
2140+ client_ctx , leaf_ctx , hostname = testing_context ()
2141+ calls = []
2142+
2143+ def make_server ():
2144+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
2145+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
2146+ # Force a HelloRetryRequest: the client offers an X25519 key
2147+ # share first, the server only accepts P-384.
2148+ dispatch_ctx .set_ecdh_curve ("secp384r1" )
2149+ def sni_cb (sslobj , server_name , ctx ):
2150+ calls .append (server_name )
2151+ sslobj .context = leaf_ctx
2152+ dispatch_ctx .sni_callback = sni_cb
2153+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2154+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
2155+ return server , s_in , s_out , weakref .ref (dispatch_ctx )
2156+
2157+ # After this only the C-level SSL object references dispatch_ctx.
2158+ server , s_in , s_out , dispatch_ref = make_server ()
2159+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2160+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
2161+ for _ in range (10 ):
2162+ for obj , out , peer_in in ((client , c_out , s_in ),
2163+ (server , s_out , c_in )):
2164+ try :
2165+ obj .do_handshake ()
2166+ except ssl .SSLWantReadError :
2167+ pass
2168+ if out .pending :
2169+ peer_in .write (out .read ())
2170+ client .do_handshake ()
2171+ server .do_handshake ()
2172+ support .gc_collect ()
2173+ self .assertIsNone (dispatch_ref ())
2174+ self .assertGreaterEqual (len (calls ), 1 )
2175+ self .assertEqual (calls [0 ], hostname )
2176+ self .assertIs (server .context , leaf_ctx )
2177+ self .assertIsNotNone (client .cipher ())
2178+
20992179class SimpleBackgroundTests (unittest .TestCase ):
21002180 """Tests that connect to a simple server running in the background"""
21012181
0 commit comments