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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy