CPython issue: python/cpython#146080
_servername_callback in Modules/_ssl.c (line 5197) calls Py_DECREF(ssl_socket) on the error label, but ssl_socket can be NULL when the SSLObject wrapper has been garbage collected while the C-level _SSLSocket still exists. The owner weakref dies, PyWeakref_GetRef returns 0, ssl_socket is set to NULL at line 5127, and the goto error at line 5128 jumps to Py_DECREF(NULL) — a guaranteed segfault.
Change Py_DECREF(ssl_socket) to Py_XDECREF(ssl_socket) at line 5197.
CPython main branch (3.15.0a6). Likely affects 3.13+ (the code structure has been stable).
import gc, ssl, test.support
CERTFILE = test.support.findfile("keycert.pem", subdir="certdata")
server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_ctx.load_cert_chain(CERTFILE)
server_ctx.set_servername_callback(lambda *a: None)
client_ctx = ssl.create_default_context()
client_ctx.check_hostname = False
client_ctx.verify_mode = ssl.CERT_NONE
server_in, server_out = ssl.MemoryBIO(), ssl.MemoryBIO()
client_in, client_out = ssl.MemoryBIO(), ssl.MemoryBIO()
server_sslobj = server_ctx.wrap_bio(server_in, server_out, server_side=True)
client_sslobj = client_ctx.wrap_bio(client_in, client_out,
server_side=False, server_hostname='test')
server_internal = server_sslobj._sslobj
try:
client_sslobj.do_handshake()
except ssl.SSLWantReadError:
pass
server_in.write(client_out.read())
del server_sslobj
gc.collect()
server_internal.do_handshake() # crashes: Py_DECREF(NULL)==PID==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#0 Py_DECREF Include/refcount.h:390
#1 _servername_callback Modules/_ssl.c:5197
The error label at line 5196 unconditionally calls Py_DECREF(ssl_socket) but ssl_socket is NULL when the weakref target has been collected. The normal (non-error) path at line 5162 correctly calls Py_DECREF(ssl_socket) only after confirming it's non-NULL (line 5130 onwards).