diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index ee73c1c8adaa7b3..f10ef0087903094 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -16,6 +16,11 @@ Operating System Utilities :class:`str` or :class:`bytes` object. Otherwise :exc:`TypeError` is raised and ``NULL`` is returned. + .. note:: + + The caller must hold a :term:`strong reference` to *path* that + its :meth:`~os.PathLike.__fspath__` method cannot release. + .. versionadded:: 3.6 diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index 634dcbce7a57915..a66cf8eed75a495 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -925,6 +925,13 @@ conversion function: If *obj* is ``NULL``, the function releases a strong reference stored in the variable referred by *result* and returns ``1``. + .. note:: + + If *obj* is not a :class:`str` or :class:`bytes` instance, the + conversion calls its :meth:`~os.PathLike.__fspath__` method. The + caller must hold a :term:`strong reference` to *obj* that this + method cannot release. + .. versionadded:: 3.1 .. versionchanged:: 3.6 @@ -952,6 +959,13 @@ conversion function: If *obj* is ``NULL``, release the strong reference to the object referred to by *result* and return ``1``. + .. note:: + + If *obj* is not a :class:`str` or :class:`bytes` instance, the + conversion calls its :meth:`~os.PathLike.__fspath__` method. The + caller must hold a :term:`strong reference` to *obj* that this + method cannot release. + .. versionadded:: 3.2 .. versionchanged:: 3.6 diff --git a/Misc/NEWS.d/next/Library/2026-06-12-00-00-00.gh-issue-151416.spawnUA.rst b/Misc/NEWS.d/next/Library/2026-06-12-00-00-00.gh-issue-151416.spawnUA.rst new file mode 100644 index 000000000000000..4d4df9088747950 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-12-00-00-00.gh-issue-151416.spawnUA.rst @@ -0,0 +1,3 @@ +Fix a crash in :func:`os.spawnv` and :func:`os.spawnve` when an *argv* +item's :meth:`~os.PathLike.__fspath__` method mutates the *argv* list +during argument conversion. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 1f1b7fa729c01c8..a0cd6fd78c7a782 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8192,18 +8192,15 @@ os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv) int i; Py_ssize_t argc; intptr_t spawnval; - PyObject *(*getitem)(PyObject *, Py_ssize_t); /* spawnv has three arguments: (mode, path, argv), where argv is a list or tuple of strings. */ if (PyList_Check(argv)) { argc = PyList_Size(argv); - getitem = PyList_GetItem; } else if (PyTuple_Check(argv)) { argc = PyTuple_Size(argv); - getitem = PyTuple_GetItem; } else { PyErr_SetString(PyExc_TypeError, @@ -8221,14 +8218,24 @@ os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv) return PyErr_NoMemory(); } for (i = 0; i < argc; i++) { - if (!fsconvert_strdup((*getitem)(argv, i), - &argvlist[i])) { + // The item must be a strong reference because of possible + // side-effects of PyUnicode_FS{Converter,Decoder}() in + // fsconvert_strdup(): an item's __fspath__() can mutate a list + // *argv*, releasing the list's reference to the item (gh-151416). + PyObject *item = PySequence_ITEM(argv, i); + if (item == NULL) { + free_string_array(argvlist, i); + return NULL; + } + if (!fsconvert_strdup(item, &argvlist[i])) { + Py_DECREF(item); free_string_array(argvlist, i); PyErr_SetString( PyExc_TypeError, "spawnv() arg 2 must contain only strings"); return NULL; } + Py_DECREF(item); if (i == 0 && !argvlist[0][0]) { free_string_array(argvlist, i + 1); PyErr_SetString( @@ -8299,7 +8306,6 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv, PyObject *res = NULL; Py_ssize_t argc, i, envc; intptr_t spawnval; - PyObject *(*getitem)(PyObject *, Py_ssize_t); Py_ssize_t lastarg = 0; /* 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, if (PyList_Check(argv)) { argc = PyList_Size(argv); - getitem = PyList_GetItem; } else if (PyTuple_Check(argv)) { argc = PyTuple_Size(argv); - getitem = PyTuple_GetItem; } else { PyErr_SetString(PyExc_TypeError, @@ -8336,12 +8340,21 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv, goto fail_0; } for (i = 0; i < argc; i++) { - if (!fsconvert_strdup((*getitem)(argv, i), - &argvlist[i])) - { + // The item must be a strong reference because of possible + // side-effects of PyUnicode_FS{Converter,Decoder}() in + // fsconvert_strdup(): an item's __fspath__() can mutate a list + // *argv*, releasing the list's reference to the item (gh-151416). + PyObject *item = PySequence_ITEM(argv, i); + if (item == NULL) { lastarg = i; goto fail_1; } + if (!fsconvert_strdup(item, &argvlist[i])) { + Py_DECREF(item); + lastarg = i; + goto fail_1; + } + Py_DECREF(item); if (i == 0 && !argvlist[0][0]) { lastarg = i + 1; PyErr_SetString(