gh-111924: Fix data races when swapping allocators (gh-130287)

CPython current temporarily changes `PYMEM_DOMAIN_RAW` to the default
allocator during initialization and shutdown. The motivation is to
ensure that core runtime structures are allocated and freed using the
same allocator. However, modifying the current allocator changes global
state and is not thread-safe even with the GIL. Other threads may be
allocating or freeing objects use PYMEM_DOMAIN_RAW; they are not
required to hold the GIL to call PyMem_RawMalloc/PyMem_RawFree.

This adds new internal-only functions like `_PyMem_DefaultRawMalloc`
that aren't affected by calls to `PyMem_SetAllocator()`, so they're
appropriate for Python runtime initialization and finalization. Use
these calls in places where we previously swapped to the default raw
allocator.
This commit is contained in:
Sam Gross 2025-02-20 11:31:15 -05:00 committed by GitHub
parent 568db400ff
commit ca22147547
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 155 additions and 154 deletions

View file

@ -13,7 +13,7 @@
#include "pycore_pyerrors.h" // _PyErr_SetString()
#include "pycore_pyhash.h" // _Py_KeyedHash()
#include "pycore_pylifecycle.h"
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pymem.h" // _PyMem_DefaultRawFree()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_sysmodule.h" // _PySys_ClearAttrString()
#include "pycore_time.h" // _PyTime_AsMicroseconds()
@ -2387,14 +2387,11 @@ PyImport_ExtendInittab(struct _inittab *newtab)
/* Force default raw memory allocator to get a known allocator to be able
to release the memory in _PyImport_Fini2() */
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
/* Allocate new memory for the combined table */
p = NULL;
if (i + n <= SIZE_MAX / sizeof(struct _inittab) - 1) {
size_t size = sizeof(struct _inittab) * (i + n + 1);
p = PyMem_RawRealloc(inittab_copy, size);
p = _PyMem_DefaultRawRealloc(inittab_copy, size);
}
if (p == NULL) {
res = -1;
@ -2408,9 +2405,7 @@ PyImport_ExtendInittab(struct _inittab *newtab)
}
memcpy(p + i, newtab, (n + 1) * sizeof(struct _inittab));
PyImport_Inittab = inittab_copy = p;
done:
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
return res;
}
@ -2445,7 +2440,7 @@ init_builtin_modules_table(void)
size++;
/* Make the copy. */
struct _inittab *copied = PyMem_RawMalloc(size * sizeof(struct _inittab));
struct _inittab *copied = _PyMem_DefaultRawMalloc(size * sizeof(struct _inittab));
if (copied == NULL) {
return -1;
}
@ -2459,7 +2454,7 @@ fini_builtin_modules_table(void)
{
struct _inittab *inittab = INITTAB;
INITTAB = NULL;
PyMem_RawFree(inittab);
_PyMem_DefaultRawFree(inittab);
}
PyObject *
@ -3977,22 +3972,10 @@ _PyImport_Init(void)
if (INITTAB != NULL) {
return _PyStatus_ERR("global import state already initialized");
}
PyStatus status = _PyStatus_OK();
/* Force default raw memory allocator to get a known allocator to be able
to release the memory in _PyImport_Fini() */
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
if (init_builtin_modules_table() != 0) {
status = PyStatus_NoMemory();
goto done;
return PyStatus_NoMemory();
}
done:
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
return status;
return _PyStatus_OK();
}
void
@ -4003,31 +3986,19 @@ _PyImport_Fini(void)
// ever dlclose() the module files?
_extensions_cache_clear_all();
/* Use the same memory allocator as _PyImport_Init(). */
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
/* Free memory allocated by _PyImport_Init() */
fini_builtin_modules_table();
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
}
void
_PyImport_Fini2(void)
{
/* Use the same memory allocator than PyImport_ExtendInittab(). */
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
// Reset PyImport_Inittab
PyImport_Inittab = _PyImport_Inittab;
/* Free memory allocated by PyImport_ExtendInittab() */
PyMem_RawFree(inittab_copy);
_PyMem_DefaultRawFree(inittab_copy);
inittab_copy = NULL;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
}

View file

@ -7,7 +7,7 @@
#include "pycore_pathconfig.h" // _Py_path_config
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
#include "pycore_pylifecycle.h" // _Py_PreInitializeFromConfig()
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pymem.h" // _PyMem_DefaultRawMalloc()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_pystats.h" // _Py_StatsOn()
#include "pycore_sysmodule.h" // _PySys_SetIntMaxStrDigits()
@ -619,53 +619,87 @@ _PyWideStringList_CheckConsistency(const PyWideStringList *list)
#endif /* Py_DEBUG */
void
_PyWideStringList_Clear(PyWideStringList *list)
static void
_PyWideStringList_ClearEx(PyWideStringList *list,
bool use_default_allocator)
{
assert(_PyWideStringList_CheckConsistency(list));
for (Py_ssize_t i=0; i < list->length; i++) {
PyMem_RawFree(list->items[i]);
if (use_default_allocator) {
_PyMem_DefaultRawFree(list->items[i]);
}
else {
PyMem_RawFree(list->items[i]);
}
}
if (use_default_allocator) {
_PyMem_DefaultRawFree(list->items);
}
else {
PyMem_RawFree(list->items);
}
PyMem_RawFree(list->items);
list->length = 0;
list->items = NULL;
}
void
_PyWideStringList_Clear(PyWideStringList *list)
{
_PyWideStringList_ClearEx(list, false);
}
int
_PyWideStringList_Copy(PyWideStringList *list, const PyWideStringList *list2)
static int
_PyWideStringList_CopyEx(PyWideStringList *list,
const PyWideStringList *list2,
bool use_default_allocator)
{
assert(_PyWideStringList_CheckConsistency(list));
assert(_PyWideStringList_CheckConsistency(list2));
if (list2->length == 0) {
_PyWideStringList_Clear(list);
_PyWideStringList_ClearEx(list, use_default_allocator);
return 0;
}
PyWideStringList copy = _PyWideStringList_INIT;
size_t size = list2->length * sizeof(list2->items[0]);
copy.items = PyMem_RawMalloc(size);
if (use_default_allocator) {
copy.items = _PyMem_DefaultRawMalloc(size);
}
else {
copy.items = PyMem_RawMalloc(size);
}
if (copy.items == NULL) {
return -1;
}
for (Py_ssize_t i=0; i < list2->length; i++) {
wchar_t *item = _PyMem_RawWcsdup(list2->items[i]);
wchar_t *item;
if (use_default_allocator) {
item = _PyMem_DefaultRawWcsdup(list2->items[i]);
}
else {
item = _PyMem_RawWcsdup(list2->items[i]);
}
if (item == NULL) {
_PyWideStringList_Clear(&copy);
_PyWideStringList_ClearEx(&copy, use_default_allocator);
return -1;
}
copy.items[i] = item;
copy.length = i + 1;
}
_PyWideStringList_Clear(list);
_PyWideStringList_ClearEx(list, use_default_allocator);
*list = copy;
return 0;
}
int
_PyWideStringList_Copy(PyWideStringList *list, const PyWideStringList *list2)
{
return _PyWideStringList_CopyEx(list, list2, false);
}
PyStatus
PyWideStringList_Insert(PyWideStringList *list,
@ -789,12 +823,7 @@ _PyWideStringList_AsTuple(const PyWideStringList *list)
void
_Py_ClearArgcArgv(void)
{
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
_PyWideStringList_Clear(&_PyRuntime.orig_argv);
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
_PyWideStringList_ClearEx(&_PyRuntime.orig_argv, true);
}
@ -802,17 +831,10 @@ static int
_Py_SetArgcArgv(Py_ssize_t argc, wchar_t * const *argv)
{
const PyWideStringList argv_list = {.length = argc, .items = (wchar_t **)argv};
int res;
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
// XXX _PyRuntime.orig_argv only gets cleared by Py_Main(),
// so it currently leaks for embedders.
res = _PyWideStringList_Copy(&_PyRuntime.orig_argv, &argv_list);
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
return res;
return _PyWideStringList_CopyEx(&_PyRuntime.orig_argv, &argv_list, true);
}

View file

@ -4,7 +4,7 @@
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_fileutils.h" // _Py_wgetcwd()
#include "pycore_pathconfig.h"
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pymem.h" // _PyMem_DefaultRawFree()
#include <wchar.h>
#include "marshal.h" // PyMarshal_ReadObjectFromString
@ -54,12 +54,9 @@ _PyPathConfig_GetGlobalModuleSearchPath(void)
void
_PyPathConfig_ClearGlobal(void)
{
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
#define CLEAR(ATTR) \
do { \
PyMem_RawFree(_Py_path_config.ATTR); \
_PyMem_DefaultRawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = NULL; \
} while (0)
@ -74,8 +71,6 @@ _PyPathConfig_ClearGlobal(void)
_Py_path_config._is_python_build = 0;
#undef CLEAR
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
}
PyStatus
@ -126,14 +121,11 @@ done:
PyStatus
_PyPathConfig_UpdateGlobal(const PyConfig *config)
{
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
#define COPY(ATTR) \
do { \
if (config->ATTR) { \
PyMem_RawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = _PyMem_RawWcsdup(config->ATTR); \
_PyMem_DefaultRawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->ATTR); \
if (!_Py_path_config.ATTR) goto error; \
} \
} while (0)
@ -141,8 +133,8 @@ _PyPathConfig_UpdateGlobal(const PyConfig *config)
#define COPY2(ATTR, SRCATTR) \
do { \
if (config->SRCATTR) { \
PyMem_RawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = _PyMem_RawWcsdup(config->SRCATTR); \
_PyMem_DefaultRawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->SRCATTR); \
if (!_Py_path_config.ATTR) goto error; \
} \
} while (0)
@ -165,9 +157,9 @@ _PyPathConfig_UpdateGlobal(const PyConfig *config)
#undef COPY2
#undef COPY_INT
PyMem_RawFree(_Py_path_config.module_search_path);
_PyMem_DefaultRawFree(_Py_path_config.module_search_path);
_Py_path_config.module_search_path = NULL;
PyMem_RawFree(_Py_path_config.calculated_module_search_path);
_PyMem_DefaultRawFree(_Py_path_config.calculated_module_search_path);
_Py_path_config.calculated_module_search_path = NULL;
do {
@ -176,7 +168,7 @@ _PyPathConfig_UpdateGlobal(const PyConfig *config)
cch += 1 + wcslen(config->module_search_paths.items[i]);
}
wchar_t *path = (wchar_t*)PyMem_RawMalloc(sizeof(wchar_t) * cch);
wchar_t *path = (wchar_t*)_PyMem_DefaultRawMalloc(sizeof(wchar_t) * cch);
if (!path) {
goto error;
}
@ -194,11 +186,9 @@ _PyPathConfig_UpdateGlobal(const PyConfig *config)
_Py_path_config.calculated_module_search_path = path;
} while (0);
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
return _PyStatus_OK();
error:
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
return _PyStatus_NO_MEMORY();
}
@ -218,29 +208,24 @@ Py_SetPath(const wchar_t *path)
return;
}
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
_PyMem_DefaultRawFree(_Py_path_config.prefix);
_PyMem_DefaultRawFree(_Py_path_config.exec_prefix);
_PyMem_DefaultRawFree(_Py_path_config.stdlib_dir);
_PyMem_DefaultRawFree(_Py_path_config.module_search_path);
_PyMem_DefaultRawFree(_Py_path_config.calculated_module_search_path);
PyMem_RawFree(_Py_path_config.prefix);
PyMem_RawFree(_Py_path_config.exec_prefix);
PyMem_RawFree(_Py_path_config.stdlib_dir);
PyMem_RawFree(_Py_path_config.module_search_path);
PyMem_RawFree(_Py_path_config.calculated_module_search_path);
_Py_path_config.prefix = _PyMem_RawWcsdup(L"");
_Py_path_config.exec_prefix = _PyMem_RawWcsdup(L"");
_Py_path_config.prefix = _PyMem_DefaultRawWcsdup(L"");
_Py_path_config.exec_prefix = _PyMem_DefaultRawWcsdup(L"");
// XXX Copy this from the new module_search_path?
if (_Py_path_config.home != NULL) {
_Py_path_config.stdlib_dir = _PyMem_RawWcsdup(_Py_path_config.home);
_Py_path_config.stdlib_dir = _PyMem_DefaultRawWcsdup(_Py_path_config.home);
}
else {
_Py_path_config.stdlib_dir = _PyMem_RawWcsdup(L"");
_Py_path_config.stdlib_dir = _PyMem_DefaultRawWcsdup(L"");
}
_Py_path_config.module_search_path = _PyMem_RawWcsdup(path);
_Py_path_config.module_search_path = _PyMem_DefaultRawWcsdup(path);
_Py_path_config.calculated_module_search_path = NULL;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
if (_Py_path_config.prefix == NULL
|| _Py_path_config.exec_prefix == NULL
|| _Py_path_config.stdlib_dir == NULL
@ -256,18 +241,13 @@ Py_SetPythonHome(const wchar_t *home)
{
int has_value = home && home[0];
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
PyMem_RawFree(_Py_path_config.home);
_PyMem_DefaultRawFree(_Py_path_config.home);
_Py_path_config.home = NULL;
if (has_value) {
_Py_path_config.home = _PyMem_RawWcsdup(home);
_Py_path_config.home = _PyMem_DefaultRawWcsdup(home);
}
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
if (has_value && _Py_path_config.home == NULL) {
path_out_of_memory(__func__);
}
@ -279,18 +259,13 @@ Py_SetProgramName(const wchar_t *program_name)
{
int has_value = program_name && program_name[0];
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
PyMem_RawFree(_Py_path_config.program_name);
_PyMem_DefaultRawFree(_Py_path_config.program_name);
_Py_path_config.program_name = NULL;
if (has_value) {
_Py_path_config.program_name = _PyMem_RawWcsdup(program_name);
_Py_path_config.program_name = _PyMem_DefaultRawWcsdup(program_name);
}
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
if (has_value && _Py_path_config.program_name == NULL) {
path_out_of_memory(__func__);
}

View file

@ -16,7 +16,7 @@
#include "pycore_parking_lot.h" // _PyParkingLot_AfterFork()
#include "pycore_pyerrors.h" // _PyErr_Clear()
#include "pycore_pylifecycle.h" // _PyAST_Fini()
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pymem.h" // _PyMem_DebugEnabled()
#include "pycore_pystate.h"
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
#include "pycore_stackref.h" // Py_STACKREF_DEBUG

View file

@ -29,7 +29,7 @@ Data members:
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
#include "pycore_pylifecycle.h" // _PyErr_WriteUnraisableDefaultHook()
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pymem.h" // _PyMem_DefaultRawFree()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_pystats.h" // _Py_PrintSpecializationStats()
#include "pycore_structseq.h" // _PyStructSequence_InitBuiltinWithFlags()
@ -2724,22 +2724,17 @@ _alloc_preinit_entry(const wchar_t *value)
/* To get this to work, we have to initialize the runtime implicitly */
_PyRuntime_Initialize();
/* Force default allocator, so we can ensure that it also gets used to
/* Use the default allocator, so we can ensure that it also gets used to
* destroy the linked list in _clear_preinit_entries.
*/
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
_Py_PreInitEntry node = PyMem_RawCalloc(1, sizeof(*node));
_Py_PreInitEntry node = _PyMem_DefaultRawCalloc(1, sizeof(*node));
if (node != NULL) {
node->value = _PyMem_RawWcsdup(value);
node->value = _PyMem_DefaultRawWcsdup(value);
if (node->value == NULL) {
PyMem_RawFree(node);
_PyMem_DefaultRawFree(node);
node = NULL;
};
};
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
return node;
}
@ -2771,15 +2766,12 @@ _clear_preinit_entries(_Py_PreInitEntry *optionlist)
_Py_PreInitEntry current = *optionlist;
*optionlist = NULL;
/* Deallocate the nodes and their contents using the default allocator */
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
while (current != NULL) {
_Py_PreInitEntry next = current->next;
PyMem_RawFree(current->value);
PyMem_RawFree(current);
_PyMem_DefaultRawFree(current->value);
_PyMem_DefaultRawFree(current);
current = next;
}
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
}