Skip to content

SSLSocket.recv/SSLSocket.send concurrency crash #151508

@divVerent

Description

@divVerent

Crash report

What happened?

import os
import socket
import ssl
import threading
import time

os.system("openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -nodes -subj /CN=localhost")

def ssl_loop(func, stop_me):
    while not stop_me.is_set():
        try:
            func()
        except ssl.SSLError as e:
            if e.errno in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE):
                continue
            break
        except Exception:
            break

def run_ssl_server(server_sock, stop_event):
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")
    conn, _ = server_sock.accept()
    ssl_conn = context.wrap_socket(conn, server_side=True)
    def server_step():
        c.sendall(b"S" * 4096)
        c.recv(4096)
    ssl_loop(server_step, stop_event)

def try_to_crash():
    server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_sock.bind(('127.0.0.1', 0))
    server_sock.listen(5)
    stop_event = threading.Event()
    threading.Thread(target=run_ssl_server, args=(server_sock, stop_event), daemon=True).start()

    client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_sock.connect(server_sock.getsockname())
    context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    ssl_client = context.wrap_socket(client_sock)
    ssl_client.settimeout(0.001)  # Can just retry if a short timeout is hit.

    stop_client = threading.Event()

    # Spawn 10 reader and writer threads against the same SSL client,
    # and let them run for one second.
    threads = []
    for _ in range(10):
        t_read = threading.Thread(target=lambda: ssl_loop(lambda: ssl_client.recv(4096), stop_client))
        t_write = threading.Thread(target=lambda: ssl_loop(lambda: ssl_client.sendall(b"Y" * 4096), stop_client))
        threads.extend([t_read, t_write])
        t_read.start()
        t_write.start()
    time.sleep(1)
    stop_event.set()
    for t in threads:
        t.join(timeout=0.1)
    ssl_client.close()
    server_sock.close()

for i in range(100):
    print(i)
    try_to_crash()
print('did not crash')

Usually crashes within about 10 seconds.

Backtrace:

* thread #9, name = 'python3', stop reason = signal SIGSEGV: sent by kernel (SI_KERNEL)
   * frame #0: 0x00007ffff7ca497d libc.so.6`_int_malloc(av=0x00007fffc0000030, bytes=21) at malloc.c:4217:14
     frame #1: 0x00007ffff7ca57f2 libc.so.6`__libc_malloc2(bytes=21) at malloc.c:3458:12
     frame #2: 0x00007ffff6a613ee libcrypto.so.3`CRYPTO_malloc at mem.c:214:11
     frame #3: 0x00007ffff6a06eb5 libcrypto.so.3`err_set_debug(es=0x00007fffc0005f60, i=1, file="../ssl/record/methods/tls_common.c", line=873, fn="tls_get_more_records") at err_local.h:69:33 [inlined]
     frame #4: 0x00007ffff6a06e70 libcrypto.so.3`ERR_set_debug(file="../ssl/record/methods/tls_common.c", line=873, func="tls_get_more_records") at err_blocks.c:37:5
     frame #5: 0x00007ffff70dfcf9 libssl.so.3`tls_get_more_records at tls_common.c:921:9
     frame #6: 0x00007ffff70de29a libssl.so.3`tls_read_record at tls_common.c:1141:15
     frame #7: 0x00007ffff70d6535 libssl.so.3`ssl3_read_bytes at rec_layer_s3.c:698:19
     frame #8: 0x00007ffff706ac40 libssl.so.3`ssl3_read_internal.part.0 at s3_lib.c:5131:11
     frame #9: 0x00007ffff7079f1d libssl.so.3`SSL_read_ex at ssl_lib.c:2388:15
     frame #10: 0x00007ffff71b0c37 _ssl.cpython-313-x86_64-linux-gnu.so`_ssl__SSLSocket_read_impl(self=0x00007ffff674c6a0, len=4096, group_right_1=<unavailable>, buffer=0x00007fffda7fbaa0) at _ssl.c:2622:18
     frame #11: 0x00007ffff71b0b51 _ssl.cpython-313-x86_64-linux-gnu.so`_ssl__SSLSocket_read(self=0x00007ffff674c6a0, args=<unavailable>) at _ssl.c.h:544:20
     frame #12: 0x0000000000560d03 python3`method_vectorcall_VARARGS(func=<unavailable>, args=<unavailable>, nargsf=<unavailable>, kwnames=<unavailable>) at descrobject.c:324:24
     frame #13: 0x0000000000559723 python3`_PyObject_VectorcallTstate(tstate=0x0000000000e248c0, callable=0x00007ffff727cc70, args=<unavailable>, nargsf=<unavailable>, kwnames=<unavailable>) at pycore_call.h:168:11 [inlined]
     frame #14: 0x0000000000559708 python3`PyObject_Vectorcall(callable=0x00007ffff727cc70, args=<unavailable>, nargsf=<unavailable>, kwnames=<unavailable>) at call.c:327:12
     frame #15: 0x000000000056ea15 python3`_PyEval_EvalFrameDefault(tstate=<unavailable>, frame=<unavailable>, throwflag=<unavailable>) at generated_cases.c.h:1850:23
     frame #16: 0x00000000005dd5ea python3`_PyObject_VectorcallTstate(tstate=0x0000000000e248c0, callable=0x00007ffff6755da0, args=0x00007fffda7fbdd8, nargsf=1, kwnames=0x0000000000000000) at pycore_call.h:168:11 [inlined]
     frame #17: 0x00000000005dd5b7 python3`method_vectorcall(method=<unavailable>, args=<unavailable>, nargsf=<unavailable>, kwnames=<unavailable>) at classobject.c:71:20
     frame #18: 0x00000000006f7c10 python3`thread_run(boot_raw=0x0000000000d47a60) at _threadmodule.c:343:21
     frame #19: 0x000000000069f908 python3`pythread_wrapper(arg=<unavailable>) at thread_pthread.h:242:5
     frame #20: 0x00007ffff7c95dc9 libc.so.6`start_thread(arg=<unavailable>) at pthread_create.c:448:8
     frame #21: 0x00007ffff7d14d88 libc.so.6`__clone3 at clone3.S:78

Cause is assuming that SSL_read and SSL_write functions in OpenSSL can be called concurrently (see #137583 (comment)).

It probably would make sense to either:

CPython versions tested on:

3.13, 3.14

Operating systems tested on:

Linux, macOS

Output from running 'python -VV' on the command line:

Python 3.13.5 (main, May 5 2026, 21:05:52) [GCC 14.2.0]

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-crashA hard crash of the interpreter, possibly with a core dump
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions