-
-
Notifications
You must be signed in to change notification settings - Fork 34.1k
Description
Bug report
Bug description:
Modules/_ssl.c:920-921 When SSL_new() returns NULL, Py_DECREF(self) frees the PySSLSocket object, then get_state_ctx(self) dereferences the freed pointer with a wrong type cast (PySSLSocket* PySSLContext*).
Bug Location
Modules/_ssl.c, function newPySSLSocket(), lines 919-922:
if (self->ssl == NULL) {
Py_DECREF(self); // frees self
_setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__); // UAF
return NULL;
}Root Cause
Two bugs in one line:
-
UAF:
Py_DECREF(self)drops refcount to 0,PySSL_dealloc()andPyObject_GC_Del(self)frees memory. Next line reads freed memory. -
Type Confusion:
get_state_ctx()is defined as(((PySSLContext *)(c))->state)(_ssl.h:52).selfisPySSLSocket*, notPySSLContext*. Thestatefield offset inPySSLContextexceedsPySSLSocketsize, out-of-bounds read.
Trigger Condition
SSL_new() returns NULL . Reachable from Python via SSLContext.wrap_bio() or SSLContext.wrap_socket().
Patch
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -917,8 +917,8 @@
self->ssl = SSL_new(ctx);
PySSL_END_ALLOW_THREADS(sslctx)
if (self->ssl == NULL) {
+ _setSSLError(get_state_ctx(sslctx), NULL, 0, __FILE__, __LINE__);
Py_DECREF(self);
- _setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__);
return NULL;
}Changes: self to sslctx, error call moved before Py_DECREF.
Reproduction
Build CPython with ASAN
./configure --with-address-sanitizer --with-pydebug
make -j$(nproc)Hook: force SSL_new() to return NULL (ssl_new_hook.c)
#define _GNU_SOURCE
#include <stdio.h>
typedef struct ssl_st SSL;
typedef struct ssl_ctx_st SSL_CTX;
SSL *SSL_new(SSL_CTX *ctx)
{
fprintf(stderr, "[HOOK] SSL_new(%p) returning NULL\n", (void *)ctx);
return NULL;
}gcc -shared -fPIC -o ssl_new_hook.so ssl_new_hook.cTrigger (trigger_uaf.py)
import ssl
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
incoming = ssl.MemoryBIO()
outgoing = ssl.MemoryBIO()
ctx.wrap_bio(incoming, outgoing, server_side=False)Run
ASAN_LIB=$(gcc -print-file-name=libasan.so)
LD_PRELOAD="$ASAN_LIB ./ssl_new_hook.so" ./python trigger_uaf.pyASAN Output (before fix)
❯ LD_PRELOAD="/usr/lib/gcc/x86_64-linux-gnu/14/libasan.so ./poc/ssl_new_hook.so" ./python ./poc/trigger_uaf.py
[*] Calling ctx.wrap_bio() - triggers newPySSLSocket() - SSL_new() will fail
[HOOK] SSL_new(0x51c000012880) called returning NULL to trigger error path
=================================================================
==88693==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50b00008d698 at pc 0x77b215896350 bp 0x7ffdf8af79b0 sp 0x7ffdf8af79a0
READ of size 8 at 0x50b00008d698 thread T0
#0 0x77b21589634f in newPySSLSocket Modules/_ssl.c:921
#1 0x77b21589672b in _ssl__SSLContext__wrap_bio_impl Modules/_ssl.c:4962
#2 0x77b215896c83 in _ssl__SSLContext__wrap_bio Modules/clinic/_ssl.c.h:2134
#3 0x616a3deb9bf1 in method_vectorcall_FASTCALL_KEYWORDS Objects/descrobject.c:421
#4 0x616a3de99cb6 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:136
#5 0x616a3de99da9 in PyObject_Vectorcall Objects/call.c:327
#6 0x616a3e116672 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:762
#7 0x616a3e12cfc9 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:3193
#8 0x616a3e15d85c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:118
#9 0x616a3e15dbc2 in _PyEval_Vector Python/ceval.c:2125
#10 0x616a3e15de78 in PyEval_EvalCode Python/ceval.c:673
#11 0x616a3e261171 in run_eval_code_obj Python/pythonrun.c:1366
#12 0x616a3e2614b7 in run_mod Python/pythonrun.c:1469
#13 0x616a3e2623ec in pyrun_file Python/pythonrun.c:1294
#14 0x616a3e265222 in _PyRun_SimpleFileObject Python/pythonrun.c:518
#15 0x616a3e2654ce in _PyRun_AnyFileObject Python/pythonrun.c:81
#16 0x616a3e2ba3f6 in pymain_run_file_obj Modules/main.c:410
#17 0x616a3e2ba663 in pymain_run_file Modules/main.c:429
#18 0x616a3e2bbe61 in pymain_run_python Modules/main.c:691
#19 0x616a3e2bc4f7 in Py_RunMain Modules/main.c:772
#20 0x616a3e2bc6e3 in pymain_main Modules/main.c:802
#21 0x616a3e2bca68 in Py_BytesMain Modules/main.c:826
#22 0x616a3dd24675 in main Programs/python.c:15
#23 0x77b21782a3b7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#24 0x77b21782a47a in __libc_start_main_impl ../csu/libc-start.c:360
#25 0x616a3dd245a4 in _start (/home/raminfp/Projects/cpython/python+0x2ec5a4) (BuildId: 0396070aaf722d21b278a88393f68f9832f122d1)
0x50b00008d698 is located 16 bytes after 104-byte region [0x50b00008d620,0x50b00008d688)
freed by thread T0 here:
#0 0x77b217cfc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x616a3df65f10 in _PyMem_RawFree Objects/obmalloc.c:93
#2 0x616a3df682ca in _PyMem_DebugRawFree Objects/obmalloc.c:3067
#3 0x616a3df6830b in _PyMem_DebugFree Objects/obmalloc.c:3212
#4 0x616a3df90698 in PyObject_Free Objects/obmalloc.c:1634
#5 0x616a3e1d0ca7 in PyObject_GC_Del Python/gc.c:2460
#6 0x77b215886942 in PySSL_dealloc Modules/_ssl.c:2463
#7 0x616a3df5c50d in _Py_Dealloc Objects/object.c:3270
#8 0x77b21587f9ba in Py_DECREF Include/refcount.h:403
#9 0x77b21589630e in newPySSLSocket Modules/_ssl.c:920
#10 0x77b21589672b in _ssl__SSLContext__wrap_bio_impl Modules/_ssl.c:4962
#11 0x77b215896c83 in _ssl__SSLContext__wrap_bio Modules/clinic/_ssl.c.h:2134
#12 0x616a3deb9bf1 in method_vectorcall_FASTCALL_KEYWORDS Objects/descrobject.c:421
#13 0x616a3de99cb6 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:136
#14 0x616a3de99da9 in PyObject_Vectorcall Objects/call.c:327
#15 0x616a3e116672 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:762
#16 0x616a3e12cfc9 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:3193
#17 0x616a3e15d85c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:118
#18 0x616a3e15dbc2 in _PyEval_Vector Python/ceval.c:2125
#19 0x616a3e15de78 in PyEval_EvalCode Python/ceval.c:673
#20 0x616a3e261171 in run_eval_code_obj Python/pythonrun.c:1366
#21 0x616a3e2614b7 in run_mod Python/pythonrun.c:1469
#22 0x616a3e2623ec in pyrun_file Python/pythonrun.c:1294
#23 0x616a3e265222 in _PyRun_SimpleFileObject Python/pythonrun.c:518
#24 0x616a3e2654ce in _PyRun_AnyFileObject Python/pythonrun.c:81
#25 0x616a3e2ba3f6 in pymain_run_file_obj Modules/main.c:410
#26 0x616a3e2ba663 in pymain_run_file Modules/main.c:429
#27 0x616a3e2bbe61 in pymain_run_python Modules/main.c:691
#28 0x616a3e2bc4f7 in Py_RunMain Modules/main.c:772
#29 0x616a3e2bc6e3 in pymain_main Modules/main.c:802
previously allocated by thread T0 here:
#0 0x77b217cfd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x616a3df66827 in _PyMem_RawMalloc Objects/obmalloc.c:65
#2 0x616a3df65bf8 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2999
#3 0x616a3df65c60 in _PyMem_DebugRawMalloc Objects/obmalloc.c:3032
#4 0x616a3df674e8 in _PyMem_DebugMalloc Objects/obmalloc.c:3197
#5 0x616a3df90554 in PyObject_Malloc Objects/obmalloc.c:1605
#6 0x616a3e1d075a in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
#7 0x616a3e1d075a in gc_alloc Python/gc.c:2352
#8 0x616a3e1d08b0 in _PyObject_GC_New Python/gc.c:2372
#9 0x77b215895e8a in newPySSLSocket Modules/_ssl.c:901
#10 0x77b21589672b in _ssl__SSLContext__wrap_bio_impl Modules/_ssl.c:4962
#11 0x77b215896c83 in _ssl__SSLContext__wrap_bio Modules/clinic/_ssl.c.h:2134
#12 0x616a3deb9bf1 in method_vectorcall_FASTCALL_KEYWORDS Objects/descrobject.c:421
#13 0x616a3de99cb6 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:136
#14 0x616a3de99da9 in PyObject_Vectorcall Objects/call.c:327
#15 0x616a3e116672 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:762
#16 0x616a3e12cfc9 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:3193
#17 0x616a3e15d85c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:118
#18 0x616a3e15dbc2 in _PyEval_Vector Python/ceval.c:2125
#19 0x616a3e15de78 in PyEval_EvalCode Python/ceval.c:673
#20 0x616a3e261171 in run_eval_code_obj Python/pythonrun.c:1366
#21 0x616a3e2614b7 in run_mod Python/pythonrun.c:1469
#22 0x616a3e2623ec in pyrun_file Python/pythonrun.c:1294
#23 0x616a3e265222 in _PyRun_SimpleFileObject Python/pythonrun.c:518
#24 0x616a3e2654ce in _PyRun_AnyFileObject Python/pythonrun.c:81
#25 0x616a3e2ba3f6 in pymain_run_file_obj Modules/main.c:410
#26 0x616a3e2ba663 in pymain_run_file Modules/main.c:429
#27 0x616a3e2bbe61 in pymain_run_python Modules/main.c:691
#28 0x616a3e2bc4f7 in Py_RunMain Modules/main.c:772
#29 0x616a3e2bc6e3 in pymain_main Modules/main.c:802
#30 0x616a3e2bca68 in Py_BytesMain Modules/main.c:826
SUMMARY: AddressSanitizer: heap-buffer-overflow Modules/_ssl.c:921 in newPySSLSocket
Shadow bytes around the buggy address:
0x50b00008d400: fa fa 00 00 00 00 00 00 00 00 00 00 00 00 00 fa
0x50b00008d480: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x50b00008d500: 00 00 00 00 00 fa fa fa fa fa fa fa fa fa fd fd
0x50b00008d580: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa
0x50b00008d600: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
=>0x50b00008d680: fd fa fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa
0x50b00008d700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50b00008d780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50b00008d800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50b00008d880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50b00008d900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==88693==ABORTING
After fix
ssl.SSLError raised normally.
[HOOK] SSL_new(0x51c000012880) called returning NULL to trigger error path
Traceback (most recent call last):
File "/home/raminfp/Projects/cpython/poc/trigger_uaf.py", line 27, in <module>
sslobj = ctx.wrap_bio(incoming, outgoing, server_side=False)
File "/home/raminfp/Projects/cpython/Lib/ssl.py", line 468, in wrap_bio
return self.sslobject_class._create(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
incoming, outgoing, server_side=server_side,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server_hostname=self._encode_hostname(server_hostname),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
session=session, context=self,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/raminfp/Projects/cpython/Lib/ssl.py", line 816, in _create
sslobj = context._wrap_bio(
incoming, outgoing, server_side=server_side,
server_hostname=server_hostname,
owner=self, session=session
)
ssl.SSLError: unknown error (0x0) (_ssl.c:920)CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-144833: Fix use-after-free in SSL module when SSL_new() fails #144843
- [3.14] gh-144833: Fix use-after-free in SSL module when SSL_new() fails (GH-144843) #144858
- [3.13] gh-144833: Fix use-after-free in SSL module when SSL_new() fails (GH-144843) #144859
- [3.12] gh-144833: Fix use-after-free in SSL module when SSL_new() fails (GH-144843) #144860
- [3.11] gh-144833: Fix use-after-free in SSL module when SSL_new() fails (GH-144843) #144861
- [3.10] gh-144833: Fix use-after-free in SSL module when SSL_new() fails (GH-144843) #144862