Skip to content

Commit 0047447

Browse files
gh-95185: Check recursion depth in the AST constructor (#95186)
Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 3c94d33 commit 0047447

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
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_ast.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,27 @@ def next(self):
793793
return self
794794
enum._test_simple_enum(_Precedence, ast._Precedence)
795795

796+
@support.cpython_only
797+
def test_ast_recursion_limit(self):
798+
fail_depth = sys.getrecursionlimit() * 3
799+
crash_depth = sys.getrecursionlimit() * 300
800+
success_depth = int(fail_depth * 0.75)
801+
802+
def check_limit(prefix, repeated):
803+
expect_ok = prefix + repeated * success_depth
804+
ast.parse(expect_ok)
805+
for depth in (fail_depth, crash_depth):
806+
broken = prefix + repeated * depth
807+
details = "Compiling ({!r} + {!r} * {})".format(
808+
prefix, repeated, depth)
809+
with self.assertRaises(RecursionError, msg=details):
810+
ast.parse(broken)
811+
812+
check_limit("a", "()")
813+
check_limit("a", ".b")
814+
check_limit("a", "[0]")
815+
check_limit("a", "*a")
816+
796817

797818
class ASTHelpers_Test(unittest.TestCase):
798819
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
@@ -1112,6 +1112,8 @@ def visitModule(self, mod):
11121112
for dfn in mod.dfns:
11131113
self.visit(dfn)
11141114
self.file.write(textwrap.dedent('''
1115+
state->recursion_depth = 0;
1116+
state->recursion_limit = 0;
11151117
state->initialized = 1;
11161118
return 1;
11171119
}
@@ -1259,8 +1261,14 @@ def func_begin(self, name):
12591261
self.emit('if (!o) {', 1)
12601262
self.emit("Py_RETURN_NONE;", 2)
12611263
self.emit("}", 1)
1264+
self.emit("if (++state->recursion_depth > state->recursion_limit) {", 1)
1265+
self.emit("PyErr_SetString(PyExc_RecursionError,", 2)
1266+
self.emit('"maximum recursion depth exceeded during ast construction");', 3)
1267+
self.emit("return 0;", 2)
1268+
self.emit("}", 1)
12621269

12631270
def func_end(self):
1271+
self.emit("state->recursion_depth--;", 1)
12641272
self.emit("return result;", 1)
12651273
self.emit("failed:", 0)
12661274
self.emit("Py_XDECREF(value);", 1)
@@ -1371,7 +1379,32 @@ class PartingShots(StaticVisitor):
13711379
if (state == NULL) {
13721380
return NULL;
13731381
}
1374-
return ast2obj_mod(state, t);
1382+
1383+
int recursion_limit = Py_GetRecursionLimit();
1384+
int starting_recursion_depth;
1385+
/* Be careful here to prevent overflow. */
1386+
int COMPILER_STACK_FRAME_SCALE = 3;
1387+
PyThreadState *tstate = _PyThreadState_GET();
1388+
if (!tstate) {
1389+
return 0;
1390+
}
1391+
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
1392+
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
1393+
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
1394+
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
1395+
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
1396+
state->recursion_depth = starting_recursion_depth;
1397+
1398+
PyObject *result = ast2obj_mod(state, t);
1399+
1400+
/* Check that the recursion depth counting balanced correctly */
1401+
if (result && state->recursion_depth != starting_recursion_depth) {
1402+
PyErr_Format(PyExc_SystemError,
1403+
"AST constructor recursion depth mismatch (before=%d, after=%d)",
1404+
starting_recursion_depth, state->recursion_depth);
1405+
return 0;
1406+
}
1407+
return result;
13751408
}
13761409
13771410
/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */
@@ -1437,6 +1470,8 @@ def visit(self, object):
14371470
def generate_ast_state(module_state, f):
14381471
f.write('struct ast_state {\n')
14391472
f.write(' int initialized;\n')
1473+
f.write(' int recursion_depth;\n')
1474+
f.write(' int recursion_limit;\n')
14401475
for s in module_state:
14411476
f.write(' PyObject *' + s + ';\n')
14421477
f.write('};')

0 commit comments

Comments
 (0)