CVE-2026-19445: CPython SNI callback frees the server SSLContext
CPython ssl servers that mint an SSLContext per connection, set sni_callback, and assign sslobj.context to a different context can drop the original SSLContext while OpenSSL still holds a borrowed pointer. A second ClientHello (HelloRetryRequest is enough) consults it. Handshake continues. Crash window. Labbed on 3.14.7.
- Name
- CVE-2026-19445: CPython SNI callback frees the server SSLContext
- Type
- N-day analysis
- CVE
- CVE-2026-19445
- CVE Risk
- critical
- Disclosure Status
- public
- Vendor
- Python Software Foundation
- Affected
- CPython ssl before 3.12.15, 3.13.0-3.13.15, 3.14.0-3.14.7, 3.15.0a1 before 3.15.0. Server-side SNI switch that drops the original context. Clients and long-lived listen wraps are outside the bug. Patched in 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0.
- Published
- 01 Oct 2026
- Updated
- 01 Oct 2026
- Tags
- n-day, cpython, ssl, sni, uaf, cwe-416, unauthenticated
The callback outlives the context
The proof of concept is on GitHub: abraxas/cve-2026-19445-sni-uaf (loopback client; @abraxas_null). The lab stack is lab/: Dockerfile, docker-compose.yml, run.sh, poc.py. Authorized lab only. It talks to loopback.
This is CVE-2026-19445 in CPython ssl. CWE-416. 9.2 Critical (CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L). Unauthenticated TLS client. Victim is a Python TLS server that SNI-switches and drops the original context. No RCE in this lab. Patched in 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0.
Same product, sibling: wrap_bio skips hostname verification. Opposite TLS side. They do not compose.
What an attacker can do
Talk TLS to a Python server that creates a fresh SSLContext for the connection, installs sni_callback, and in that callback assigns a different context then lets the original one die. Send SNI. Force a second ClientHello (restrict the server curve to P-384 so a default X25519 client gets HelloRetryRequest). After GC, the Python object is gone. The C tlsext_servername callback still has the borrowed args pointer from SSL_CTX_set_tlsext_servername_arg. Handshake completes on this pin. Under ASan that is a use-after-free. In production it is a crash / corruption window the next time that memory is reused.
Servers that wrap the listening socket once with a process-lifetime context keep the reference. Those are outside the bug. wrap_socket clients are outside the bug.
How I found it
PSF posted CVE-2026-19445. The reservation issue is python/cpython#156293. The real map is PR 158504 / commit 0f63c2aa. _servername_callback used the borrowed OpenSSL arg. After SNI switches sslobj.context and the original SSLContext is GC'd, the second ClientHello still calls into it. The patch looks up the context from SSL_get_app_data → ssl->ctx and clears the callback in context_dealloc.
I ran CPython's own tests as a lab against python:3.14.7-slim-bookworm (3.14.7 is in the affected range; 3.14.8 is the fix). MemoryBIO, no TCP for the UAF path.
dispatch_ctx.set_ecdh_curve("secp384r1"), client default X25519. sni_callback assigns sslobj.context = leaf_ctx. After the first SNI, three gc.collect() calls, weakref.ref(dispatch_ctx) is None. Handshake still completes (TLS_AES_256_GCM_SHA384). HRR is in _msg_callback (ServerHello random at offset 6). sni_calls=1: the second ClientHello never reached a live Python callback.
Secondary path (callback switches, del ctx, raises LookupError) did not abort 3.14.7. SSLError: CALLBACK_FAILED, unraisable LookupError. Negative: process-lifetime context wrapping a listen socket on 127.0.0.1:18500. Handshake works. Weakref stays alive.
Wrong turns already recorded: pinning P-384 on the leaf as well as dispatch, plus locking the client to X25519-only, produced NO_SUITABLE_KEY_SHARE instead of HRR. CPython only pins the dispatch context. HRR magic is at ServerHello offset 6, not 2. First probe missed HRR even when it fired. dispatch_ctx is still alive right after wrap_bio because server.context holds it. It becomes collectable only after the SNI switch. Theatre: a reverse shell. The oracle is dispatch_ref is None plus a completed handshake.
Keep the reference
Keep a reference to every SSLContext that sets sni_callback for the lifetime of the server. Upgrade. The C patch stops using the borrowed OpenSSL arg.
I am @abraxas_null. Site abraxaslabs.tech. GitHub abraxas. Mail abraxas.null@proton.me.