diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index f5aef61ec6f7c0..cdae9bfba53004 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1945,6 +1945,22 @@ def visit_Ellipsis(self, node): ]) +class ModuleReplacementTests(unittest.TestCase): + # bpo-41631 + # Check that compile(..., ast.PyCF_ONLY_AST) fails if _ast is tampered with + def test_replaced_ast_module(self): + old_ast = sys.modules['_ast'] + try: + for replacement in None, 'a string', ast: + with self.subTest(replacement=replacement): + sys.modules['_ast'] = replacement + with self.assertRaises(SystemError): + compile('1+1', '', 'eval', + flags=ast.PyCF_ONLY_AST) + finally: + sys.modules['_ast'] = old_ast + + def main(): if __name__ != '__main__': return diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-08-26-22-25-25.bpo-41631.RTN8yW.rst b/Misc/NEWS.d/next/Core and Builtins/2020-08-26-22-25-25.bpo-41631.RTN8yW.rst new file mode 100644 index 00000000000000..acaeb4e2b5768e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-08-26-22-25-25.bpo-41631.RTN8yW.rst @@ -0,0 +1,2 @@ +If the _ast module is unavailable or replaced with an unexpected object, +compiling to AST now raises an exception. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index 6fe44b99f793bb..7f5e5a1a7e60ee 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -1382,6 +1382,8 @@ def generate_module_def(f, mod): return (astmodulestate*)state; } +static struct PyModuleDef _astmodule; + static astmodulestate* get_global_ast_state(void) { @@ -1400,6 +1402,29 @@ def generate_module_def(f, mod): return NULL; } } + if (!PyModule_Check(module)) { + PyErr_SetString( + PyExc_SystemError, + "Non-module object imported as _ast"); + return NULL; + } + +// testing "pegen" requires generating a C extension module, which contains +// a copy of the symbols defined in Python-ast.c and needs to be treated +// as the _ast module. +#ifndef AST_SKIP_MODULE_CHECK + + struct PyModuleDef *def = PyModule_GetDef(module); + if (def != &_astmodule) { + if (!PyErr_Occurred()) { + PyErr_SetString( + PyExc_SystemError, + "Unexpected module imported as _ast"); + } + return NULL; + } +#endif + astmodulestate *state = get_ast_state(module); Py_DECREF(module); return state; diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 396a6832702b35..e78a5d3b8411a1 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -232,6 +232,8 @@ get_ast_state(PyObject *module) return (astmodulestate*)state; } +static struct PyModuleDef _astmodule; + static astmodulestate* get_global_ast_state(void) { @@ -250,6 +252,29 @@ get_global_ast_state(void) return NULL; } } + if (!PyModule_Check(module)) { + PyErr_SetString( + PyExc_SystemError, + "Non-module object imported as _ast"); + return NULL; + } + +// testing "pegen" requires generating a C extension module, which contains +// a copy of the symbols defined in Python-ast.c and needs to be treated +// as the _ast module. +#ifndef AST_SKIP_MODULE_CHECK + + struct PyModuleDef *def = PyModule_GetDef(module); + if (def != &_astmodule) { + if (!PyErr_Occurred()) { + PyErr_SetString( + PyExc_SystemError, + "Unexpected module imported as _ast"); + } + return NULL; + } +#endif + astmodulestate *state = get_ast_state(module); Py_DECREF(module); return state; diff --git a/Tools/peg_generator/pegen/ast_dump.py b/Tools/peg_generator/pegen/ast_dump.py index 93dfbfd963ca6d..6bc5c5de71730f 100644 --- a/Tools/peg_generator/pegen/ast_dump.py +++ b/Tools/peg_generator/pegen/ast_dump.py @@ -3,7 +3,8 @@ because testing pegen requires generating a C extension module, which contains a copy of the symbols defined in Python-ast.c. Thus, the isinstance check would always fail. We rely on string comparison of the base classes instead. -TODO: Remove the above-described hack. +TODO: Remove the above-described hack, along with AST_SKIP_MODULE_CHECK +elsewhere. """ diff --git a/Tools/peg_generator/pegen/build.py b/Tools/peg_generator/pegen/build.py index 9edde372e8d135..b9ce7871bf184d 100644 --- a/Tools/peg_generator/pegen/build.py +++ b/Tools/peg_generator/pegen/build.py @@ -59,6 +59,12 @@ def compile_c_extension( extra_link_args = get_extra_flags("LDFLAGS", "PY_LDFLAGS_NODIST") if keep_asserts: extra_compile_args.append("-UNDEBUG") + + # testing pegen requires generating C extension modules, which contains + # a copy of the symbols defined in Python-ast.c and needs to be treated + # as the _ast module. See ast_dump.py. + extra_compile_args.append("-DAST_SKIP_MODULE_CHECK") + extension = [ Extension( extension_name,