@@ -2179,6 +2179,86 @@ def test_unwrap(self):
21792179 c_in .write (s_out .read ())
21802180 client .unwrap ()
21812181
2182+ def test_sni_callback_context_released_and_callback_raises (self ):
2183+ # Variant of the test below without a HelloRetryRequest: the callback
2184+ # switches the connection to another context, drops the last
2185+ # references to the context that carries it, and raises. The C
2186+ # callback must not touch that context after the Python callback
2187+ # returned.
2188+ client_ctx , server_ctx , hostname = testing_context ()
2189+ leaf_ctx = server_ctx
2190+
2191+ def sni_cb (sslobj , server_name , ctx ):
2192+ sslobj .context = leaf_ctx
2193+ del ctx
2194+ raise LookupError ("no certificate for " + repr (server_name ))
2195+
2196+ def make_server ():
2197+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
2198+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
2199+ dispatch_ctx .sni_callback = sni_cb
2200+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2201+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
2202+ return server , s_in , s_out
2203+
2204+ server , s_in , s_out = make_server ()
2205+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2206+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
2207+ with self .assertRaises (ssl .SSLWantReadError ):
2208+ client .do_handshake ()
2209+ s_in .write (c_out .read ())
2210+ with support .catch_unraisable_exception () as cm :
2211+ with self .assertRaises (ssl .SSLError ):
2212+ server .do_handshake ()
2213+ self .assertIsInstance (cm .unraisable .exc_value , LookupError )
2214+ self .assertIs (server .context , leaf_ctx )
2215+
2216+ def test_sni_callback_context_released_before_second_client_hello (self ):
2217+ # The SSLContext carrying sni_callback may be released by the
2218+ # application once the callback has switched the connection over to
2219+ # another context. If the server then sends a HelloRetryRequest, the
2220+ # second ClientHello makes OpenSSL consult the original SSL_CTX's
2221+ # servername callback again; that must not use the deallocated
2222+ # SSLContext object.
2223+ client_ctx , leaf_ctx , hostname = testing_context ()
2224+ calls = []
2225+
2226+ def make_server ():
2227+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
2228+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
2229+ # Force a HelloRetryRequest: the client offers an X25519 key
2230+ # share first, the server only accepts P-384.
2231+ dispatch_ctx .set_ecdh_curve ("secp384r1" )
2232+ def sni_cb (sslobj , server_name , ctx ):
2233+ calls .append (server_name )
2234+ sslobj .context = leaf_ctx
2235+ dispatch_ctx .sni_callback = sni_cb
2236+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2237+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
2238+ return server , s_in , s_out , weakref .ref (dispatch_ctx )
2239+
2240+ # After this only the C-level SSL object references dispatch_ctx.
2241+ server , s_in , s_out , dispatch_ref = make_server ()
2242+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
2243+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
2244+ for _ in range (10 ):
2245+ for obj , out , peer_in in ((client , c_out , s_in ),
2246+ (server , s_out , c_in )):
2247+ try :
2248+ obj .do_handshake ()
2249+ except ssl .SSLWantReadError :
2250+ pass
2251+ if out .pending :
2252+ peer_in .write (out .read ())
2253+ client .do_handshake ()
2254+ server .do_handshake ()
2255+ support .gc_collect ()
2256+ self .assertIsNone (dispatch_ref ())
2257+ self .assertGreaterEqual (len (calls ), 1 )
2258+ self .assertEqual (calls [0 ], hostname )
2259+ self .assertIs (server .context , leaf_ctx )
2260+ self .assertIsNotNone (client .cipher ())
2261+
21822262class SimpleBackgroundTests (unittest .TestCase ):
21832263 """Tests that connect to a simple server running in the background"""
21842264
0 commit comments