Skip to content

Requests v2.32.0-v2.32.3 caused a segmentation fault with multiple simultaneous requests with a certificate. #6872

Closed
@alloni

Description

@alloni

We are having intermittent segmentation faults on requests (using requests v2.32.0-v2.32.3, urllib3 v1.26.19). On investigation with faulthandler, they occur when a certificate is passed in, and two separate threads are trying to access the same ssl_context at the same time - one updating it with the given cert, one reading:

Current thread 0x00007f46c114d700 (most recent call first):
  File "<library path>/site-packages/urllib3/util/ssl_.py", line 418 in ssl_wrap_socket
  File "<library path>/site-packages/urllib3/connection.py", line 419 in connect
  File "<library path>/site-packages/urllib3/connectionpool.py", line 1060 in _validate_conn
  File "<library path>/site-packages/urllib3/connectionpool.py", line 404 in _make_request
  File "<library path>/site-packages/urllib3/connectionpool.py", line 715 in urlopen
  File "<library path>/site-packages/requests/adapters.py", line 667 in send
  File "<library path>/site-packages/requests/sessions.py", line 703 in send
  File "<library path>/site-packages/requests/sessions.py", line 589 in request
  File "<library path>/site-packages/requests/sessions.py", line 602 in get
  ...

Thread 0x00007f469e7fc700 (most recent call first):
  File "/usr/ssl.py", line 1382 in do_handshake
  File "/usr/ssl.py", line 1104 in _create
  File "/usr/ssl.py", line 517 in wrap_socket
  File "<library path>/site-packages/urllib3/util/ssl_.py", line 493 in _ssl_wrap_socket_impl
  File "<library path>/site-packages/urllib3/util/ssl_.py", line 449 in ssl_wrap_socket
  File "<library path>/site-packages/urllib3/connection.py", line 419 in connect
  File "<library path>/site-packages/urllib3/connectionpool.py", line 1060 in _validate_conn
  File "<library path>/site-packages/urllib3/connectionpool.py", line 404 in _make_request
  File "<library path>/site-packages/urllib3/connectionpool.py", line 715 in urlopen
  File "<library path>/site-packages/requests/adapters.py", line 667 in send
  File "<library path>/site-packages/requests/sessions.py", line 703 in send
  File "<library path>/site-packages/requests/sessions.py", line 589 in request
  File "<library path>/site-packages/requests/sessions.py", line 602 in get
  ...

This seems to happen because requests v2.32 switched from having a request without an SSL context create a new one to using the same preloaded SSL context across multiple requests. (This is #6745 with more information as to what exactly is going on - opening a new issue as the original issue creator has left the company.)

Expected Result

No segmentation faults.

Actual Result

Got intermittent segmentation faults.

Reproduction Steps

As per the original issue - and note that it only crashes if a generated certificate is passed in - openssl req -x509 -newkey rsa:4096 -keyout client.key -out client.crt -days 365 -nodes:

import concurrent.futures
import random
import uuid
from threading import Thread
from time import time

import requests


def do_request():
    start = time()
    random_id = uuid.uuid4()
    delay = random.randint(1, 5)
    print("start {} delay {} seconds".format(random_id, delay))
    endpoints = []
    endpoints.append('https://httpbin.org/delay/' + str(delay))
    delay = str(random.randint(1, 5)) + 's'
    endpoints.append('https://run.mocky.io/v3/0432e9f0-674f-45bd-9c18-628b861c2258?mocky-delay=' + str(delay))
    random.shuffle(endpoints)
    response = None
    for endpoint in endpoints:
        try:
            print("start {} delay {} seconds".format(random_id, endpoint))
            if 'run' in endpoint:
                cert = './client.crt', './client.key'
                response = requests.get(endpoint, timeout=random.randint(1, 5), cert=cert)
            else:
                response = requests.get(endpoint, timeout=random.randint(1, 5))
        except Exception as e:
            print(e)
    end = time()


    print("finished {} in {} seconds".format(random_id, end - start))
    return response


def measure():
    cnt = 20
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        futures = []
        for server in range(1, cnt):
            futures.append(executor.submit(do_request))
        for future in concurrent.futures.as_completed(futures):
            pass


for i in range(1, 500):
    threads = [Thread(target=measure, args=()) for _ in range(5)]

    for t in threads: t.start()
    for t in threads: t.join()

System Information

{
  "chardet": {
    "version": null
  },
  "charset_normalizer": {
    "version": "3.4.1"
  },
  "cryptography": {
    "version": "43.0.1"
  },
  "idna": {
    "version": "3.10"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.11.11"
  },
  "platform": {
    "release": "4.18.0-553.34.1.el8_10.x86_64",
    "system": "Linux"
  },
  "pyOpenSSL": {
    "openssl_version": "30300020",
    "version": "24.2.1"
  },
  "requests": {
    "version": "2.32.3"
  },
  "system_ssl": {
    "version": "101010bf"
  },
  "urllib3": {
    "version": "1.26.19"
  },
  "using_charset_normalizer": true,
  "using_pyopenssl": true
}

(Attempted with requests v2.32.0, v2.32.2, v2.32.3, urllib3 v1.26.18, v1.26.19.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions