Skip to content

Commit 8da22af

Browse files
committed
gh-151416: Fix use-after-free in os.spawnv/spawnve when __fspath__ mutates argv
The argv conversion loops passed references borrowed from the argv list into fsconvert_strdup(). An item's __fspath__() can mutate the list and release its reference to the item, leaving the converter operating on a freed object. A shrunk list could also make PyList_GetItem() return NULL, which PyUnicode_FS{Converter,Decoder}() treat as a request to release an uninitialized output variable. Hold a strong reference to each item across the conversion, matching parse_arglist() and parse_envlist().
1 parent d986124 commit 8da22af

2 files changed

Lines changed: 27 additions & 11 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash in :func:`os.spawnv` and :func:`os.spawnve` when an *argv*
2+
item's :meth:`~os.PathLike.__fspath__` method mutates the *argv* list
3+
during argument conversion.

Modules/posixmodule.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8192,18 +8192,15 @@ os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv)
81928192
int i;
81938193
Py_ssize_t argc;
81948194
intptr_t spawnval;
8195-
PyObject *(*getitem)(PyObject *, Py_ssize_t);
81968195

81978196
/* spawnv has three arguments: (mode, path, argv), where
81988197
argv is a list or tuple of strings. */
81998198

82008199
if (PyList_Check(argv)) {
82018200
argc = PyList_Size(argv);
8202-
getitem = PyList_GetItem;
82038201
}
82048202
else if (PyTuple_Check(argv)) {
82058203
argc = PyTuple_Size(argv);
8206-
getitem = PyTuple_GetItem;
82078204
}
82088205
else {
82098206
PyErr_SetString(PyExc_TypeError,
@@ -8221,14 +8218,24 @@ os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv)
82218218
return PyErr_NoMemory();
82228219
}
82238220
for (i = 0; i < argc; i++) {
8224-
if (!fsconvert_strdup((*getitem)(argv, i),
8225-
&argvlist[i])) {
8221+
// The item must be a strong reference because of possible
8222+
// side-effects of PyUnicode_FS{Converter,Decoder}() in
8223+
// fsconvert_strdup(): an item's __fspath__() can mutate a list
8224+
// *argv*, releasing the list's reference to the item (gh-151416).
8225+
PyObject *item = PySequence_ITEM(argv, i);
8226+
if (item == NULL) {
8227+
free_string_array(argvlist, i);
8228+
return NULL;
8229+
}
8230+
if (!fsconvert_strdup(item, &argvlist[i])) {
8231+
Py_DECREF(item);
82268232
free_string_array(argvlist, i);
82278233
PyErr_SetString(
82288234
PyExc_TypeError,
82298235
"spawnv() arg 2 must contain only strings");
82308236
return NULL;
82318237
}
8238+
Py_DECREF(item);
82328239
if (i == 0 && !argvlist[0][0]) {
82338240
free_string_array(argvlist, i + 1);
82348241
PyErr_SetString(
@@ -8299,7 +8306,6 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv,
82998306
PyObject *res = NULL;
83008307
Py_ssize_t argc, i, envc;
83018308
intptr_t spawnval;
8302-
PyObject *(*getitem)(PyObject *, Py_ssize_t);
83038309
Py_ssize_t lastarg = 0;
83048310

83058311
/* spawnve has four arguments: (mode, path, argv, env), where
@@ -8308,11 +8314,9 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv,
83088314

83098315
if (PyList_Check(argv)) {
83108316
argc = PyList_Size(argv);
8311-
getitem = PyList_GetItem;
83128317
}
83138318
else if (PyTuple_Check(argv)) {
83148319
argc = PyTuple_Size(argv);
8315-
getitem = PyTuple_GetItem;
83168320
}
83178321
else {
83188322
PyErr_SetString(PyExc_TypeError,
@@ -8336,12 +8340,21 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv,
83368340
goto fail_0;
83378341
}
83388342
for (i = 0; i < argc; i++) {
8339-
if (!fsconvert_strdup((*getitem)(argv, i),
8340-
&argvlist[i]))
8341-
{
8343+
// The item must be a strong reference because of possible
8344+
// side-effects of PyUnicode_FS{Converter,Decoder}() in
8345+
// fsconvert_strdup(): an item's __fspath__() can mutate a list
8346+
// *argv*, releasing the list's reference to the item (gh-151416).
8347+
PyObject *item = PySequence_ITEM(argv, i);
8348+
if (item == NULL) {
83428349
lastarg = i;
83438350
goto fail_1;
83448351
}
8352+
if (!fsconvert_strdup(item, &argvlist[i])) {
8353+
Py_DECREF(item);
8354+
lastarg = i;
8355+
goto fail_1;
8356+
}
8357+
Py_DECREF(item);
83458358
if (i == 0 && !argvlist[0][0]) {
83468359
lastarg = i + 1;
83478360
PyErr_SetString(

0 commit comments

Comments
 (0)