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