Skip to content

Commit 9609574

Browse files
authored
pythongh-143309: fix UAF in os.execve when the environment is concurrently mutated (python#143314)
1 parent 6c53af1 commit 9609574

File tree

3 files changed

+60
-18
lines changed

3 files changed

+60
-18
lines changed

Lib/test/test_os/test_os.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2624,6 +2624,40 @@ def test_execve_invalid_env(self):
26242624
with self.assertRaises(ValueError):
26252625
os.execve(args[0], args, newenv)
26262626

2627+
# See https://github.com/python/cpython/issues/137934 and the other
2628+
# related issues for the reason why we cannot test this on Windows.
2629+
@unittest.skipIf(os.name == "nt", "POSIX-specific test")
2630+
def test_execve_env_concurrent_mutation_with_fspath_posix(self):
2631+
# Prevent crash when mutating environment during parsing.
2632+
# Regression test for https://github.com/python/cpython/issues/143309.
2633+
2634+
message = "hello from execve"
2635+
code = """
2636+
import os, sys
2637+
2638+
class MyPath:
2639+
def __fspath__(self):
2640+
mutated.clear()
2641+
return b"pwn"
2642+
2643+
mutated = KEYS = VALUES = [MyPath()]
2644+
2645+
class MyEnv:
2646+
def __getitem__(self): raise RuntimeError("must not be called")
2647+
def __len__(self): return 1
2648+
def keys(self): return KEYS
2649+
def values(self): return VALUES
2650+
2651+
args = [sys.executable, '-c', "print({message!r})"]
2652+
os.execve(args[0], args, MyEnv())
2653+
""".format(message=message)
2654+
2655+
# Use '__cleanenv' to signal to assert_python_ok() not
2656+
# to do a copy of os.environ on its own.
2657+
rc, out, _ = assert_python_ok('-c', code, __cleanenv=True)
2658+
self.assertEqual(rc, 0)
2659+
self.assertIn(bytes(message, "ascii"), out)
2660+
26272661
@unittest.skipUnless(sys.platform == "win32", "Win32-specific test")
26282662
def test_execve_with_empty_path(self):
26292663
# bpo-32890: Check GetLastError() misuse
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.execve` on non-Windows platforms when
2+
given a custom environment mapping which is then mutated during
3+
parsing. Patch by Bénédikt Tran.

Modules/posixmodule.c

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7291,8 +7291,8 @@ static EXECV_CHAR**
72917291
parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
72927292
{
72937293
Py_ssize_t i, pos, envc;
7294-
PyObject *keys=NULL, *vals=NULL;
7295-
PyObject *key2, *val2, *keyval;
7294+
PyObject *keys = NULL, *vals = NULL;
7295+
PyObject *key = NULL, *val = NULL, *key2 = NULL, *val2 = NULL;
72967296
EXECV_CHAR **envlist;
72977297

72987298
i = PyMapping_Size(env);
@@ -7317,20 +7317,22 @@ parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
73177317
}
73187318

73197319
for (pos = 0; pos < i; pos++) {
7320-
PyObject *key = PyList_GetItem(keys, pos); // Borrowed ref.
7320+
// The 'key' and 'val' must be strong references because of
7321+
// possible side-effects by PyUnicode_FS{Converter,Decoder}().
7322+
key = PyList_GetItemRef(keys, pos);
73217323
if (key == NULL) {
73227324
goto error;
73237325
}
7324-
PyObject *val = PyList_GetItem(vals, pos); // Borrowed ref.
7326+
val = PyList_GetItemRef(vals, pos);
73257327
if (val == NULL) {
73267328
goto error;
73277329
}
73287330

73297331
#if defined(HAVE_WEXECV) || defined(HAVE_WSPAWNV)
7330-
if (!PyUnicode_FSDecoder(key, &key2))
7332+
if (!PyUnicode_FSDecoder(key, &key2)) {
73317333
goto error;
7334+
}
73327335
if (!PyUnicode_FSDecoder(val, &val2)) {
7333-
Py_DECREF(key2);
73347336
goto error;
73357337
}
73367338
/* Search from index 1 because on Windows starting '=' is allowed for
@@ -7339,39 +7341,38 @@ parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
73397341
PyUnicode_FindChar(key2, '=', 1, PyUnicode_GET_LENGTH(key2), 1) != -1)
73407342
{
73417343
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
7342-
Py_DECREF(key2);
7343-
Py_DECREF(val2);
73447344
goto error;
73457345
}
7346-
keyval = PyUnicode_FromFormat("%U=%U", key2, val2);
7346+
PyObject *keyval = PyUnicode_FromFormat("%U=%U", key2, val2);
73477347
#else
7348-
if (!PyUnicode_FSConverter(key, &key2))
7348+
if (!PyUnicode_FSConverter(key, &key2)) {
73497349
goto error;
7350+
}
73507351
if (!PyUnicode_FSConverter(val, &val2)) {
7351-
Py_DECREF(key2);
73527352
goto error;
73537353
}
73547354
if (PyBytes_GET_SIZE(key2) == 0 ||
73557355
strchr(PyBytes_AS_STRING(key2) + 1, '=') != NULL)
73567356
{
73577357
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
7358-
Py_DECREF(key2);
7359-
Py_DECREF(val2);
73607358
goto error;
73617359
}
7362-
keyval = PyBytes_FromFormat("%s=%s", PyBytes_AS_STRING(key2),
7363-
PyBytes_AS_STRING(val2));
7360+
PyObject *keyval = PyBytes_FromFormat("%s=%s", PyBytes_AS_STRING(key2),
7361+
PyBytes_AS_STRING(val2));
73647362
#endif
7365-
Py_DECREF(key2);
7366-
Py_DECREF(val2);
7367-
if (!keyval)
7363+
if (!keyval) {
73687364
goto error;
7365+
}
73697366

73707367
if (!fsconvert_strdup(keyval, &envlist[envc++])) {
73717368
Py_DECREF(keyval);
73727369
goto error;
73737370
}
73747371

7372+
Py_CLEAR(key);
7373+
Py_CLEAR(val);
7374+
Py_CLEAR(key2);
7375+
Py_CLEAR(val2);
73757376
Py_DECREF(keyval);
73767377
}
73777378
Py_DECREF(vals);
@@ -7382,6 +7383,10 @@ parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
73827383
return envlist;
73837384

73847385
error:
7386+
Py_XDECREF(key);
7387+
Py_XDECREF(val);
7388+
Py_XDECREF(key2);
7389+
Py_XDECREF(val2);
73857390
Py_XDECREF(keys);
73867391
Py_XDECREF(vals);
73877392
free_string_array(envlist, envc);

0 commit comments

Comments
 (0)