Research/cve-2026-19445-sni-uaf
N-dayCVE-2026-19445CriticalPublic

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.

CVE-2026-19445: CPython SNI callback frees the server SSLContext · Abraxas Labs