https://github.com/python/cpython/commit/44bdb833d11688524322c4c832e3552adb1aeb3b
commit: 44bdb833d11688524322c4c832e3552adb1aeb3b
branch: 3.13
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2026-01-05T19:56:02+01:00
summary:
[3.13] gh-143309: fix UAF in `os.execve` when the environment is concurrently
mutated (GH-143314) (#143431)
[3.13] gh-143309: fix UAF in `os.execve` when the environment is concurrently
mutated (GH-143314) (#143431)
(cherry picked from commit 9609574e7fd36edfaa8b575558a82cc14e65bfbc)
(cherry picked from commit c99f7667436d8978b4077704333e2a351f2a026f)
files:
A Misc/NEWS.d/next/Library/2025-12-31-20-43-02.gh-issue-143309.cdFxdH.rst
M Lib/test/test_os.py
M Modules/posixmodule.c
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 75bbef019f4c5f..5819447f7892e9 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -2255,6 +2255,45 @@ def test_execve_invalid_env(self):
with self.assertRaises(ValueError):
os.execve(args[0], args, newenv)
+ # See https://github.com/python/cpython/issues/137934 and the other
+ # related issues for the reason why we cannot test this on Windows.
+ @unittest.skipIf(os.name == "nt", "POSIX-specific test")
+ @unittest.skipUnless(unix_shell and os.path.exists(unix_shell),
+ "requires a shell")
+ def test_execve_env_concurrent_mutation_with_fspath_posix(self):
+ # Prevent crash when mutating environment during parsing.
+ # Regression test for https://github.com/python/cpython/issues/143309.
+
+ message = "hello from execve"
+ code = """if 1:
+ import os, sys
+
+ class MyPath:
+ def __fspath__(self):
+ mutated.clear()
+ return b"pwn"
+
+ mutated = KEYS = VALUES = [MyPath()]
+
+ class MyEnv:
+ def __getitem__(self): raise RuntimeError("must not be called")
+ def __len__(self): return 1
+ def keys(self): return KEYS
+ def values(self): return VALUES
+
+ args = [{unix_shell!r}, '-c', 'echo \"{message!s}\"']
+ os.execve(args[0], args, MyEnv())
+ """.format(unix_shell=unix_shell, message=message)
+
+ # Make sure to forward "LD_*" variables so that assert_python_ok()
+ # can run correctly.
+ minimal = {k: v for k, v in os.environ.items() if k.startswith("LD_")}
+ with os_helper.EnvironmentVarGuard() as env:
+ env.clear()
+ env.update(minimal)
+ _, out, _ = assert_python_ok('-c', code, **env)
+ self.assertIn(bytes(message, "ascii"), out)
+
@unittest.skipUnless(sys.platform == "win32", "Win32-specific test")
def test_execve_with_empty_path(self):
# bpo-32890: Check GetLastError() misuse
diff --git
a/Misc/NEWS.d/next/Library/2025-12-31-20-43-02.gh-issue-143309.cdFxdH.rst
b/Misc/NEWS.d/next/Library/2025-12-31-20-43-02.gh-issue-143309.cdFxdH.rst
new file mode 100644
index 00000000000000..5f30ed340bf0eb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-31-20-43-02.gh-issue-143309.cdFxdH.rst
@@ -0,0 +1,3 @@
+Fix a crash in :func:`os.execve` on non-Windows platforms when
+given a custom environment mapping which is then mutated during
+parsing. Patch by Bénédikt Tran.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 08229a33b9e96d..3c8f188d0a9c5a 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -6740,8 +6740,8 @@ 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;
+ PyObject *keys = NULL, *vals = NULL;
+ PyObject *key = NULL, *val = NULL, *key2 = NULL, *val2 = NULL;
EXECV_CHAR **envlist;
i = PyMapping_Size(env);
@@ -6766,20 +6766,22 @@ parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
}
for (pos = 0; pos < i; pos++) {
- PyObject *key = PyList_GetItem(keys, pos); // Borrowed ref.
+ // The 'key' and 'val' must be strong references because of
+ // possible side-effects by PyUnicode_FS{Converter,Decoder}().
+ key = PyList_GetItemRef(keys, pos);
if (key == NULL) {
goto error;
}
- PyObject *val = PyList_GetItem(vals, pos); // Borrowed ref.
+ val = PyList_GetItemRef(vals, pos);
if (val == NULL) {
goto error;
}
#if defined(HAVE_WEXECV) || defined(HAVE_WSPAWNV)
- if (!PyUnicode_FSDecoder(key, &key2))
+ 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
@@ -6788,39 +6790,38 @@ parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
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);
+ PyObject *keyval = PyUnicode_FromFormat("%U=%U", key2, val2);
#else
- if (!PyUnicode_FSConverter(key, &key2))
+ if (!PyUnicode_FSConverter(key, &key2)) {
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));
+ PyObject *keyval = PyBytes_FromFormat("%s=%s", PyBytes_AS_STRING(key2),
+ PyBytes_AS_STRING(val2));
#endif
- Py_DECREF(key2);
- Py_DECREF(val2);
- if (!keyval)
+ if (!keyval) {
goto error;
+ }
if (!fsconvert_strdup(keyval, &envlist[envc++])) {
Py_DECREF(keyval);
goto error;
}
+ Py_CLEAR(key);
+ Py_CLEAR(val);
+ Py_CLEAR(key2);
+ Py_CLEAR(val2);
Py_DECREF(keyval);
}
Py_DECREF(vals);
@@ -6831,6 +6832,10 @@ parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
return envlist;
error:
+ Py_XDECREF(key);
+ Py_XDECREF(val);
+ Py_XDECREF(key2);
+ Py_XDECREF(val2);
Py_XDECREF(keys);
Py_XDECREF(vals);
free_string_array(envlist, envc);
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]