From 78c9cde8743cc012b6105648e420b267e00f0f85 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Thu, 17 Apr 2025 10:00:29 +1200 Subject: [PATCH 1/6] gh-127081: lock non-re-entrant *pwent calls The libc setpwent, getpwent, and endpwent functions are not thread-safe. Protect them with mutexs in free-threading builds. --- ...025-04-21-00-58-04.gh-issue-127081.3DCl92.rst | 2 ++ Modules/pwdmodule.c | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst diff --git a/Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst b/Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst new file mode 100644 index 00000000000000..a99669a1bc021a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst @@ -0,0 +1,2 @@ +Fix libc thread safety issues with :mod:`pwd` by locking access to +``getpwall``. diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index 2240e2078b2d98..dfcd9eb2d863ac 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -301,18 +301,28 @@ pwd_getpwall_impl(PyObject *module) struct passwd *p; if ((d = PyList_New(0)) == NULL) return NULL; + +#ifdef Py_GIL_DISABLED + static PyMutex getpwall_mutex = {0}; + PyMutex_Lock(&getpwall_mutex); +#endif setpwent(); + while ((p = getpwent()) != NULL) { PyObject *v = mkpwent(module, p); if (v == NULL || PyList_Append(d, v) != 0) { Py_XDECREF(v); - Py_DECREF(d); - endpwent(); - return NULL; + Py_CLEAR(d); + goto done; } Py_DECREF(v); } + +done: endpwent(); +#ifdef Py_GIL_DISABLED + PyMutex_Unlock(&getpwall_mutex); +#endif return d; } #endif From d30a13969f930e9eabc23f1cb91f8120070c42ca Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Mon, 21 Apr 2025 11:00:34 +1200 Subject: [PATCH 2/6] Defer dec-refs on created entries until outside mutex to avoid possibility of re-entrancy. --- Modules/pwdmodule.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index dfcd9eb2d863ac..f170a8265d6463 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -9,6 +9,7 @@ #include "Python.h" #include "posixmodule.h" +#include "listobject.h" // PyList_Size/PyList_GetItem #include // ERANGE #include // getpwuid() @@ -306,16 +307,18 @@ pwd_getpwall_impl(PyObject *module) static PyMutex getpwall_mutex = {0}; PyMutex_Lock(&getpwall_mutex); #endif + int failure = 0; + PyObject *orphan = NULL; setpwent(); - while ((p = getpwent()) != NULL) { + /* NOTE: Ref counts are not decremented here, as we cannot allow + * re-entrancy while holding the mutex. */ PyObject *v = mkpwent(module, p); if (v == NULL || PyList_Append(d, v) != 0) { - Py_XDECREF(v); - Py_CLEAR(d); + orphan = v; + failure = 1; goto done; } - Py_DECREF(v); } done: @@ -323,6 +326,18 @@ pwd_getpwall_impl(PyObject *module) #ifdef Py_GIL_DISABLED PyMutex_Unlock(&getpwall_mutex); #endif + /* Deferred decref on entries created above and added to the list */ + Py_ssize_t n = PyList_Size(d); + for (Py_ssize_t i = 0; i < n; ++i) { + PyObject *entry = PyList_GetItem(d, i); + Py_DECREF(entry); + } + if (failure) { + /* If there was a failure we might have created an entry but not added + * it: dec-ref that, if it exists, before clearing the list. */ + Py_XDECREF(orphan); + Py_CLEAR(d); + } return d; } #endif From 6b78b2987fb64e44000248e1ac8cbc0727f848d1 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Sun, 27 Apr 2025 10:37:38 +1200 Subject: [PATCH 3/6] Address reviewer feedback --- Modules/pwdmodule.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index f170a8265d6463..ed828aa81b0e2b 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -326,15 +326,12 @@ pwd_getpwall_impl(PyObject *module) #ifdef Py_GIL_DISABLED PyMutex_Unlock(&getpwall_mutex); #endif - /* Deferred decref on entries created above and added to the list */ - Py_ssize_t n = PyList_Size(d); - for (Py_ssize_t i = 0; i < n; ++i) { - PyObject *entry = PyList_GetItem(d, i); - Py_DECREF(entry); + /* Deferred decref on entries created above and added to the list. */ + Py_ssize_t n = PyList_GET_SIZE(d); + while (--n >= 0) { + Py_DECREF(PyList_GET_ITEM(d, n)); } if (failure) { - /* If there was a failure we might have created an entry but not added - * it: dec-ref that, if it exists, before clearing the list. */ Py_XDECREF(orphan); Py_CLEAR(d); } From 33f7feada4ed429f6230c3d6ab32f06e7debf457 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Sun, 27 Apr 2025 11:36:06 +1200 Subject: [PATCH 4/6] Switch back to using functions available in the limited API. --- Modules/pwdmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index ed828aa81b0e2b..25fc2a4b79f6c6 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -327,9 +327,9 @@ pwd_getpwall_impl(PyObject *module) PyMutex_Unlock(&getpwall_mutex); #endif /* Deferred decref on entries created above and added to the list. */ - Py_ssize_t n = PyList_GET_SIZE(d); + Py_ssize_t n = PyList_Size(d); while (--n >= 0) { - Py_DECREF(PyList_GET_ITEM(d, n)); + Py_DECREF(PyList_GetItem(d, n)); } if (failure) { Py_XDECREF(orphan); From 7ca685875c45c680c1bff3d07541ca0e12b4d508 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Thu, 22 May 2025 10:14:13 +1200 Subject: [PATCH 5/6] Once the newly created pwent items are successfully added to the list they are strongly referenced by that, so we can dec-ref them safely. --- Modules/pwdmodule.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index 25fc2a4b79f6c6..30175a61ce6085 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -308,17 +308,16 @@ pwd_getpwall_impl(PyObject *module) PyMutex_Lock(&getpwall_mutex); #endif int failure = 0; - PyObject *orphan = NULL; + PyObject *v = NULL; setpwent(); while ((p = getpwent()) != NULL) { - /* NOTE: Ref counts are not decremented here, as we cannot allow - * re-entrancy while holding the mutex. */ - PyObject *v = mkpwent(module, p); + v = mkpwent(module, p); if (v == NULL || PyList_Append(d, v) != 0) { - orphan = v; + /* NOTE: cannot dec-ref here, while holding the mutex. */ failure = 1; goto done; } + Py_DECREF(v); } done: @@ -326,13 +325,8 @@ pwd_getpwall_impl(PyObject *module) #ifdef Py_GIL_DISABLED PyMutex_Unlock(&getpwall_mutex); #endif - /* Deferred decref on entries created above and added to the list. */ - Py_ssize_t n = PyList_Size(d); - while (--n >= 0) { - Py_DECREF(PyList_GetItem(d, n)); - } if (failure) { - Py_XDECREF(orphan); + Py_XDECREF(v); Py_CLEAR(d); } return d; From 68aa01090e306dbabe6245f5df6ce0e1293dcdb1 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Thu, 22 May 2025 21:33:18 +1200 Subject: [PATCH 6/6] Remove no-longer needed include --- Modules/pwdmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index 30175a61ce6085..c5a8cead19a773 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -9,7 +9,6 @@ #include "Python.h" #include "posixmodule.h" -#include "listobject.h" // PyList_Size/PyList_GetItem #include // ERANGE #include // getpwuid()