python / python/cpython

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

Đang mở
#141,012 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

extension-modules topic-SSL type-crash
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với trình tái hiện đồng thời và các vị trí trong Modules/_ssl.c được hiển thị trong trace của AddressSanitizer, đặc biệt là set_alpn_protocols và wrap_socket. Chạy trình tái hiện dưới AddressSanitizer trong khi dùng chung một SSLContext; được xem là hoàn tất khi việc cập nhật ALPN đồng thời và tạo kết nối không còn gây ra use-after-free.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
security
Loại issue
Lỗi
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Cần làm rõ
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.