@@ -1818,6 +1818,86 @@ def test_unwrap(self):
18181818 c_in .write (s_out .read ())
18191819 client .unwrap ()
18201820
1821+ def test_sni_callback_context_released_and_callback_raises (self ):
1822+ # Variant of the test below without a HelloRetryRequest: the callback
1823+ # switches the connection to another context, drops the last
1824+ # references to the context that carries it, and raises. The C
1825+ # callback must not touch that context after the Python callback
1826+ # returned.
1827+ client_ctx , server_ctx , hostname = testing_context ()
1828+ leaf_ctx = server_ctx
1829+
1830+ def sni_cb (sslobj , server_name , ctx ):
1831+ sslobj .context = leaf_ctx
1832+ del ctx
1833+ raise LookupError ("no certificate for " + repr (server_name ))
1834+
1835+ def make_server ():
1836+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
1837+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
1838+ dispatch_ctx .sni_callback = sni_cb
1839+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
1840+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
1841+ return server , s_in , s_out
1842+
1843+ server , s_in , s_out = make_server ()
1844+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
1845+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
1846+ with self .assertRaises (ssl .SSLWantReadError ):
1847+ client .do_handshake ()
1848+ s_in .write (c_out .read ())
1849+ with support .catch_unraisable_exception () as cm :
1850+ with self .assertRaises (ssl .SSLError ):
1851+ server .do_handshake ()
1852+ self .assertIsInstance (cm .unraisable .exc_value , LookupError )
1853+ self .assertIs (server .context , leaf_ctx )
1854+
1855+ def test_sni_callback_context_released_before_second_client_hello (self ):
1856+ # The SSLContext carrying sni_callback may be released by the
1857+ # application once the callback has switched the connection over to
1858+ # another context. If the server then sends a HelloRetryRequest, the
1859+ # second ClientHello makes OpenSSL consult the original SSL_CTX's
1860+ # servername callback again; that must not use the deallocated
1861+ # SSLContext object.
1862+ client_ctx , leaf_ctx , hostname = testing_context ()
1863+ calls = []
1864+
1865+ def make_server ():
1866+ dispatch_ctx = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
1867+ dispatch_ctx .load_cert_chain (SIGNED_CERTFILE )
1868+ # Force a HelloRetryRequest: the client offers an X25519 key
1869+ # share first, the server only accepts P-384.
1870+ dispatch_ctx .set_ecdh_curve ("secp384r1" )
1871+ def sni_cb (sslobj , server_name , ctx ):
1872+ calls .append (server_name )
1873+ sslobj .context = leaf_ctx
1874+ dispatch_ctx .sni_callback = sni_cb
1875+ s_in , s_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
1876+ server = dispatch_ctx .wrap_bio (s_in , s_out , server_side = True )
1877+ return server , s_in , s_out , weakref .ref (dispatch_ctx )
1878+
1879+ # After this only the C-level SSL object references dispatch_ctx.
1880+ server , s_in , s_out , dispatch_ref = make_server ()
1881+ c_in , c_out = ssl .MemoryBIO (), ssl .MemoryBIO ()
1882+ client = client_ctx .wrap_bio (c_in , c_out , server_hostname = hostname )
1883+ for _ in range (10 ):
1884+ for obj , out , peer_in in ((client , c_out , s_in ),
1885+ (server , s_out , c_in )):
1886+ try :
1887+ obj .do_handshake ()
1888+ except ssl .SSLWantReadError :
1889+ pass
1890+ if out .pending :
1891+ peer_in .write (out .read ())
1892+ client .do_handshake ()
1893+ server .do_handshake ()
1894+ support .gc_collect ()
1895+ self .assertIsNone (dispatch_ref ())
1896+ self .assertGreaterEqual (len (calls ), 1 )
1897+ self .assertEqual (calls [0 ], hostname )
1898+ self .assertIs (server .context , leaf_ctx )
1899+ self .assertIsNotNone (client .cipher ())
1900+
18211901class SimpleBackgroundTests (unittest .TestCase ):
18221902 """Tests that connect to a simple server running in the background"""
18231903
0 commit comments