Skip to content

Commit eddc8c0

Browse files
authored
gh-116738: Make pwd module thread-safe (#136695)
Make the pwd module functions getpwuid(), getpwnam(), and getpwall() thread-safe. These changes apply to scenarios where the GIL is disabled or in subinterpreter use cases.
1 parent 22af5d3 commit eddc8c0

File tree

5 files changed

+66
-15
lines changed

5 files changed

+66
-15
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import unittest
2+
3+
from test.support import threading_helper
4+
from test.support.threading_helper import run_concurrently
5+
6+
from test import test_pwd
7+
8+
9+
NTHREADS = 10
10+
11+
12+
@threading_helper.requires_working_threading()
13+
class TestPwd(unittest.TestCase):
14+
def setUp(self):
15+
self.test_pwd = test_pwd.PwdTest()
16+
17+
def test_racing_test_values(self):
18+
# test_pwd.test_values() calls pwd.getpwall() and checks the entries
19+
run_concurrently(
20+
worker_func=self.test_pwd.test_values, nthreads=NTHREADS
21+
)
22+
23+
def test_racing_test_values_extended(self):
24+
# test_pwd.test_values_extended() calls pwd.getpwall(), pwd.getpwnam(),
25+
# pwd.getpwduid() and checks the entries
26+
run_concurrently(
27+
worker_func=self.test_pwd.test_values_extended,
28+
nthreads=NTHREADS,
29+
)
30+
31+
32+
if __name__ == "__main__":
33+
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make functions in :mod:`pwd` thread-safe on the :term:`free threaded <free threading>` build.

Modules/clinic/pwdmodule.c.h

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/pwdmodule.c

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11

22
/* UNIX password file access module */
33

4-
// Need limited C API version 3.13 for PyMem_RawRealloc()
5-
#include "pyconfig.h" // Py_GIL_DISABLED
6-
#ifndef Py_GIL_DISABLED
7-
# define Py_LIMITED_API 0x030d0000
8-
#endif
9-
104
#include "Python.h"
115
#include "posixmodule.h"
126

@@ -69,6 +63,11 @@ get_pwd_state(PyObject *module)
6963

7064
static struct PyModuleDef pwdmodule;
7165

66+
/* Mutex to protect calls to getpwuid(), getpwnam(), and getpwent().
67+
* These functions return pointer to static data structure, which
68+
* may be overwritten by any subsequent calls. */
69+
static PyMutex pwd_db_mutex = {0};
70+
7271
#define DEFAULT_BUFFER_SIZE 1024
7372

7473
static PyObject *
@@ -182,9 +181,15 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)
182181

183182
Py_END_ALLOW_THREADS
184183
#else
184+
PyMutex_Lock(&pwd_db_mutex);
185+
// The getpwuid() function is not required to be thread-safe.
186+
// https://pubs.opengroup.org/onlinepubs/009604499/functions/getpwuid.html
185187
p = getpwuid(uid);
186188
#endif
187189
if (p == NULL) {
190+
#ifndef HAVE_GETPWUID_R
191+
PyMutex_Unlock(&pwd_db_mutex);
192+
#endif
188193
PyMem_RawFree(buf);
189194
if (nomem == 1) {
190195
return PyErr_NoMemory();
@@ -200,6 +205,8 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)
200205
retval = mkpwent(module, p);
201206
#ifdef HAVE_GETPWUID_R
202207
PyMem_RawFree(buf);
208+
#else
209+
PyMutex_Unlock(&pwd_db_mutex);
203210
#endif
204211
return retval;
205212
}
@@ -265,9 +272,15 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)
265272

266273
Py_END_ALLOW_THREADS
267274
#else
275+
PyMutex_Lock(&pwd_db_mutex);
276+
// The getpwnam() function is not required to be thread-safe.
277+
// https://pubs.opengroup.org/onlinepubs/009604599/functions/getpwnam.html
268278
p = getpwnam(name_chars);
269279
#endif
270280
if (p == NULL) {
281+
#ifndef HAVE_GETPWNAM_R
282+
PyMutex_Unlock(&pwd_db_mutex);
283+
#endif
271284
if (nomem == 1) {
272285
PyErr_NoMemory();
273286
}
@@ -278,6 +291,9 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)
278291
goto out;
279292
}
280293
retval = mkpwent(module, p);
294+
#ifndef HAVE_GETPWNAM_R
295+
PyMutex_Unlock(&pwd_db_mutex);
296+
#endif
281297
out:
282298
PyMem_RawFree(buf);
283299
Py_DECREF(bytes);
@@ -302,12 +318,12 @@ pwd_getpwall_impl(PyObject *module)
302318
if ((d = PyList_New(0)) == NULL)
303319
return NULL;
304320

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

322338
done:
323339
endpwent();
324-
#ifdef Py_GIL_DISABLED
325-
PyMutex_Unlock(&getpwall_mutex);
326-
#endif
340+
PyMutex_Unlock(&pwd_db_mutex);
327341
if (failure) {
328342
Py_XDECREF(v);
329343
Py_CLEAR(d);

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ Python/sysmodule.c - _preinit_xoptions -
168168
Modules/faulthandler.c faulthandler_dump_traceback reentrant -
169169
Modules/faulthandler.c faulthandler_dump_c_stack reentrant -
170170
Modules/grpmodule.c - group_db_mutex -
171+
Modules/pwdmodule.c - pwd_db_mutex -
171172
Python/pylifecycle.c _Py_FatalErrorFormat reentrant -
172173
Python/pylifecycle.c fatal_error reentrant -
173174

0 commit comments

Comments
 (0)