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: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)

View 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);

View 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

View file

@ -794,6 +794,7 @@ def f():
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 @@ def f():
t.join()
""")
with file:
interpreters.run_string(self.id, script)
interpreters.run_string(subinterp, script)
out = file.read()
self.assertEqual(out, 'it worked!')

View 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 @@ def test_init_from_config(self):
'check_hash_pycs_mode': 'always',
'pathconfig_warnings': 0,
'_isolated_interpreter': 1,
}
self.check_all_configs("test_init_from_config", config, preconfig,
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;
}
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;

View 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();

View 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);

View 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,

View 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) {

View 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();

View 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;

View 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 @@ new_interpreter(PyThreadState **tstate_p)
}
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.