]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-40453: Add PyConfig._isolated_subinterpreter (GH-19820)
authorVictor Stinner <vstinner@python.org>
Fri, 1 May 2020 09:33:44 +0000 (11:33 +0200)
committerGitHub <noreply@github.com>
Fri, 1 May 2020 09:33:44 +0000 (11:33 +0200)
An isolated subinterpreter cannot spawn threads, spawn a child
process or call os.fork().

* Add private _Py_NewInterpreter(isolated_subinterpreter) function.
* Add isolated=True keyword-only parameter to
  _xxsubinterpreters.create().
* Allow again os.fork() in "non-isolated" subinterpreters.

14 files changed:
Doc/c-api/init_config.rst
Include/cpython/initconfig.h
Include/cpython/pylifecycle.h
Lib/test/test__xxsubinterpreters.py
Lib/test/test_embed.py
Misc/NEWS.d/next/Library/2020-04-30-22-04-58.bpo-40453.ggz7sl.rst [new file with mode: 0644]
Modules/_posixsubprocess.c
Modules/_threadmodule.c
Modules/_winapi.c
Modules/_xxsubinterpretersmodule.c
Modules/posixmodule.c
Programs/_testembed.c
Python/initconfig.c
Python/pylifecycle.c

index 49507c8bad3ed7bbbe30d6e8168f3858f3ece7a7..fc82c3eb5902482f5060f38567141c1c1ceb118d 100644 (file)
@@ -1004,6 +1004,8 @@ Private provisional API:
 
 * :c:member:`PyConfig._init_main`: if set to 0,
   :c:func:`Py_InitializeFromConfig` stops at the "Core" initialization phase.
+* :c:member:`PyConfig._isolated_interpreter`: if non-zero,
+  disallow threads, subprocesses and fork.
 
 .. c:function:: PyStatus _Py_InitializeMain(void)
 
index 8326c235702bd02ac6fd542c23e22312ea8728f0..df93a5539d48bdee7b313c828c33dd8ab992edeb 100644 (file)
@@ -409,6 +409,10 @@ typedef struct {
 
     /* If equal to 0, stop Python initialization before the "main" phase */
     int _init_main;
+
+    /* If non-zero, disallow threads, subprocesses, and fork.
+       Default: 0. */
+    int _isolated_interpreter;
 } PyConfig;
 
 PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
index a01e9c94f12d75f150336419e561373cd47a7e66..eb523b82e182d8c4018661ed8ea7c9e2e587efb8 100644 (file)
@@ -65,6 +65,8 @@ PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn);
 PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn);
 PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category);
 
+PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(int isolated_subinterpreter);
+
 #ifdef __cplusplus
 }
 #endif
index 80eff19152f15c498dffaffc6896c3ea0b7cfbb9..e17bfde2c2f75accfbf17bbd75652c9c50337d66 100644 (file)
@@ -794,6 +794,7 @@ class RunStringTests(TestBase):
         self.assertEqual(out, 'it worked!')
 
     def test_create_thread(self):
+        subinterp = interpreters.create(isolated=False)
         script, file = _captured_script("""
             import threading
             def f():
@@ -804,7 +805,7 @@ class RunStringTests(TestBase):
             t.join()
             """)
         with file:
-            interpreters.run_string(self.id, script)
+            interpreters.run_string(subinterp, script)
             out = file.read()
 
         self.assertEqual(out, 'it worked!')
index 0bdfae1b7e38730a501d5037d9e4ad1642fa9033..3d60b2f330c628c3a28989a62c7117dc66f6fdd0 100644 (file)
@@ -406,6 +406,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         'check_hash_pycs_mode': 'default',
         'pathconfig_warnings': 1,
         '_init_main': 1,
+        '_isolated_interpreter': 0,
     }
     if MS_WINDOWS:
         CONFIG_COMPAT.update({
@@ -766,6 +767,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
 
             'check_hash_pycs_mode': 'always',
             'pathconfig_warnings': 0,
+
+            '_isolated_interpreter': 1,
         }
         self.check_all_configs("test_init_from_config", config, preconfig,
                                api=API_COMPAT)
diff --git a/Misc/NEWS.d/next/Library/2020-04-30-22-04-58.bpo-40453.ggz7sl.rst b/Misc/NEWS.d/next/Library/2020-04-30-22-04-58.bpo-40453.ggz7sl.rst
new file mode 100644 (file)
index 0000000..f20c666
--- /dev/null
@@ -0,0 +1,3 @@
+Add ``isolated=True`` keyword-only parameter to
+``_xxsubinterpreters.create()``. An isolated subinterpreter cannot spawn
+threads, spawn a child process or call ``os.fork()``.
index 60dd78d92a4f524c79390faf5048ae2d93c9ade3..add2962189b1c8807f29479cb2a4a16a4352ae0a 100644 (file)
@@ -663,6 +663,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
         return NULL;
     }
 
+    PyInterpreterState *interp = PyInterpreterState_Get();
+    const PyConfig *config = _PyInterpreterState_GetConfig(interp);
+    if (config->_isolated_interpreter) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "subprocess not supported for isolated subinterpreters");
+        return NULL;
+    }
+
     /* We need to call gc.disable() when we'll be calling preexec_fn */
     if (preexec_fn != Py_None) {
         PyObject *result;
index b3d90b22c5a665b75b4bf1d0fb9cb05aaa347ab2..77baba4847897b08c89b67ce00fb9d349d18ba52 100644 (file)
@@ -1085,6 +1085,14 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
                         "optional 3rd arg must be a dictionary");
         return NULL;
     }
+
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    if (interp->config._isolated_interpreter) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "thread is not supported for isolated subinterpreters");
+        return NULL;
+    }
+
     boot = PyMem_NEW(struct bootstate, 1);
     if (boot == NULL)
         return PyErr_NoMemory();
index 1b28adb0b39838027325d965743021e3673baa91..e1672c478522e874e4560fed10828a8b4a4212db 100644 (file)
@@ -1080,6 +1080,14 @@ _winapi_CreateProcess_impl(PyObject *module,
         return NULL;
     }
 
+    PyInterpreterState *interp = PyInterpreterState_Get();
+    const PyConfig *config = _PyInterpreterState_GetConfig(interp);
+    if (config->_isolated_interpreter) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "subprocess not supported for isolated subinterpreters");
+        return NULL;
+    }
+
     ZeroMemory(&si, sizeof(si));
     si.StartupInfo.cb = sizeof(si);
 
index 15e80559ec6f60fda120df0ff7d95e43ff6ab791..de11c090870f94a29f1cb8bddbefd3b91ba57864 100644 (file)
@@ -1999,16 +1999,20 @@ _global_channels(void) {
 }
 
 static PyObject *
-interp_create(PyObject *self, PyObject *args)
+interp_create(PyObject *self, PyObject *args, PyObject *kwds)
 {
-    if (!PyArg_UnpackTuple(args, "create", 0, 0)) {
+
+    static char *kwlist[] = {"isolated", NULL};
+    int isolated = 1;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|$i:create", kwlist,
+                                     &isolated)) {
         return NULL;
     }
 
     // Create and initialize the new interpreter.
     PyThreadState *save_tstate = PyThreadState_Swap(NULL);
     // XXX Possible GILState issues?
-    PyThreadState *tstate = Py_NewInterpreter();
+    PyThreadState *tstate = _Py_NewInterpreter(isolated);
     PyThreadState_Swap(save_tstate);
     if (tstate == NULL) {
         /* Since no new thread state was created, there is no exception to
@@ -2547,8 +2551,8 @@ channel__channel_id(PyObject *self, PyObject *args, PyObject *kwds)
 }
 
 static PyMethodDef module_functions[] = {
-    {"create",                    (PyCFunction)interp_create,
-     METH_VARARGS, create_doc},
+    {"create",                    (PyCFunction)(void(*)(void))interp_create,
+     METH_VARARGS | METH_KEYWORDS, create_doc},
     {"destroy",                   (PyCFunction)(void(*)(void))interp_destroy,
      METH_VARARGS | METH_KEYWORDS, destroy_doc},
     {"list_all",                  interp_list_all,
index 3d3f6ac969926ff3535080191d05050f17453849..0163b0757aefa5838f8e691b909483bf42312c80 100644 (file)
@@ -6243,9 +6243,10 @@ os_fork_impl(PyObject *module)
 /*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/
 {
     pid_t pid;
-
-    if (_PyInterpreterState_GET() != PyInterpreterState_Main()) {
-        PyErr_SetString(PyExc_RuntimeError, "fork not supported for subinterpreters");
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    if (interp->config._isolated_interpreter) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "fork not supported for isolated subinterpreters");
         return NULL;
     }
     if (PySys_Audit("os.fork", NULL) < 0) {
index 2cf0d71b470bf3a626aad6b5f944f2af04bb5a4e..5c83678f650d0bfbfe650392a1fad34c2250af96 100644 (file)
@@ -603,6 +603,8 @@ static int test_init_from_config(void)
     Py_FrozenFlag = 0;
     config.pathconfig_warnings = 0;
 
+    config._isolated_interpreter = 1;
+
     init_from_config_clear(&config);
 
     dump_config();
index 58cca562f336dc2845d1bdb5695ea74b684d97ac..185935c05fb286d106fbd47b9ba6b6b6c9d168e2 100644 (file)
@@ -632,6 +632,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
     config->check_hash_pycs_mode = NULL;
     config->pathconfig_warnings = -1;
     config->_init_main = 1;
+    config->_isolated_interpreter = 0;
 #ifdef MS_WINDOWS
     config->legacy_windows_stdio = -1;
 #endif
@@ -850,6 +851,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
     COPY_WSTR_ATTR(check_hash_pycs_mode);
     COPY_ATTR(pathconfig_warnings);
     COPY_ATTR(_init_main);
+    COPY_ATTR(_isolated_interpreter);
 
 #undef COPY_ATTR
 #undef COPY_WSTR_ATTR
@@ -949,6 +951,7 @@ config_as_dict(const PyConfig *config)
     SET_ITEM_WSTR(check_hash_pycs_mode);
     SET_ITEM_INT(pathconfig_warnings);
     SET_ITEM_INT(_init_main);
+    SET_ITEM_INT(_isolated_interpreter);
 
     return dict;
 
index 7909cdbf5b77204dd5958eb44e36119ea5b0e98d..5726a559cfcb7399a13bb9d4cae13be0c3024747 100644 (file)
@@ -1526,7 +1526,7 @@ Py_Finalize(void)
 */
 
 static PyStatus
-new_interpreter(PyThreadState **tstate_p)
+new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter)
 {
     PyStatus status;
 
@@ -1573,6 +1573,7 @@ new_interpreter(PyThreadState **tstate_p)
     if (_PyStatus_EXCEPTION(status)) {
         goto error;
     }
+    interp->config._isolated_interpreter = isolated_subinterpreter;
 
     status = pycore_interp_init(tstate);
     if (_PyStatus_EXCEPTION(status)) {
@@ -1606,10 +1607,10 @@ error:
 }
 
 PyThreadState *
-Py_NewInterpreter(void)
+_Py_NewInterpreter(int isolated_subinterpreter)
 {
     PyThreadState *tstate = NULL;
-    PyStatus status = new_interpreter(&tstate);
+    PyStatus status = new_interpreter(&tstate, isolated_subinterpreter);
     if (_PyStatus_EXCEPTION(status)) {
         Py_ExitStatusException(status);
     }
@@ -1617,6 +1618,12 @@ Py_NewInterpreter(void)
 
 }
 
+PyThreadState *
+Py_NewInterpreter(void)
+{
+    return _Py_NewInterpreter(0);
+}
+
 /* Delete an interpreter and its last thread.  This requires that the
    given thread state is current, that the thread has no remaining
    frames, and that it is its interpreter's only remaining thread.