python / python/cpython

Use-after-free due to race between SSLContext.set_alpn_protocols and opening a connection

Ouverte
#141,012 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

extension-modules topic-SSL type-crash
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

Description

Bug report

Bug description:

I am not completely sure whether this should be considered a bug in Python, or urllib3, or the code using them, but I encountered a use-after-free related to the set_alpn_protocols method of SSLContext. I saw it with production code using urllib3 via requests, but here is a reproducer using just standard library modules. It assumes an HTTPS server running on localhost:

import ssl
import threading
import http.client

context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

class Test(threading.Thread):
    def run(self):
        while True:
            context.set_alpn_protocols(['http/1.1'])

            conn = http.client.HTTPSConnection("localhost", context=context)
            conn.request("GET", "/")
            response = conn.getresponse()
            conn.close()


threads = [ Test() for _ in range(8) ]
for t in threads:
    t.start()

Run it under AddressSanitizer. You don't actually need to compile with ASAN to detect the problem, because its normal heap checking catches the problem. On my system it runs for a few minutes before it fails. This is from Python 3.11 where I have somewhat useful debugging symbols, but I've seen it with 3.13, and I don't think anything has changed since then.

$ LD_PRELOAD=/usr/lib64/libasan.so.8 python3 alpn_test.py
=================================================================
==229438==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b9ae6766350 at pc 0x7f7ae7ce4937 bp 0x7b7ad33ea950 sp 0x7b7ad33ea110
READ of size 9 at 0x7b9ae6766350 thread T3
    #0 0x7f7ae7ce4936 in memcpy (/usr/lib64/libasan.so.8+0xe4936) (BuildId: 10b8ccd49f75c21babf1d7abe51bb63589d8471f)
    #1 0x7f7ae674283e in ossl_ssl_connection_new_int (/lib64/libssl.so.3+0x1a83e) (BuildId: 9618498bf75fd2ec21ade1eb4d21b85902c78f54)
    #2 0x7f7ae7be3f08 in newPySSLSocket /usr/src/debug/tw-python3-3.11.4-4.el9.x86_64/Modules/_ssl.c:830
    #3 0x7f7ae7be45a5 in _ssl__SSLContext__wrap_socket_impl /usr/src/debug/tw-python3-3.11.4-4.el9.x86_64/Modules/_ssl.c:4236
    #4 0x7f7ae7be45a5 in _ssl__SSLContext__wrap_socket /usr/src/debug/tw-python3-3.11.4-4.el9.x86_64/Modules/clinic/_ssl.c.h:687
    #5 0x7f7ae77ea4d0 in method_vectorcall_FASTCALL_KEYWORDS Objects/descrobject.c:426
    #6 0x7f7ae77e59a7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:92
    #7 0x7f7ae77e59a7 in PyObject_Vectorcall Objects/call.c:299
    #8 0x7f7ae78438c9 in _PyEval_EvalFrameDefault Python/ceval.c:4774
    #9 0x7f7ae7842556 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:73
    #10 0x7f7ae7842556 in _PyEval_Vector Python/ceval.c:6439
    #11 0x7f7ae77e7185 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:92
    #12 0x7f7ae77e7185 in method_vectorcall Objects/classobject.c:67
    #13 0x7f7ae78f627f in thread_run Modules/_threadmodule.c:1092
    #14 0x7f7ae78d91a3 in pythread_wrapper Python/thread_pthread.h:241
    #15 0x7f7ae7c28ee5 in asan_thread_start(void*) (/usr/lib64/libasan.so.8+0x28ee5) (BuildId: 10b8ccd49f75c21babf1d7abe51bb63589d8471f)
    #16 0x7f7ae747ff53 in start_thread (/lib64/libc.so.6+0x71f53) (BuildId: 48c4b9b1efb1df15da8e787f489128bf31893317)
    #17 0x7f7ae750332b in __clone3 (/lib64/libc.so.6+0xf532b) (BuildId: 48c4b9b1efb1df15da8e787f489128bf31893317)

0x7b9ae6766350 is located 0 bytes inside of 9-byte region [0x7b9ae6766350,0x7b9ae6766359)
freed by thread T7 here:
    #0 0x7f7ae7ce5beb in free.part.0 (/usr/lib64/libasan.so.8+0xe5beb) (BuildId: 10b8ccd49f75c21babf1d7abe51bb63589d8471f)
    #1 0x7f7ae67416f0 in SSL_CTX_set_alpn_protos (/lib64/libssl.so.3+0x196f0) (BuildId: 9618498bf75fd2ec21ade1eb4d21b85902c78f54)
    #2 0x7f7ae7be2109 in _ssl__SSLContext__set_alpn_protocols_impl /usr/src/debug/tw-python3-3.11.4-4.el9.x86_64/Modules/_ssl.c:3378
    #3 0x7f7ae7be2109 in _ssl__SSLContext__set_alpn_protocols /usr/src/debug/tw-python3-3.11.4-4.el9.x86_64/Modules/clinic/_ssl.c.h:507
    #4 0x7b7ae4b1d4ef  (<unknown module>)

previously allocated by thread T3 here:
    #0 0x7f7ae7ce6f2b in malloc (/usr/lib64/libasan.so.8+0xe6f2b) (BuildId: 10b8ccd49f75c21babf1d7abe51bb63589d8471f)
    #1 0x7b7ad5b32b4d in CRYPTO_malloc (/lib64/libcrypto.so.3+0x132b4d) (BuildId: bb6058f574b5116601d7bf1eaa40513e2b781e36)
    #2 0x7b7ad5b32e6b in CRYPTO_memdup (/lib64/libcrypto.so.3+0x132e6b) (BuildId: bb6058f574b5116601d7bf1eaa40513e2b781e36)
    #3 0x7f7ae67416cf in SSL_CTX_set_alpn_protos (/lib64/libssl.so.3+0x196cf) (BuildId: 9618498bf75fd2ec21ade1eb4d21b85902c78f54)
    #4 0x7f7ae7be2109 in _ssl__SSLContext__set_alpn_protocols_impl /usr/src/debug/tw-python3-3.11.4-4.el9.x86_64/Modules/_ssl.c:3378
    #5 0x7f7ae7be2109 in _ssl__SSLContext__set_alpn_protocols /usr/src/debug/tw-python3-3.11.4-4.el9.x86_64/Modules/clinic/_ssl.c.h:507
    #6 0x7b7ae4b1f06f  (<unknown module>)

What is happening is that the threads are sharing the SSLContext object. The call to set_alpn_protocols() replaces the ALPN data in the underlying OpenSSL SSL_CTX. It frees the previously-set value. It does that while holding the GIL. Simultaneously, another thread uses the same SSL_CTX, while not holding the GIL, as it opens a new connection. It can therefore read the data freed by another thread.

This is particularly a problem related to urllib3, because that always calls set_alpn_protocols(), even if it is given an existing SSLContext. Perhaps it should be considered a problem there, rather than a core Python bug.

CPython versions tested on:

3.13

Operating systems tested on:

Linux

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par le cas de reproduction concurrente et les emplacements de Modules/_ssl.c indiqués dans la trace d’AddressSanitizer, en particulier set_alpn_protocols et wrap_socket. Exécutez le cas de reproduction sous AddressSanitizer tout en partageant un SSLContext ; c’est terminé lorsque les mises à jour concurrentes d’ALPN et la création de connexions ne produisent plus de use-after-free.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
security
Type d'issue
Bug
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
À l'abandon
Clarté
À clarifier
Accessibilité débutants
25/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.