Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSL issue starting from openssl 3.2 #128141

Open
nurfed1 opened this issue Dec 20, 2024 · 0 comments
Open

SSL issue starting from openssl 3.2 #128141

nurfed1 opened this issue Dec 20, 2024 · 0 comments
Labels
extension-modules C modules in the Modules dir topic-SSL type-bug An unexpected behavior, bug, or error

Comments

@nurfed1
Copy link

nurfed1 commented Dec 20, 2024

Bug report

Bug description:

Hi,

There appears to be an issue either in the asyncio SSL code when upgrading a connection with start_tls or in the function _ssl__SSLSocket_read_impl, starting from OpenSSL 3.2.

The following code works fine with OpenSSL 3.1.4 but fails with OpenSSL 3.2.0 when TLS 1.3 is used. I’ve tested multiple combinations of OpenSSL and Python versions, and the issue seems to lie either in OpenSSL or Python. I’m unsure whether to report this here or on the OpenSSL GitHub, but it does appear to be a bug.

Starting with OpenSSL 3.2, the following exception is raised:
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2576)

This issue is caused by SSL_read_ex returning SSL_ERROR_SYSCALL after successfully reading the HTTP response body. From my observations, errno is 0 at the time of the error.

The OpenSSL 1.1.1 documentation (SSL_get_error) suggests this behavior should not occur in modern versions of OpenSSL. If I understand correctly, this could also be a regression introduced in OpenSSL 3.2.

The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected EOF from the peer. This will be properly 
reported as SSL_ERROR_SSL with reason code SSL_R_UNEXPECTED_EOF_WHILE_READING in the OpenSSL 3.0 
release because it is truly a TLS protocol error to terminate the connection without a SSL_shutdown().

The issue is kept unfixed in OpenSSL 1.1.1 releases because many applications which choose to ignore this 
protocol error depend on the existing way of reporting the error.

I’m not sure if this is a bug in Python or OpenSSL, but it seems likely to become a problem once more systems upgrade to OpenSSL 3.2.

Here's the minimal reproducible sample code:

import asyncio
import ssl


class HttpProxyClient:
    def __init__(self, proxy_host, proxy_port, target_host, target_port):
        self.proxy_host = proxy_host
        self.proxy_port = proxy_port
        self.target_host = target_host
        self.target_port = target_port

    async def connect(self):
        reader, writer = await asyncio.open_connection(self.proxy_host, self.proxy_port)

        connect_request = (
            f"CONNECT {self.target_host}:{self.target_port} HTTP/1.1\r\n"
            f"Host: {self.target_host}:{self.target_port}\r\n"
            f"Proxy-Connection: keep-alive\r\n\r\n"
        )
        writer.write(connect_request.encode())
        await writer.drain()

        response = await reader.read(4096)

        if b'200 Connection established' not in response:
            writer.close()
            await writer.wait_closed()
            raise Exception("Failed to establish connection with the proxy.")

        ctx = ssl.create_default_context(
            purpose=ssl.Purpose.SERVER_AUTH
        )
        # Disable checks for docker test container
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        # Disable tls1.3: this fixes the issue with openssl 3.2+
        # ctx.options |= ssl.OP_NO_TLSv1_3

        # Disable everything except tls1.3: this causes a crash with openssl 3.2+
        ctx.options |= ssl.OP_NO_TLSv1_2 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3

        await writer.start_tls(ctx)

        print("Upgraded to SSL/TLS")

        return reader, writer

    async def run(self):
        http_request = (
            f"GET / HTTP/1.1\r\n"
            f"Host: {self.target_host}\r\n"
            f"Accept: */*\r\n"
            f"Connection: close\r\n\r\n"
        )
        try:
            reader, writer = await self.connect()

            writer.write(http_request.encode())
            await writer.drain()

            while not reader.at_eof():
                response = await reader.read(4096)
                print(response.decode())

            writer.close()
            await writer.wait_closed()
        except Exception as e:
            raise


async def main():
    proxy_host = "localhost"
    proxy_port = 9001
    target_host = "google.com"
    target_port = 443

    client = HttpProxyClient(proxy_host, proxy_port, target_host, target_port)
    await client.run()

asyncio.run(main())

CPython versions tested on:

3.10, 3.11, 3.12, 3.13, 3.14

Operating systems tested on:

Linux

@nurfed1 nurfed1 added the type-bug An unexpected behavior, bug, or error label Dec 20, 2024
@picnixz picnixz added topic-SSL extension-modules C modules in the Modules dir labels Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extension-modules C modules in the Modules dir topic-SSL type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

2 participants