Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34.3k
Description
Crash report
What happened?
It's possible to cause a segfault in_servername_callback error label by makingssl_socket to beNULL.
Automated diagnosis:
Bug:Py_DECREF(NULL) crash in_ssl _servername_callback. When the SSL socket/owner weakref has been garbage collected during an SNI callback,ssl_socket isNULL. The error label at line 5197 callsPy_DECREF(ssl_socket) onNULL -> segfault.
Fix: ChangePy_DECREF toPy_XDECREF
File:Modules/_ssl.c, line 5197
WARNING: THIS MRE WILLRUN openssl IN A SUBPROCESS ANDLEAK THE CERT AND KEY FILES
MRE:
"""WARNING: THIS MRE WILL RUN openssl IN A SUBPROCESS AND LEAK THE CERT AND KEY FILESStrategy: Use SSLObject (not SSLSocket) so the Python-level wrapper canbe GC'd independently of the C-level SSL object. In the SNI callback,delete the only reference to the SSLObject and force GC. The weakrefin ssl->owner dies, PyWeakref_GetRef returns 0, ssl_socket = NULL,goto error -> Py_DECREF(NULL) -> crash."""importsslimportgcimporttempfileimportsubprocessdefgenerate_self_signed_cert():""" Generate a self-signed cert+key for testing. WARNING: THIS RUNS openssl IN A SUBPROCESS AND LEAKS THE CERT AND KEY FILES """certpath=tempfile.mktemp(suffix='.pem')keypath=tempfile.mktemp(suffix='.key')subprocess.run(['openssl','req','-x509','-newkey','rsa:2048','-keyout',keypath,'-out',certpath,'-days','1','-nodes','-subj','/CN=test' ],capture_output=True,check=True)returncertpath,keypathdefsni_callback(sslobj,servername,sslctx):passcertpath,keypath=generate_self_signed_cert()server_ctx=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)server_ctx.load_cert_chain(certpath,keypath)server_ctx.set_servername_callback(sni_callback)# Approach: Use MemoryBIO-based SSLObject (not socket-based SSLSocket)# to have more control over the object lifecycle.server_incoming=ssl.MemoryBIO()server_outgoing=ssl.MemoryBIO()server_sslobj=server_ctx.wrap_bio(server_incoming,server_outgoing,server_side=True)client_ctx=ssl.create_default_context()client_ctx.check_hostname=Falseclient_ctx.verify_mode=ssl.CERT_NONEclient_incoming=ssl.MemoryBIO()client_outgoing=ssl.MemoryBIO()client_sslobj=client_ctx.wrap_bio(client_incoming,client_outgoing,server_side=False,server_hostname='test')# Get the internal _SSLSocket objectsserver_ssl_internal=server_sslobj._sslobjclient_ssl_internal=client_sslobj._sslobj# The _SSLSocket's "owner" weakref points to the SSLObject.# If we delete the SSLObject, the weakref dies.# Try to do the handshakeforiinrange(20):# Client steptry:client_sslobj.do_handshake()exceptssl.SSLWantReadError:pass# Transfer client -> serverdata=client_outgoing.read()ifdata:server_incoming.write(data)# NOW: before server processes the ClientHello (which triggers SNI),# try to kill the server SSLObject so the weakref dies.ifi==0anddata:# The server hasn't processed ClientHello yet.# Delete the SSLObject wrapper — the internal _SSLSocket# still exists (we hold server_ssl_internal).# The weakref ssl->owner should now be dead.delserver_sslobjgc.collect()print(f"server_ssl_internal still alive:{server_ssl_internalisnotNone}")# Now do_handshake on the internal object directly# This will trigger the SNI callback with a dead owner weakrefserver_ssl_internal.do_handshake()
Backtrace:
Program received signal SIGSEGV, Segmentation fault.0x00007bfff59bf197 in Py_DECREF (lineno=5197, op=0x0, filename=<optimized out>) at ./Include/refcount.h:390390 if (op->ob_refcnt_full <= 0 || op->ob_refcnt > (((PY_UINT32_T)-1) - (1<<20))) {#0 0x00007bfff59bf197 in Py_DECREF (lineno=5197, op=0x0, filename=<optimized out>) at ./Include/refcount.h:390#1 _servername_callback (s=0x7e1ff6ff8100, al=<optimized out>, args=0x7d4ff7011930) at ./Modules/_ssl.c:5197#2 0x00007bfff459d89a in ?? () from /lib/x86_64-linux-gnu/libssl.so.3#3 0x00007bfff459eddc in ?? () from /lib/x86_64-linux-gnu/libssl.so.3#4 0x00007bfff45c15f2 in ?? () from /lib/x86_64-linux-gnu/libssl.so.3#5 0x00007bfff45abdbb in ?? () from /lib/x86_64-linux-gnu/libssl.so.3#6 0x00007bfff59bff61 in _ssl__SSLSocket_do_handshake_impl (self=0x7caff70e4070) at ./Modules/_ssl.c:1052#7 _ssl__SSLSocket_do_handshake (self=0x7caff70e4070, _unused_ignored=<optimized out>) at ./Modules/clinic/_ssl.c.h:30#8 0x0000555555ae6992 in method_vectorcall_NOARGS (func=func@entry=0x7c7ff70f14c0, args=args@entry=0x7bfff5d8c728, nargsf=nargsf@entry=9223372036854775809, kwnames=kwnames@entry=0x0) at Objects/descrobject.c:448#9 0x0000555555ab9e00 in _PyObject_VectorcallTstate (tstate=0x5555568f7b18 <_PyRuntime+360664>, callable=0x7c7ff70f14c0, args=0x7bfff5d8c728, nargsf=9223372036854775809, kwnames=0x0) at ./Include/internal/pycore_call.h:136#10 0x0000555555e588dd in _Py_VectorCallInstrumentation_StackRefSteal (callable=..., arguments=<optimized out>, total_args=1, kwnames=..., call_instrumentation=<optimized out>, frame=<optimized out>, this_instr=<optimized out>, tstate=<optimized out>) at Python/ceval.c:770#11 0x0000555555e94263 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:1838#12 0x0000555555e57778 in _PyEval_EvalFrame (tstate=0x5555568f7b18 <_PyRuntime+360664>, frame=0x7e8ff6fe5220, throwflag=0) at ./Include/internal/pycore_ceval.h:118#13 _PyEval_Vector (tstate=<optimized out>, func=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0) at Python/ceval.c:2134#14 0x0000555555e57195 in PyEval_EvalCode (co=<optimized out>, globals=<optimized out>, locals=0x7c7ff70884c0) at Python/ceval.c:681#15 0x0000555556061fb0 in run_eval_code_obj (tstate=tstate@entry=0x5555568f7b18 <_PyRuntime+360664>, co=co@entry=0x7d8ff7016690, globals=globals@entry=0x7c7ff70884c0, locals=locals@entry=0x7c7ff70884c0) at Python/pythonrun.c:1368#16 0x000055555606117c in run_mod (mod=<optimized out>, filename=<optimized out>, globals=<optimized out>, locals=<optimized out>, flags=<optimized out>, arena=<optimized out>, interactive_src=<optimized out>, generate_new_source=<optimized out>) at Python/pythonrun.c:1471Found usingcpython-review-toolkit with Claude Opus 4.6, using the/cpython-review-toolkit:explore Modules/_ssl.c all deep command.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.15.0a7+ (heads/main:99e2c5eccd2, Mar 17 2026, 08:26:50) [Clang 21.1.2 (2ubuntu6)]