bpo-40453: Add PyConfig._isolated_subinterpreter (GH-19820)

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.
This commit is contained in:
Victor Stinner 2020-05-01 11:33:44 +02:00 committed by GitHub
parent 8bcfd31cc0
commit 252346acd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 68 additions and 12 deletions

View File

@ -1004,6 +1004,8 @@ Private provisional API:
* :c:member:`PyConfig._init_main`: if set to 0, * :c:member:`PyConfig._init_main`: if set to 0,
:c:func:`Py_InitializeFromConfig` stops at the "Core" initialization phase. :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) .. c:function:: PyStatus _Py_InitializeMain(void)

View File

@ -409,6 +409,10 @@ typedef struct {
/* If equal to 0, stop Python initialization before the "main" phase */ /* If equal to 0, stop Python initialization before the "main" phase */
int _init_main; int _init_main;
/* If non-zero, disallow threads, subprocesses, and fork.
Default: 0. */
int _isolated_interpreter;
} PyConfig; } PyConfig;
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config); PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);

View File

@ -65,6 +65,8 @@ PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn);
PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn); PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn);
PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category); PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category);
PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(int isolated_subinterpreter);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -794,6 +794,7 @@ class RunStringTests(TestBase):
self.assertEqual(out, 'it worked!') self.assertEqual(out, 'it worked!')
def test_create_thread(self): def test_create_thread(self):
subinterp = interpreters.create(isolated=False)
script, file = _captured_script(""" script, file = _captured_script("""
import threading import threading
def f(): def f():
@ -804,7 +805,7 @@ class RunStringTests(TestBase):
t.join() t.join()
""") """)
with file: with file:
interpreters.run_string(self.id, script) interpreters.run_string(subinterp, script)
out = file.read() out = file.read()
self.assertEqual(out, 'it worked!') self.assertEqual(out, 'it worked!')

View File

@ -406,6 +406,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'check_hash_pycs_mode': 'default', 'check_hash_pycs_mode': 'default',
'pathconfig_warnings': 1, 'pathconfig_warnings': 1,
'_init_main': 1, '_init_main': 1,
'_isolated_interpreter': 0,
} }
if MS_WINDOWS: if MS_WINDOWS:
CONFIG_COMPAT.update({ CONFIG_COMPAT.update({
@ -766,6 +767,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'check_hash_pycs_mode': 'always', 'check_hash_pycs_mode': 'always',
'pathconfig_warnings': 0, 'pathconfig_warnings': 0,
'_isolated_interpreter': 1,
} }
self.check_all_configs("test_init_from_config", config, preconfig, self.check_all_configs("test_init_from_config", config, preconfig,
api=API_COMPAT) api=API_COMPAT)

View File

@ -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()``.

View File

@ -663,6 +663,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
return NULL; 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 */ /* We need to call gc.disable() when we'll be calling preexec_fn */
if (preexec_fn != Py_None) { if (preexec_fn != Py_None) {
PyObject *result; PyObject *result;

View File

@ -1085,6 +1085,14 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
"optional 3rd arg must be a dictionary"); "optional 3rd arg must be a dictionary");
return NULL; 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); boot = PyMem_NEW(struct bootstate, 1);
if (boot == NULL) if (boot == NULL)
return PyErr_NoMemory(); return PyErr_NoMemory();

View File

@ -1080,6 +1080,14 @@ _winapi_CreateProcess_impl(PyObject *module,
return NULL; 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)); ZeroMemory(&si, sizeof(si));
si.StartupInfo.cb = sizeof(si); si.StartupInfo.cb = sizeof(si);

View File

@ -1999,16 +1999,20 @@ _global_channels(void) {
} }
static PyObject * 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; return NULL;
} }
// Create and initialize the new interpreter. // Create and initialize the new interpreter.
PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *save_tstate = PyThreadState_Swap(NULL);
// XXX Possible GILState issues? // XXX Possible GILState issues?
PyThreadState *tstate = Py_NewInterpreter(); PyThreadState *tstate = _Py_NewInterpreter(isolated);
PyThreadState_Swap(save_tstate); PyThreadState_Swap(save_tstate);
if (tstate == NULL) { if (tstate == NULL) {
/* Since no new thread state was created, there is no exception to /* 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[] = { static PyMethodDef module_functions[] = {
{"create", (PyCFunction)interp_create, {"create", (PyCFunction)(void(*)(void))interp_create,
METH_VARARGS, create_doc}, METH_VARARGS | METH_KEYWORDS, create_doc},
{"destroy", (PyCFunction)(void(*)(void))interp_destroy, {"destroy", (PyCFunction)(void(*)(void))interp_destroy,
METH_VARARGS | METH_KEYWORDS, destroy_doc}, METH_VARARGS | METH_KEYWORDS, destroy_doc},
{"list_all", interp_list_all, {"list_all", interp_list_all,

View File

@ -6243,9 +6243,10 @@ os_fork_impl(PyObject *module)
/*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/ /*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/
{ {
pid_t pid; pid_t pid;
PyInterpreterState *interp = _PyInterpreterState_GET();
if (_PyInterpreterState_GET() != PyInterpreterState_Main()) { if (interp->config._isolated_interpreter) {
PyErr_SetString(PyExc_RuntimeError, "fork not supported for subinterpreters"); PyErr_SetString(PyExc_RuntimeError,
"fork not supported for isolated subinterpreters");
return NULL; return NULL;
} }
if (PySys_Audit("os.fork", NULL) < 0) { if (PySys_Audit("os.fork", NULL) < 0) {

View File

@ -603,6 +603,8 @@ static int test_init_from_config(void)
Py_FrozenFlag = 0; Py_FrozenFlag = 0;
config.pathconfig_warnings = 0; config.pathconfig_warnings = 0;
config._isolated_interpreter = 1;
init_from_config_clear(&config); init_from_config_clear(&config);
dump_config(); dump_config();

View File

@ -632,6 +632,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
config->check_hash_pycs_mode = NULL; config->check_hash_pycs_mode = NULL;
config->pathconfig_warnings = -1; config->pathconfig_warnings = -1;
config->_init_main = 1; config->_init_main = 1;
config->_isolated_interpreter = 0;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
config->legacy_windows_stdio = -1; config->legacy_windows_stdio = -1;
#endif #endif
@ -850,6 +851,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
COPY_WSTR_ATTR(check_hash_pycs_mode); COPY_WSTR_ATTR(check_hash_pycs_mode);
COPY_ATTR(pathconfig_warnings); COPY_ATTR(pathconfig_warnings);
COPY_ATTR(_init_main); COPY_ATTR(_init_main);
COPY_ATTR(_isolated_interpreter);
#undef COPY_ATTR #undef COPY_ATTR
#undef COPY_WSTR_ATTR #undef COPY_WSTR_ATTR
@ -949,6 +951,7 @@ config_as_dict(const PyConfig *config)
SET_ITEM_WSTR(check_hash_pycs_mode); SET_ITEM_WSTR(check_hash_pycs_mode);
SET_ITEM_INT(pathconfig_warnings); SET_ITEM_INT(pathconfig_warnings);
SET_ITEM_INT(_init_main); SET_ITEM_INT(_init_main);
SET_ITEM_INT(_isolated_interpreter);
return dict; return dict;

View File

@ -1526,7 +1526,7 @@ Py_Finalize(void)
*/ */
static PyStatus static PyStatus
new_interpreter(PyThreadState **tstate_p) new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter)
{ {
PyStatus status; PyStatus status;
@ -1573,6 +1573,7 @@ new_interpreter(PyThreadState **tstate_p)
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
goto error; goto error;
} }
interp->config._isolated_interpreter = isolated_subinterpreter;
status = pycore_interp_init(tstate); status = pycore_interp_init(tstate);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
@ -1606,10 +1607,10 @@ error:
} }
PyThreadState * PyThreadState *
Py_NewInterpreter(void) _Py_NewInterpreter(int isolated_subinterpreter)
{ {
PyThreadState *tstate = NULL; PyThreadState *tstate = NULL;
PyStatus status = new_interpreter(&tstate); PyStatus status = new_interpreter(&tstate, isolated_subinterpreter);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
Py_ExitStatusException(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 /* Delete an interpreter and its last thread. This requires that the
given thread state is current, that the thread has no remaining given thread state is current, that the thread has no remaining
frames, and that it is its interpreter's only remaining thread. frames, and that it is its interpreter's only remaining thread.