Skip to content

Commit b0adc30

Browse files
committed
Free threading fixes
1 parent 80133a5 commit b0adc30

File tree

2 files changed

+30
-19
lines changed

2 files changed

+30
-19
lines changed

Python/import.c

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4250,8 +4250,9 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name, PyObject *builtin
42504250
}
42514251
}
42524252

4253-
// Release the lock - we only needed it for initialization
4254-
// The dict operations below are thread-safe on their own
4253+
// Release the lock - we only needed it for initialization.
4254+
// The dict operations below use PyDict_SetDefaultRef which uses
4255+
// critical sections for thread-safety in free-threaded builds.
42554256
_PyImport_ReleaseLock(interp);
42564257

42574258
Py_INCREF(name);
@@ -4283,19 +4284,17 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name, PyObject *builtin
42834284
}
42844285

42854286
// Record the child to be added when the parent is imported.
4286-
PyObject *lazy_submodules;
4287-
if (PyDict_GetItemRef(lazy_modules, parent, &lazy_submodules) < 0) {
4287+
// Use PyDict_SetDefaultRef to atomically get-or-create the set,
4288+
// avoiding TOCTOU races in free-threaded builds.
4289+
PyObject *empty_set = PySet_New(NULL);
4290+
if (empty_set == NULL) {
42884291
goto done;
42894292
}
4290-
if (lazy_submodules == NULL) {
4291-
lazy_submodules = PySet_New(NULL);
4292-
if (lazy_submodules == NULL) {
4293-
goto done;
4294-
}
4295-
if (PyDict_SetItem(lazy_modules, parent, lazy_submodules) < 0) {
4296-
Py_DECREF(lazy_submodules);
4297-
goto done;
4298-
}
4293+
PyObject *lazy_submodules;
4294+
int setdefault_result = PyDict_SetDefaultRef(lazy_modules, parent, empty_set, &lazy_submodules);
4295+
Py_DECREF(empty_set);
4296+
if (setdefault_result < 0) {
4297+
goto done;
42994298
}
43004299
assert(PyAnySet_CheckExact(lazy_submodules));
43014300
if (PySet_Add(lazy_submodules, child) < 0) {
@@ -4357,11 +4356,15 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
43574356
_PyInterpreterFrame *frame = _PyEval_GetFrame();
43584357
assert(frame != NULL && frame->f_globals == frame->f_locals); // should only be called in global scope
43594358

4360-
// Check if the filter disables the lazy import
4359+
// Check if the filter disables the lazy import.
4360+
// We must hold a reference to the filter while calling it to prevent
4361+
// use-after-free if another thread replaces it via PyImport_SetLazyImportsFilter.
43614362
PyObject *filter = FT_ATOMIC_LOAD_PTR_RELAXED(LAZY_IMPORTS_FILTER(interp));
4363+
Py_XINCREF(filter);
43624364
if (filter != NULL) {
43634365
PyObject *modname;
43644366
if (PyDict_GetItemRef(globals, &_Py_ID(__name__), &modname) < 0) {
4367+
Py_DECREF(filter);
43654368
Py_DECREF(abs_name);
43664369
return NULL;
43674370
} else if (modname == NULL) {
@@ -4376,6 +4379,7 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
43764379
);
43774380

43784381
Py_DECREF(modname);
4382+
Py_DECREF(filter);
43794383

43804384
if (res == NULL) {
43814385
Py_DECREF(abs_name);
@@ -4408,15 +4412,20 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
44084412
return NULL;
44094413
}
44104414

4411-
// Add the module name to sys.lazy_modules set (PEP 810)
4412-
PyObject *lazy_modules_set = interp->imports.lazy_modules_set;
4415+
// Add the module name to sys.lazy_modules set (PEP 810).
4416+
// We must hold a reference to the set while using it to prevent
4417+
// use-after-free if another thread clears it during interpreter shutdown.
4418+
PyObject *lazy_modules_set = FT_ATOMIC_LOAD_PTR_RELAXED(interp->imports.lazy_modules_set);
4419+
Py_XINCREF(lazy_modules_set);
44134420
if (lazy_modules_set != NULL) {
44144421
assert(PyAnySet_CheckExact(lazy_modules_set));
44154422
if (PySet_Add(lazy_modules_set, abs_name) < 0) {
4423+
Py_DECREF(lazy_modules_set);
44164424
Py_DECREF(res);
44174425
Py_DECREF(abs_name);
44184426
return NULL;
44194427
}
4428+
Py_DECREF(lazy_modules_set);
44204429
}
44214430

44224431
Py_DECREF(abs_name);
@@ -4782,9 +4791,11 @@ PyImport_SetLazyImportsFilter(PyObject *filter)
47824791

47834792
PyInterpreterState *interp = _PyInterpreterState_GET();
47844793
#ifdef Py_GIL_DISABLED
4785-
// exchange just in case another thread did same thing at same time
4794+
// Exchange the filter atomically. Use deferred DECREF to prevent
4795+
// use-after-free: another thread may have loaded the old filter
4796+
// and be about to INCREF it.
47864797
PyObject *old = _Py_atomic_exchange_ptr(&LAZY_IMPORTS_FILTER(interp), Py_XNewRef(filter));
4787-
Py_XDECREF(old);
4798+
_PyObject_XDecRefDelayed(old);
47884799
#else
47894800
Py_XSETREF(LAZY_IMPORTS_FILTER(interp), Py_XNewRef(filter));
47904801
#endif

Python/sysmodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2908,7 +2908,7 @@ sys_get_lazy_modules_impl(PyObject *module)
29082908
/*[clinic end generated code: output=4c641f8881ba87c0 input=511b3a9682c09282]*/
29092909
{
29102910
PyInterpreterState *interp = _PyInterpreterState_GET();
2911-
PyObject *lazy_modules_set = interp->imports.lazy_modules_set;
2911+
PyObject *lazy_modules_set = FT_ATOMIC_LOAD_PTR_RELAXED(interp->imports.lazy_modules_set);
29122912
if (lazy_modules_set == NULL) {
29132913
return PySet_New(NULL);
29142914
}

0 commit comments

Comments
 (0)