Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Lib/test/test_ctypes/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ctypes.util import find_library, struct, wrap_dll_function
from test import support
from test.support import import_helper
from test.support import script_helper
_ctypes_test = import_helper.import_module("_ctypes_test")


Expand Down Expand Up @@ -559,6 +560,19 @@ def cb():

self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)

def test_import_does_not_open_fds(self):
# gh-135893: Importing ctypes must not allocate a libffi closure,
# which can leave a file descriptor open for the rest of the
# process' lifetime.
code = """if 1:
from test.support import os_helper
before = os_helper.fd_count()
import ctypes
after = os_helper.fd_count()
assert before == after
"""
script_helper.assert_python_ok("-c", code)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a regression where importing :mod:`ctypes` could leave an extra file
descriptor open on some Linux systems, or hang in x86-64 macOS applications
signed with the hardened runtime.
12 changes: 0 additions & 12 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -6553,18 +6553,6 @@ _ctypes_add_version_constants(PyObject *mod)
static int
_ctypes_mod_exec(PyObject *mod)
{
// See https://github.com/python/cpython/issues/128485
// This allocates some memory and then frees it to ensure that the
// the dlmalloc allocator initializes itself to avoid data races
// in free-threading.
void *codeloc = NULL;
void *ptr = Py_ffi_closure_alloc(sizeof(void *), &codeloc);
if (ptr == NULL) {
PyErr_NoMemory();
return -1;
}
Py_ffi_closure_free(ptr);

_ctypes_init_fielddesc();

ctypes_state *st = get_module_state(mod);
Expand Down
28 changes: 28 additions & 0 deletions Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#endif

#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_lock.h" // _PyOnceFlag_CallOnce()
#include "pycore_runtime.h" // _Py_ID()

#include <ffi.h>
Expand Down Expand Up @@ -412,6 +413,27 @@ static CThunkObject* CThunkObject_new(ctypes_state *st, Py_ssize_t nargs)
return p;
}

static _PyOnceFlag closure_alloc_init_once = {0};

// This is done lazily with a once flag rather than in the module exec function,
// because allocating a closure can have some side effects that affect the whole
// process (gh-135893).
static int
closure_alloc_init(void *Py_UNUSED(arg))
{
// See https://github.com/python/cpython/issues/128485
// This allocates some memory and then frees it to ensure that libffi's
// dlmalloc allocator initializes itself exactly once, avoiding data races
// between threads that don't share a GIL.
void *codeloc = NULL;
void *ptr = Py_ffi_closure_alloc(sizeof(void *), &codeloc);
if (ptr == NULL) {
return -1;
}
Py_ffi_closure_free(ptr);
return 0;
}

CThunkObject *_ctypes_alloc_callback(ctypes_state *st,
PyObject *callable,
PyObject *converters,
Expand All @@ -431,6 +453,12 @@ CThunkObject *_ctypes_alloc_callback(ctypes_state *st,

assert(CThunk_CheckExact(st, (PyObject *)p));

if (_PyOnceFlag_CallOnce(&closure_alloc_init_once,
closure_alloc_init, NULL) < 0) {
PyErr_NoMemory();
goto error;
}

p->pcl_write = Py_ffi_closure_alloc(sizeof(ffi_closure), &p->pcl_exec);
if (p->pcl_write == NULL) {
PyErr_NoMemory();
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/ignored.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Modules/posixmodule.c os_pipe_impl pipe2_works_atomic -
## guards around resource init
Python/thread_pthread.h PyThread__init_thread lib_initialized -
Modules/_struct.c - endian_tables_init_once -
Modules/_ctypes/callbacks.c - closure_alloc_init_once -

##-----------------------
## other values (not Python-specific)
Expand Down
Loading