Skip to content

Commit

Permalink
[3.11] gh-95185: Check recursion depth in the AST constructor (GH-95186
Browse files Browse the repository at this point in the history
…) (GH-95208)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
(cherry picked from commit 0047447)

Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
  • Loading branch information
miss-islington and pablogsal committed Jul 26, 2022
1 parent 5a9920f commit 86eb500
Show file tree
Hide file tree
Showing 6 changed files with 614 additions and 443 deletions.
887 changes: 446 additions & 441 deletions Doc/data/python3.11.abi

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Include/internal/pycore_ast_state.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions Lib/test/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,27 @@ def next(self):
return self
enum._test_simple_enum(_Precedence, ast._Precedence)

@support.cpython_only
def test_ast_recursion_limit(self):
fail_depth = sys.getrecursionlimit() * 3
crash_depth = sys.getrecursionlimit() * 300
success_depth = int(fail_depth * 0.75)

def check_limit(prefix, repeated):
expect_ok = prefix + repeated * success_depth
ast.parse(expect_ok)
for depth in (fail_depth, crash_depth):
broken = prefix + repeated * depth
details = "Compiling ({!r} + {!r} * {})".format(
prefix, repeated, depth)
with self.assertRaises(RecursionError, msg=details):
ast.parse(broken)

check_limit("a", "()")
check_limit("a", ".b")
check_limit("a", "[0]")
check_limit("a", "*a")


class ASTHelpers_Test(unittest.TestCase):
maxDiff = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Prevented crashes in the AST constructor when compiling some absurdly long
expressions like ``"+0"*1000000``. :exc:`RecursionError` is now raised
instead. Patch by Pablo Galindo
37 changes: 36 additions & 1 deletion Parser/asdl_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,8 @@ def visitModule(self, mod):
for dfn in mod.dfns:
self.visit(dfn)
self.file.write(textwrap.dedent('''
state->recursion_depth = 0;
state->recursion_limit = 0;
state->initialized = 1;
return 1;
}
Expand Down Expand Up @@ -1260,8 +1262,14 @@ def func_begin(self, name):
self.emit('if (!o) {', 1)
self.emit("Py_RETURN_NONE;", 2)
self.emit("}", 1)
self.emit("if (++state->recursion_depth > state->recursion_limit) {", 1)
self.emit("PyErr_SetString(PyExc_RecursionError,", 2)
self.emit('"maximum recursion depth exceeded during ast construction");', 3)
self.emit("return 0;", 2)
self.emit("}", 1)

def func_end(self):
self.emit("state->recursion_depth--;", 1)
self.emit("return result;", 1)
self.emit("failed:", 0)
self.emit("Py_XDECREF(value);", 1)
Expand Down Expand Up @@ -1372,7 +1380,32 @@ class PartingShots(StaticVisitor):
if (state == NULL) {
return NULL;
}
return ast2obj_mod(state, t);
int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth;
/* Be careful here to prevent overflow. */
int COMPILER_STACK_FRAME_SCALE = 3;
PyThreadState *tstate = _PyThreadState_GET();
if (!tstate) {
return 0;
}
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
state->recursion_depth = starting_recursion_depth;
PyObject *result = ast2obj_mod(state, t);
/* Check that the recursion depth counting balanced correctly */
if (result && state->recursion_depth != starting_recursion_depth) {
PyErr_Format(PyExc_SystemError,
"AST constructor recursion depth mismatch (before=%d, after=%d)",
starting_recursion_depth, state->recursion_depth);
return 0;
}
return result;
}
/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */
Expand Down Expand Up @@ -1438,6 +1471,8 @@ def visit(self, object):
def generate_ast_state(module_state, f):
f.write('struct ast_state {\n')
f.write(' int initialized;\n')
f.write(' int recursion_depth;\n')
f.write(' int recursion_limit;\n')
for s in module_state:
f.write(' PyObject *' + s + ';\n')
f.write('};')
Expand Down
Loading

0 comments on commit 86eb500

Please sign in to comment.