8000 [3.10] gh-95185: Check recursion depth in the AST constructor (GH-95186) · python/cpython@eabe3cf · GitHub
[go: up one dir, main page]

Skip to content

Commit eabe3cf

Browse files
committed
[3.10] gh-95185: Check recursion depth in the AST constructor (GH-95186)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>. (cherry picked from commit 0047447) Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
1 parent 6d332a6 commit eabe3cf

File tree

5 files changed

+168
-2
lines changed

5 files changed

+168
-2
lines changed

Include/internal/pycore_ast_state.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ extern "C" {
1212

1313
struct ast_state {
1414
int initialized;
15+
int recursion_depth;
16+
int recursion_limit;
1517
PyObject *AST_type;
1618
PyObject *Add_singleton;
1719
PyObject *Add_type;

Lib/test/test_ast.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,27 @@ def test_constant_as_name(self):
711711
with self.assertRaisesRegex(ValueError, f"identifier field can't represent '{constant}' constant"):
712712
compile(expr, "<test>", "eval")
713713

714+
@support.cpython_only
715+
def test_ast_recursion_limit(self):
716+
fail_depth = sys.getrecursionlimit() * 3
717+
crash_depth = sys.getrecursionlimit() * 300
718+
success_depth = int(fail_depth * 0.75)
719+
720+
def check_limit(prefix, repeated):
721+
expect_ok = prefix + repeated * success_depth
722+
ast.parse(expect_ok)
723+
for depth in (fail_depth, crash_depth):
724+
broken = prefix + repeated * depth
725+
details = "Compiling ({!r} + {!r} * {})".format(
726+
prefix, repeated, depth)
727+
with self.assertRaises(RecursionError, msg=details):
728+
ast.parse(broken)
729+
730+
check_limit("a", "()")
731+
check_limit("a", ".b")
732+
check_limit("a", "[0]")
733+
check_limit("a", "*a")
734+
714735

715736
class ASTHelpers_Test(unittest.TestCase):
716737
maxDiff = None
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Prevented crashes in the AST constructor when compiling some absurdly long
2+
expressions like ``"+0"*1000000``. :exc:`RecursionError` is now raised
3+
instead. Patch by Pablo Galindo

Parser/asdl_c.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,8 @@ def visitModule(self, mod):
10521052
for dfn in mod.dfns:
10531053
self.visit(dfn)
10541054
self.file.write(textwrap.dedent('''
1055+
state->recursion_depth = 0;
1056+
state->recursion_limit = 0;
10551057
state->initialized = 1;
10561058
return 1;
10571059
}
@@ -1203,8 +1205,14 @@ def func_begin(self, name):
12031205
self.emit('if (!o) {', 1)
12041206
self.emit("Py_RETURN_NONE;", 2)
12051207
self.emit("}", 1)
1208+
self.emit("if (++state->recursion_depth > state->recursion_limit) {", 1)
1209+
self.emit("PyErr_SetString(PyExc_RecursionError,", 2)
1210+
self.emit('"maximum recursion depth exceeded during ast construction");', 3)
1211+
self.emit("return 0;", 2)
1212+
self.emit("}", 1)
12061213

12071214
def func_end(self):
1215+
self.emit("state->recursion_depth--;", 1)
12081216
self.emit("return result;", 1)
12091217
self.emit("failed:", 0)
12101218
self.emit("Py_XDECREF(value);", 1)
@@ -1310,7 +1318,32 @@ class PartingShots(StaticVisitor):
13101318
if (state == NULL) {
13111319
return NULL;
13121320
}
1313-
return ast2obj_mod(state, t);
1321+
1322+
int recursion_limit = Py_GetRecursionLimit();
1323+
int starting_recursion_depth;
1324+
/* Be careful here to prevent overflow. */
1325+
int COMPILER_STACK_FRAME_SCALE = 3;
1326+
PyThreadState *tstate = _PyThreadState_GET();
1327+
if (!tstate) {
1328+
return 0;
1329+
}
1330+
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
1331+
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
1332+
1333+
starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
1334+
tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
1335+
state->recursion_depth = starting_recursion_depth;
1336+
1337+
PyObject *result = ast2obj_mod(state, t);
1338+
1339+
/* Check that the recursion depth counting balanced correctly */
1340+
if (result && state->recursion_depth != starting_recursion_depth) {
1341+
PyErr_Format(PyExc_SystemError,
1342+
"AST constructor recursion depth mismatch (before=%d, after=%d)",
1343+
starting_recursion_depth, state->recursion_depth);
1344+
return 0;
1345+
}
1346+
return result;
13141347
}
13151348
13161349
/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */
@@ -1374,6 +1407,8 @@ def visit(self, object):
13741407
def generate_ast_state(module_state, f):
13751408
f.write('struct ast_state {\n')
13761409
f.write(' int initialized;\n')
1410+
f.write(' int recursion_depth;\n')
1411+
f.write(' int recursion_limit;\n')
13771412
for s in module_state:
13781413
f.write(' PyObject *' + s + 3E23 ';\n')
13791414
f.write('};')

0 commit comments

Comments
 (0)
0