/* Buggy Re-entrant Path */
static PyObject *
os_execve_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *env)
/*[clinic end generated code: output=ff9fa8e4da8bde58 input=626804fa092606d9]*/
{
/* ... */
envlist = parse_envlist(env, &envc);
if (envlist == NULL)
goto fail_0;
/* ... */
return NULL;
}
static EXECV_CHAR**
parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
{
Py_ssize_t i, pos, envc;
PyObject *keys=NULL, *vals=NULL;
PyObject *key2, *val2, *keyval;
EXECV_CHAR **envlist;
i = PyMapping_Size(env);
if (i < 0)
return NULL;
envlist = PyMem_NEW(EXECV_CHAR *, i + 1);
if (envlist == NULL) {
PyErr_NoMemory();
return NULL;
}
envc = 0;
keys = PyMapping_Keys(env);
if (!keys)
goto error;
vals = PyMapping_Values(env);
if (!vals)
goto error;
if (!PyList_Check(keys) || !PyList_Check(vals)) {
PyErr_Format(PyExc_TypeError,
"env.keys() or env.values() is not a list");
goto error;
}
for (pos = 0; pos < i; pos++) {
PyObject *key = PyList_GetItem(keys, pos); /* crashing pointer derived */
if (key == NULL) {
goto error;
}
PyObject *val = PyList_GetItem(vals, pos);
if (val == NULL) {
goto error;
}
#if defined(HAVE_WEXECV) || defined(HAVE_WSPAWNV)
if (!PyUnicode_FSDecoder(key, &key2))
goto error;
if (!PyUnicode_FSDecoder(val, &val2)) {
Py_DECREF(key2);
goto error;
}
/* Search from index 1 because on Windows starting '=' is allowed for
defining hidden environment variables. */
if (PyUnicode_GET_LENGTH(key2) == 0 ||
PyUnicode_FindChar(key2, '=', 1, PyUnicode_GET_LENGTH(key2), 1) != -1)
{
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
Py_DECREF(key2);
Py_DECREF(val2);
goto error;
}
keyval = PyUnicode_FromFormat("%U=%U", key2, val2);
#else
if (!PyUnicode_FSConverter(key, &key2)) /* Reentrant call site */
goto error;
if (!PyUnicode_FSConverter(val, &val2)) {
Py_DECREF(key2);
goto error;
}
if (PyBytes_GET_SIZE(key2) == 0 ||
strchr(PyBytes_AS_STRING(key2) + 1, '=') != NULL)
{
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
Py_DECREF(key2);
Py_DECREF(val2);
goto error;
}
keyval = PyBytes_FromFormat("%s=%s", PyBytes_AS_STRING(key2),
PyBytes_AS_STRING(val2));
#endif
Py_DECREF(key2);
Py_DECREF(val2);
if (!keyval)
goto error;
if (!fsconvert_strdup(keyval, &envlist[envc++])) {
Py_DECREF(keyval);
goto error;
}
Py_DECREF(keyval);
}
Py_DECREF(vals);
Py_DECREF(keys);
envlist[envc] = 0;
*envc_ptr = envc;
return envlist;
error:
Py_XDECREF(keys);
Py_XDECREF(vals);
free_string_array(envlist, envc);
return NULL;
}
PyObject *
PyOS_FSPath(PyObject *path)
{
if (PyUnicode_Check(path) || PyBytes_Check(path)) { /* Crash site */
return Py_NewRef(path);
}
/* ... */
return path_repr;
}
/* Clobbering Path */
static void
list_clear_impl(PyListObject *a, bool is_resize)
{
PyObject **items = a->ob_item;
/* Because XDECREF can recursively invoke operations on this list,
we make it empty first. */
Py_ssize_t i = Py_SIZE(a);
Py_SET_SIZE(a, 0);
FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item, NULL); /* state mutate site */
/* ... */
}
What happened?
In
parse_envlistthe borrowed entries fromPyMapping_KeysandPyMapping_Valuesare processed byPyUnicode_FSConverter, which triggers user__fspath__onPathEntryobjects. The craftedAliasEnvdrops each entry during conversion so the loop keeps a dangling pointer, and the subsequentPyOS_FSPathaccess reuses freed memory leading to a use-after-free.Proof of Concept:
Affected Versions
Details
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20)Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)]Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)]Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)]Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 16:55:18) [Clang 18.1.3 (1ubuntu1)]Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 16:56:16) [Clang 18.1.3 (1ubuntu1)]Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]Vulnerable Code
Details
Sanitizer Output
Details
Linked PRs
os.execvewhen the environment is concurrently mutated #143314os.execvewhen the environment is concurrently mutated (GH-143314) #143398os.execvewhen the environment is concurrently mutated (GH-143314) #143399test_execve_env_concurrent_mutation_with_fspath_posixbuildbot failure #143415test_execve_env_concurrent_mutation_with_fspath_posixbuildbot failure (GH-143415) #143419os.execvewhen the environment is concurrently mutated (GH-143314) #143431