Skip to content

On OpenSSL 4, SSL_read_ex() fails with a different error on EOF #30894

@vstinner

Description

@vstinner

Hi,

I'm updating Python to OpenSSL 4 and I get a different error between OpenSSL 3 (3.5.4) and OpenSSL 4 (4.0.0) on SSL_read_ex() on the TLS client after the TLS server closes the socket.

When the server closes the socket (with sslconn.shutdown(socket.SHUT_WR); sslconn.close()), the first SSL_read_ex() on the client fails with SSL_R_UNEXPECTED_EOF_WHILE_READING (same behavior on OpenSSL 3 and OpenSSL 4):

  • SSL_get_error()=SSL_ERROR_SSL
  • ERR_GET_LIB()=ERR_LIB_SSL
  • ERR_GET_REASON()=SSL_R_UNEXPECTED_EOF_WHILE_READING

This error is helpful. My problem is on the second SSL_read_ex() on the client, I get a different error between OpenSSL 3 and OpenSSL 4:

  • OpenSSL 3: SSL_get_error()=SSL_ERROR_SYSCALL, ERR_peek_last_error()=0. Python raises a SSLEOFError in this case (behaves correctly).
  • OpenSSL 4: SSL_get_error()=SSL_ERROR_SSL, ERR_peek_last_error()=0. Python raises a generic SSLError("A failure in the SSL library occurred") in this case (which is unexpected).

OpenSSL 4 sets a generic SSL_ERROR_SSL error with ERR_peek_last_error()=0. It's not helpful to detect an "EOF".

My question is if the different error on OpenSSL 4 was made on purpose, or if it's an issue in OpenSSL 4?


If you want to reproduce my issue, get the Python script:

Details
import os.path
import socket
import ssl
import threading

CERT = os.path.join('Lib', 'test', 'certdata', 'keycert.pem')
HOST = '127.0.0.1'
HOSTNAME = 'localhost'

class Server(threading.Thread):
    def __init__(self):
        super().__init__()
        self.listening = threading.Event()
        self.address = None

    def run(self):
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain(CERT)
        server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        #server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        #server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        server_sock.bind((HOST, 0))
        server_sock.listen(5)

        self.address = server_sock.getsockname()
        self.listening.set()

        sock, addr = server_sock.accept()
        sslconn = context.wrap_socket(sock, server_side=True)

        request = b''
        while True:
            chunk = sslconn.recv(65537)
            request += chunk
            if b'\r\n\r\n' in request:
                break
        print(f"server got request: {request!r}")

        print("server sendall")
        sslconn.sendall(
            b'HTTP/1.0 200 OK\r\n'
            b'Server: TestHTTP/ Python/3.15.0a8+\r\n'
            b'Date: Thu, 16 Apr 2026 12:42:37 GMT\r\n'
            b'Content-type: text/plain\r\n\r\n')
        sslconn.sendall(b'we care a bit')

        print("server shutdown write")
        sslconn.shutdown(socket.SHUT_WR)
        print("server close socket")
        sslconn.close()

        server_sock.close()

def main():
    server = Server()
    server.start()
    server.listening.wait()
    port = server.address[1]

    context = ssl.create_default_context(cafile=CERT)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, port))
    sslsock = context.wrap_socket(sock, server_hostname=HOSTNAME)

    sslsock.sendall(b'GET /bizarre HTTP/1.0\r\n\r\n')
    sslobj = sslsock._sslobj

    def read(prefix, sslobj):
        try:
            data = sslobj.read(1024)
            result = repr(data)
        except ssl.SSLError as exc:
            result = f'<{exc!r}>'
        print(prefix, result)

    for i in range(1, 5):
        read(f"client read #{i}:", sslobj)
    sslsock.close()

    server.join()

if __name__ == "__main__":
    main()

And run these commands on Linux:

git clone https://github.com/python/cpython --depth=1
cd cpython/
# Build OpenSSL 4 in ~/multissl/openssl/4.0.0/
python3 Tools/ssl/multissltests.py --steps=library --base-directory $HOME/multissl --openssl 4.0.0 --system Linux
./configure --with-pydebug --with-openssl=$HOME/multissl/openssl/4.0.0/
LD_LIBRARY_PATH=~/multissl/openssl/4.0.0/lib64/ make
LD_LIBRARY_PATH=~/multissl/openssl/4.0.0/lib64/ ./python script.py 

Victor

Metadata

Metadata

Assignees

No one assigned

    Labels

    issue: bug reportThe issue was opened to report a bugtriaged: bugThe issue/pr is/fixes a bug

    Type

    No type
    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