Skip to content

gh-116738: Make pwd module thread-safe #136695

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 17, 2025
Merged
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
33 changes: 33 additions & 0 deletions Lib/test/test_free_threading/test_pwd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import unittest

from test.support import threading_helper
from test.support.threading_helper import run_concurrently

from test import test_pwd


NTHREADS = 10


@threading_helper.requires_working_threading()
class TestPwd(unittest.TestCase):
def setUp(self):
self.test_pwd = test_pwd.PwdTest()

def test_racing_test_values(self):
# test_pwd.test_values() calls pwd.getpwall() and checks the entries
run_concurrently(
worker_func=self.test_pwd.test_values, nthreads=NTHREADS
)

def test_racing_test_values_extended(self):
# test_pwd.test_values_extended() calls pwd.getpwall(), pwd.getpwnam(),
# pwd.getpwduid() and checks the entries
run_concurrently(
worker_func=self.test_pwd.test_values_extended,
nthreads=NTHREADS,
)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make functions in :mod:`pwd` thread-safe on the :term:`free threaded <free threading>` build.
6 changes: 4 additions & 2 deletions Modules/clinic/pwdmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 27 additions & 13 deletions Modules/pwdmodule.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@

/* UNIX password file access module */

// Need limited C API version 3.13 for PyMem_RawRealloc()
#include "pyconfig.h" // Py_GIL_DISABLED
#ifndef Py_GIL_DISABLED
# define Py_LIMITED_API 0x030d0000
#endif

#include "Python.h"
#include "posixmodule.h"

Expand Down Expand Up @@ -69,6 +63,11 @@ get_pwd_state(PyObject *module)

static struct PyModuleDef pwdmodule;

/* Mutex to protect calls to getpwuid(), getpwnam(), and getpwent().
* These functions return pointer to static data structure, which
* may be overwritten by any subsequent calls. */
static PyMutex pwd_db_mutex = {0};

#define DEFAULT_BUFFER_SIZE 1024

static PyObject *
Expand Down Expand Up @@ -182,9 +181,15 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)

Py_END_ALLOW_THREADS
#else
PyMutex_Lock(&pwd_db_mutex);
// The getpwuid() function is not required to be thread-safe.
// https://pubs.opengroup.org/onlinepubs/009604499/functions/getpwuid.html
p = getpwuid(uid);
#endif
if (p == NULL) {
#ifndef HAVE_GETPWUID_R
PyMutex_Unlock(&pwd_db_mutex);
#endif
PyMem_RawFree(buf);
if (nomem == 1) {
return PyErr_NoMemory();
Expand All @@ -200,6 +205,8 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)
retval = mkpwent(module, p);
#ifdef HAVE_GETPWUID_R
PyMem_RawFree(buf);
#else
PyMutex_Unlock(&pwd_db_mutex);
#endif
return retval;
}
Expand Down Expand Up @@ -265,9 +272,15 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)

Py_END_ALLOW_THREADS
#else
PyMutex_Lock(&pwd_db_mutex);
// The getpwnam() function is not required to be thread-safe.
// https://pubs.opengroup.org/onlinepubs/009604599/functions/getpwnam.html
p = getpwnam(name_chars);
#endif
if (p == NULL) {
#ifndef HAVE_GETPWNAM_R
PyMutex_Unlock(&pwd_db_mutex);
#endif
if (nomem == 1) {
PyErr_NoMemory();
}
Expand All @@ -278,6 +291,9 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)
goto out;
}
retval = mkpwent(module, p);
#ifndef HAVE_GETPWNAM_R
PyMutex_Unlock(&pwd_db_mutex);
#endif
out:
PyMem_RawFree(buf);
Py_DECREF(bytes);
Expand All @@ -302,12 +318,12 @@ pwd_getpwall_impl(PyObject *module)
if ((d = PyList_New(0)) == NULL)
return NULL;

#ifdef Py_GIL_DISABLED
static PyMutex getpwall_mutex = {0};
PyMutex_Lock(&getpwall_mutex);
#endif
PyMutex_Lock(&pwd_db_mutex);
int failure = 0;
PyObject *v = NULL;
// The setpwent(), getpwent() and endpwent() functions are not required to
// be thread-safe.
// https://pubs.opengroup.org/onlinepubs/009696799/functions/setpwent.html
setpwent();
while ((p = getpwent()) != NULL) {
v = mkpwent(module, p);
Expand All @@ -321,9 +337,7 @@ pwd_getpwall_impl(PyObject *module)

done:
endpwent();
#ifdef Py_GIL_DISABLED
PyMutex_Unlock(&getpwall_mutex);
#endif
PyMutex_Unlock(&pwd_db_mutex);
if (failure) {
Py_XDECREF(v);
Py_CLEAR(d);
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 @@ -168,6 +168,7 @@ Python/sysmodule.c - _preinit_xoptions -
Modules/faulthandler.c faulthandler_dump_traceback reentrant -
Modules/faulthandler.c faulthandler_dump_c_stack reentrant -
Modules/grpmodule.c - group_db_mutex -
Modules/pwdmodule.c - pwd_db_mutex -
Python/pylifecycle.c _Py_FatalErrorFormat reentrant -
Python/pylifecycle.c fatal_error reentrant -

Expand Down
Loading