Title: Segfault from `Py_DECREF(NULL)` in `_servername_callback` error label in `_ssl.c` · Issue #146080 · python/cpython · GitHub
Open Graph Title: Segfault from `Py_DECREF(NULL)` in `_servername_callback` error label in `_ssl.c` · Issue #146080 · python/cpython
X Title: Segfault from `Py_DECREF(NULL)` in `_servername_callback` error label in `_ssl.c` · Issue #146080 · python/cpython
Description: Crash report What happened? It's possible to cause a segfault in _servername_callback error label by making ssl_socket to be NULL. Automated diagnosis: Bug: Py_DECREF(NULL) crash in _ssl _servername_callback. When the SSL socket/owner we...
Open Graph Description: Crash report What happened? It's possible to cause a segfault in _servername_callback error label by making ssl_socket to be NULL. Automated diagnosis: Bug: Py_DECREF(NULL) crash in _ssl _servernam...
X Description: Crash report What happened? It's possible to cause a segfault in _servername_callback error label by making ssl_socket to be NULL. Automated diagnosis: Bug: Py_DECREF(NULL) crash in _ssl _serve...
Opengraph URL: https://github.com/python/cpython/issues/146080
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Segfault from `Py_DECREF(NULL)` in `_servername_callback` error label in `_ssl.c`","articleBody":"# Crash report\n\n### What happened?\n\nIt's possible to cause a segfault in `_servername_callback` error label by making `ssl_socket` to be `NULL`.\n\n### Automated diagnosis:\n\nBug: `Py_DECREF(NULL)` crash in `_ssl _servername_callback`. When the SSL socket/owner weakref has been garbage collected during an SNI callback, `ssl_socket` is `NULL`. The error label at line 5197 calls `Py_DECREF(ssl_socket)` on `NULL` -\u003e segfault.\nFix: Change `Py_DECREF` to `Py_XDECREF`\nFile: `Modules/_ssl.c`, line 5197\n\n[Full report](https://gist.github.com/devdanzin/50f9880636df43efd3886f6bda2077b3)\n\n\n**WARNING**: THIS MRE WILL **RUN openssl** IN A SUBPROCESS AND **LEAK** THE CERT AND KEY FILES\nMRE:\n```python\n\"\"\"\nWARNING: THIS MRE WILL RUN openssl IN A SUBPROCESS AND LEAK THE CERT AND KEY FILES\n\nStrategy: Use SSLObject (not SSLSocket) so the Python-level wrapper can\nbe GC'd independently of the C-level SSL object. In the SNI callback,\ndelete the only reference to the SSLObject and force GC. The weakref\nin ssl-\u003eowner dies, PyWeakref_GetRef returns 0, ssl_socket = NULL,\ngoto error -\u003e Py_DECREF(NULL) -\u003e crash.\n\"\"\"\nimport ssl\nimport gc\nimport tempfile\nimport subprocess\n\ndef generate_self_signed_cert():\n \"\"\"\n Generate a self-signed cert+key for testing.\n WARNING: THIS RUNS openssl IN A SUBPROCESS AND LEAKS THE CERT AND KEY FILES\n \"\"\"\n certpath = tempfile.mktemp(suffix='.pem')\n keypath = tempfile.mktemp(suffix='.key')\n subprocess.run([\n 'openssl', 'req', '-x509', '-newkey', 'rsa:2048',\n '-keyout', keypath, '-out', certpath,\n '-days', '1', '-nodes', '-subj', '/CN=test'\n ], capture_output=True, check=True)\n return certpath, keypath\n\ndef sni_callback(sslobj, servername, sslctx):\n pass\n\ncertpath, keypath = generate_self_signed_cert()\nserver_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\nserver_ctx.load_cert_chain(certpath, keypath)\n\nserver_ctx.set_servername_callback(sni_callback)\n\n# Approach: Use MemoryBIO-based SSLObject (not socket-based SSLSocket)\n# to have more control over the object lifecycle.\nserver_incoming = ssl.MemoryBIO()\nserver_outgoing = ssl.MemoryBIO()\nserver_sslobj = server_ctx.wrap_bio(\n server_incoming, server_outgoing, server_side=True\n)\n\nclient_ctx = ssl.create_default_context()\nclient_ctx.check_hostname = False\nclient_ctx.verify_mode = ssl.CERT_NONE\n\nclient_incoming = ssl.MemoryBIO()\nclient_outgoing = ssl.MemoryBIO()\nclient_sslobj = client_ctx.wrap_bio(\n client_incoming, client_outgoing,\n server_side=False, server_hostname='test'\n)\n\n# Get the internal _SSLSocket objects\nserver_ssl_internal = server_sslobj._sslobj\nclient_ssl_internal = client_sslobj._sslobj\n\n# The _SSLSocket's \"owner\" weakref points to the SSLObject.\n# If we delete the SSLObject, the weakref dies.\n# Try to do the handshake\nfor i in range(20):\n # Client step\n try:\n client_sslobj.do_handshake()\n except ssl.SSLWantReadError:\n pass\n\n # Transfer client -\u003e server\n data = client_outgoing.read()\n if data:\n server_incoming.write(data)\n\n # NOW: before server processes the ClientHello (which triggers SNI),\n # try to kill the server SSLObject so the weakref dies.\n if i == 0 and data:\n # The server hasn't processed ClientHello yet.\n # Delete the SSLObject wrapper — the internal _SSLSocket\n # still exists (we hold server_ssl_internal).\n # The weakref ssl-\u003eowner should now be dead.\n del server_sslobj\n gc.collect()\n print(f\"server_ssl_internal still alive: {server_ssl_internal is not None}\")\n\n # Now do_handshake on the internal object directly\n # This will trigger the SNI callback with a dead owner weakref\n server_ssl_internal.do_handshake()\n```\n\nBacktrace:\n```\nProgram received signal SIGSEGV, Segmentation fault.\n0x00007bfff59bf197 in Py_DECREF (lineno=5197, op=0x0, filename=\u003coptimized out\u003e) at ./Include/refcount.h:390\n390 if (op-\u003eob_refcnt_full \u003c= 0 || op-\u003eob_refcnt \u003e (((PY_UINT32_T)-1) - (1\u003c\u003c20))) {\n\n#0 0x00007bfff59bf197 in Py_DECREF (lineno=5197, op=0x0, filename=\u003coptimized out\u003e) at ./Include/refcount.h:390\n#1 _servername_callback (s=0x7e1ff6ff8100, al=\u003coptimized out\u003e, args=0x7d4ff7011930) at ./Modules/_ssl.c:5197\n#2 0x00007bfff459d89a in ?? () from /lib/x86_64-linux-gnu/libssl.so.3\n#3 0x00007bfff459eddc in ?? () from /lib/x86_64-linux-gnu/libssl.so.3\n#4 0x00007bfff45c15f2 in ?? () from /lib/x86_64-linux-gnu/libssl.so.3\n#5 0x00007bfff45abdbb in ?? () from /lib/x86_64-linux-gnu/libssl.so.3\n#6 0x00007bfff59bff61 in _ssl__SSLSocket_do_handshake_impl (self=0x7caff70e4070) at ./Modules/_ssl.c:1052\n#7 _ssl__SSLSocket_do_handshake (self=0x7caff70e4070, _unused_ignored=\u003coptimized out\u003e) at ./Modules/clinic/_ssl.c.h:30\n#8 0x0000555555ae6992 in method_vectorcall_NOARGS (func=func@entry=0x7c7ff70f14c0, args=args@entry=0x7bfff5d8c728, nargsf=nargsf@entry=9223372036854775809, kwnames=kwnames@entry=0x0)\n at Objects/descrobject.c:448\n#9 0x0000555555ab9e00 in _PyObject_VectorcallTstate (tstate=0x5555568f7b18 \u003c_PyRuntime+360664\u003e, callable=0x7c7ff70f14c0, args=0x7bfff5d8c728, nargsf=9223372036854775809, kwnames=0x0)\n at ./Include/internal/pycore_call.h:136\n#10 0x0000555555e588dd in _Py_VectorCallInstrumentation_StackRefSteal (callable=..., arguments=\u003coptimized out\u003e, total_args=1, kwnames=..., call_instrumentation=\u003coptimized out\u003e,\n frame=\u003coptimized out\u003e, this_instr=\u003coptimized out\u003e, tstate=\u003coptimized out\u003e) at Python/ceval.c:770\n#11 0x0000555555e94263 in _PyEval_EvalFrameDefault (tstate=\u003coptimized out\u003e, frame=\u003coptimized out\u003e, throwflag=\u003coptimized out\u003e) at Python/generated_cases.c.h:1838\n#12 0x0000555555e57778 in _PyEval_EvalFrame (tstate=0x5555568f7b18 \u003c_PyRuntime+360664\u003e, frame=0x7e8ff6fe5220, throwflag=0) at ./Include/internal/pycore_ceval.h:118\n#13 _PyEval_Vector (tstate=\u003coptimized out\u003e, func=\u003coptimized out\u003e, locals=\u003coptimized out\u003e, args=\u003coptimized out\u003e, argcount=\u003coptimized out\u003e, kwnames=0x0) at Python/ceval.c:2134\n#14 0x0000555555e57195 in PyEval_EvalCode (co=\u003coptimized out\u003e, globals=\u003coptimized out\u003e, locals=0x7c7ff70884c0) at Python/ceval.c:681\n#15 0x0000555556061fb0 in run_eval_code_obj (tstate=tstate@entry=0x5555568f7b18 \u003c_PyRuntime+360664\u003e, co=co@entry=0x7d8ff7016690, globals=globals@entry=0x7c7ff70884c0,\n locals=locals@entry=0x7c7ff70884c0) at Python/pythonrun.c:1368\n#16 0x000055555606117c in run_mod (mod=\u003coptimized out\u003e, filename=\u003coptimized out\u003e, globals=\u003coptimized out\u003e, locals=\u003coptimized out\u003e, flags=\u003coptimized out\u003e, arena=\u003coptimized out\u003e,\n interactive_src=\u003coptimized out\u003e, generate_new_source=\u003coptimized out\u003e) at Python/pythonrun.c:1471\n```\n\n\nFound using [cpython-review-toolkit](https://github.com/devdanzin/cpython-review-toolkit) with Claude Opus 4.6, using the `/cpython-review-toolkit:explore Modules/_ssl.c all deep` command.\n\n\n\n### CPython versions tested on:\n\nCPython main branch\n\n### Operating systems tested on:\n\nLinux\n\n### Output from running 'python -VV' on the command line:\n\nPython 3.15.0a7+ (heads/main:99e2c5eccd2, Mar 17 2026, 08:26:50) [Clang 21.1.2 (2ubuntu6)]\n\n\u003c!-- gh-linked-prs --\u003e\n### Linked PRs\n* gh-146573\n* gh-146597\n* gh-146598\n\u003c!-- /gh-linked-prs --\u003e\n","author":{"url":"https://github.com/devdanzin","@type":"Person","name":"devdanzin"},"datePublished":"2026-03-17T20:08:45.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":10},"url":"https://github.com/146080/cpython/issues/146080"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:30c71f5d-f80b-5ab5-52dd-301247a9136a |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | BB9C:620C2:13F665B:1A4F760:6A51B8AD |
| html-safe-nonce | 600b7075061ad21b10278879a0bed283dbe9a2274ef68a8285903768cae1ee92 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCQjlDOjYyMEMyOjEzRjY2NUI6MUE0Rjc2MDo2QTUxQjhBRCIsInZpc2l0b3JfaWQiOiIyNjM5MzA1NDc0NDQ5NzE3NDIxIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 40ef89d76fcc5837f9d9929194ee3eee1463b5e84c2f26edd26d38eedcc33c86 |
| hovercard-subject-tag | issue:4090920538 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/cpython/146080/issue_layout |
| twitter:image | https://opengraph.githubassets.com/e0cc36bedb1aa198d973765344bcaf425351feac40e21cf284ff6a2abc2fd0fc/python/cpython/issues/146080 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/e0cc36bedb1aa198d973765344bcaf425351feac40e21cf284ff6a2abc2fd0fc/python/cpython/issues/146080 |
| og:image:alt | Crash report What happened? It's possible to cause a segfault in _servername_callback error label by making ssl_socket to be NULL. Automated diagnosis: Bug: Py_DECREF(NULL) crash in _ssl _servernam... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | devdanzin |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7aed05249554b889eb33d002851a973eebcc7e91 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width