Unsuccessful TLS handshake when using `requests` in multiple threads on Ubuntu Jammy

This is a bit deeper into the SSL/TLS stack than I've been before, so apologies if I'm off on anything here.

When issuing requests across multiple threads, `requests` does not successfully negotiate TLS versions. When issuing requests in a single thread, connections are opened and successfully negotiate TLSv1.3.

When utilizing retries, the connections do eventually negotiate TLSv1.3 and succeed, but all connections are initially opened with TLSv1.

## Expected Result
Handshake completes successfully, TLS v1.3 is selected for further communication and an unexpected EOFError is not raised.

```
requests.exceptions.SSLError: HTTPSConnectionPool(host='google.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1007)')))
```

## Actual Result
Handshake fails to complete.

`Alert (Level: Fatal, Description: Decode Error)`

## Reproduction steps

[I've attached `tcpdump` captures and TLS log (`(Pre)-Master-Secret log`) files for each of the provided test cases.](https://github.com/psf/requests/files/13185052/tcpdump.zip)

Additionally, here are screenshots of the Wireshark sessions, notice that the multi-threaded tcpdump cannot complete the TLS handshake successfully.

### `requests` single-threaded tcpdump
<img width="1195" alt="Screenshot 2023-10-26 at 10 02 11 PM" src="https://github.com/psf/requests/assets/437360/9e995fd0-f69e-4866-860e-337821c0044b">

### `requests` multi-threaded tcpdump
<img width="1271" alt="Screenshot 2023-10-26 at 10 01 52 PM" src="https://github.com/psf/requests/assets/437360/55c6fdcf-559f-4d88-8d9d-47f917d70aff">

### `urllib3` tcpdump
<img width="1266" alt="Screenshot 2023-10-26 at 10 02 24 PM" src="https://github.com/psf/requests/assets/437360/e492d76f-935d-46a1-afad-49caae421d45">

---

```dockerfile
# Dockerfile
FROM ubuntu:jammy
RUN apt update -y && \
    apt install -y \
    python3.10-dev python3.10-distutils python3-pip
RUN pip install requests
```

```python
# main.py
import logging
import os
import sys
import warnings
from concurrent import futures

import requests
import urllib3.poolmanager

WORKER_COUNT = 128


def test_requests():
    session = requests.Session()
    for i in range(48):
        session.get('https://google.com')


def test_requests_threaded():
    executor = futures.ThreadPoolExecutor(max_workers=WORKER_COUNT)

    def get():
        session = requests.Session()
        return session.get('https://google.com')

    return [
        future.result()
        for future in futures.as_completed([executor.submit(get) for i in range(48)])
    ]


def test_urllib3_threaded():
    executor = futures.ThreadPoolExecutor(max_workers=WORKER_COUNT)
    connection_manager = urllib3.poolmanager.PoolManager(num_pools=WORKER_COUNT, maxsize=WORKER_COUNT)

    def get(connection_manager):
        return connection_manager.request('GET', 'https://google.com')

    return [
        future.result()
        for future in futures.as_completed([executor.submit(get, connection_manager) for i in range(48)])
    ]


tests = {
    "test_requests": test_requests,
    "test_requests_threaded": test_requests_threaded,
    "test_urllib3_threaded": test_urllib3_threaded,
}

if __name__ == "__main__":
    os.environ["SSLKEYLOGFILE"] = "/app/tls.log"
    # Enable all logging and warnings
    warnings.simplefilter("always")

    logging.basicConfig(level=logging.DEBUG)
    loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
    for logger in loggers:
        logger.setLevel(logging.DEBUG)
    try:

        tests[sys.argv[1]]()
    finally:
        with open(os.environ["SSLKEYLOGFILE"], "r", encoding="utf-8") as file:
            print(file.read())
```

```bash
docker build . -t requests-tls-repro
docker run --entrypoint=bash -v .:/app/ -it requests-tls-repro
$ cd /app/
$ python3 main.py test_requests
$ python3 main.py test_requests_threaded
$ python3 main.py test_urllib3_threaded
```

## System Information

```json
{
  "chardet": {
    "version": null
  },
  "charset_normalizer": {
    "version": "3.3.1"
  },
  "cryptography": {
    "version": ""
  },
  "idna": {
    "version": "3.4"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.10.12"
  },
  "platform": {
    "release": "5.15.49-linuxkit-pr",
    "system": "Linux"
  },
  "pyOpenSSL": {
    "openssl_version": "",
    "version": null
  },
  "requests": {
    "version": "2.31.0"
  },
  "system_ssl": {
    "version": "30000020"
  },
  "urllib3": {
    "version": "2.0.7"
  },
  "using_charset_normalizer": true,
  "using_pyopenssl": false
}
```




The low-level sockets API always successfully negotiates to TLS v1.3, too.
```python
def test_sockets():
    executor = futures.ThreadPoolExecutor(max_workers=WORKER_COUNT)
    def get():
        ctx = ssl.SSLContext(
            protocol=ssl.PROTOCOL_TLS_CLIENT,
        )
        ctx.load_default_certs()

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('www.google.com', 443))
        s = ctx.wrap_socket(s, server_hostname='google.com')
        s.sendall(b"GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n")

        while True:
            new = s.recv(4096)
            if not new:
                s.close()
                break
            print(new)

    return [
        future.result()
        for future in futures.as_completed([executor.submit(get) for i in range(48)])
    ]
```

Does this also happen with https://pypi.org/project/requests-futures/?

Thanks for the response @sigmavirus24! I see the same issue with `requests-futures`.
```python

def test_requests_futures_threaded():
    executor = futures.ThreadPoolExecutor(max_workers=WORKER_COUNT)

    session = FuturesSession(executor=executor)
    request_futures = [session.get('https://google.com') for i in range(48)]

    return [
        future.result()

        for future in futures.as_completed(request_futures)
    ]
```

<img width="1090" alt="Screenshot 2023-10-27 at 9 49 50 AM" src="https://github.com/psf/requests/assets/437360/732d5f1f-ce3d-439c-a73c-fd5d4058f0a8">

---

I've tried setting custom `SSLContext` options by subclassing `HTTPAdapter` and modifying the `PoolManager`, but that seems to have had no effect.

This reproduces in Jammy across Python 3.9, 3.10, 3.11, and 3.12.
I can get the correct behavior by installing OpenSSL v3.0.7 from source.

```dockerfile
FROM ubuntu:jammy
ENV DEBIAN_FRONTEND noninteractive


RUN apt update -y && \
    apt install -y software-properties-common wget && \
    add-apt-repository ppa:deadsnakes/ppa
RUN apt-get install -y build-essential cmake zlib1g-dev libcppunit-dev git subversion
RUN wget http://www.openssl.org/source/openssl-3.0.7.tar.gz
RUN tar -xzvf openssl-3.0.7.tar.gz
RUN cd openssl-3.0.7 && ./config --prefix=/usr/local/openssl --openssldir=/usr/local/openssl && make && make install
ENV LDFLAGS="-L/usr/local/openssl/lib/ -L/usr/local/openssl/lib64/"
ENV LD_LIBRARY_PATH="/usr/local/openssl/lib/:/usr/local/openssl/lib64/"
ENV CPPFLAGS="-I/usr/local/openssl/include -I/usr/local/openssl/include/openssl"
# Add a package repo to get archived python versions.
RUN apt install -y software-properties-common python3.11-dev python3.11-distutils python3-pip vim


RUN pip install requests requests-futures
```



Ubuntu Lunar Lobster uses OpenSSL v3.0.8 and does not reproduce the issue. 

I've bisected the issue to be fixed by OpenSSL v3.0.4.

This is probably safe to close, but will await a response before doing so.

I'm intrigued but there's nothing for us to do because python itself binds to OpenSSL for us and we don't control that layer. Genuinely not sure why this wouldn't reproduce with urllib3 directly but again, I can't think of what we could be doing differently except using a different cert bundle (maybe).

I should also say this was a truly excellent report. Thank you for the details and the investigation you did. Hopefully it helps someone else

Thanks. There were a number of memory-related fixes [between OpenSSL v3.0.3 and v3.0.4](https://github.com/openssl/openssl/compare/openssl-3.0.3...openssl-3.0.4) which may play into why this behavior is exhibited when using threading.

For now our fix will be to use Ubuntu > 23.04.