
Proof-of-concept lab for CVE-2026-19445, a CPython ssl SNI SSLContext use-after-free where a remote TLS client frees the server's dispatch context during handshake.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19445-sni-uaf
CPython ssl - Python Software Foundation
A TLS server that mints an SSLContext per connection, sets sni_callback, and assigns sslobj.context = other_ctx can drop the last Python reference to the original context while OpenSSL still holds a borrowed pointer to it. The next ClientHello (TLS 1.3 HelloRetryRequest is enough) consults that pointer. The handshake continues. The process may crash later. Clients are not affected. A long-lived wrap of the listening socket is not affected.
A remote TLS client can free the server's dispatch SSLContext while the C SNI callback still points at it. Handshake still completes. Crash and memory-corruption window. No RCE in this lab.
| ID | CVE-2026-19445 |
| CWE | CWE-416 |
| CVSS | Critical: 9.2 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L |
| Product | CPython ssl |
| Affected | < 3.12.15, 3.13.0–3.13.15, 3.14.0–3.14.7, 3.15.0a1 before 3.15.0 |
| Auth | unauthenticated TLS client, server must SNI-switch and drop the original context |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
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.
Same product, sibling leftover: wrap_bio skips hostname verification. Opposite TLS side. They do not compose.
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.
cd lab
./run.sh
Image python:3.14.7-slim-bookworm. Compose project cve-2026-19445. Loopback 127.0.0.1:18500 is the negative listen wrap. Primary/secondary oracles are in-memory MemoryBIO.
primary_dispatch_ref_after_sni_gc='none'
primary_hrr='yes'
primary_handshake='complete'
SUCCESS CVE-2026-19445 dispatch_ctx_gc=yes handshake=complete hrr=yes secondary_crash=no CVE-2026-19445-SNI-UAF-WITNESS
Keep a reference to every SSLContext that sets sni_callback for the lifetime of the server. Upgrade to 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0. The C patch stops using the borrowed OpenSSL arg.