From: Sam Gross Date: Wed, 24 Dec 2025 13:02:19 +0000 (-0500) Subject: gh-143100: Fix memcpy data race in setobject.c (gh-143127) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e8e044eda343b4b3dd7a7e532c88c2c97242000d;p=thirdparty%2FPython%2Fcpython.git gh-143100: Fix memcpy data race in setobject.c (gh-143127) --- diff --git a/Objects/setobject.c b/Objects/setobject.c index 5f868c858273..55e30fe2cdd8 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1420,6 +1420,17 @@ set_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return make_new_set(type, NULL); } +#ifdef Py_GIL_DISABLED +static void +copy_small_table(setentry *dest, setentry *src) +{ + for (Py_ssize_t i = 0; i < PySet_MINSIZE; i++) { + _Py_atomic_store_ptr_release(&dest[i].key, src[i].key); + _Py_atomic_store_ssize_relaxed(&dest[i].hash, src[i].hash); + } +} +#endif + /* set_swap_bodies() switches the contents of any two sets by moving their internal data pointers and, if needed, copying the internal smalltables. Semantically equivalent to: @@ -1462,8 +1473,13 @@ set_swap_bodies(PySetObject *a, PySetObject *b) if (a_table == a->smalltable || b_table == b->smalltable) { memcpy(tab, a->smalltable, sizeof(tab)); +#ifndef Py_GIL_DISABLED memcpy(a->smalltable, b->smalltable, sizeof(tab)); memcpy(b->smalltable, tab, sizeof(tab)); +#else + copy_small_table(a->smalltable, b->smalltable); + copy_small_table(b->smalltable, tab); +#endif } if (PyType_IsSubtype(Py_TYPE(a), &PyFrozenSet_Type) && diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index f05e0ded9865..a3e1e54284f0 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -20,6 +20,3 @@ thread:pthread_create # PyObject_Realloc internally does memcpy which isn't atomic so can race # with non-locking reads. See #132070 race:PyObject_Realloc - -# gh-143100: set_swap_bodies in setobject.c calls memcpy, which isn't atomic -race:set_swap_bodies