Skip to content

Commit 72ab890

Browse files
[3.15] gh-159288: Avoid segfault by adding missing recursion checks when walking lazy import chains (GH-159289) (#159416)
gh-159288: Avoid segfault by adding missing recursion checks when walking lazy import chains (GH-159289) Add missing recursion checks when walking lazy import chains `lazy import a.b.c as d` nests one placeholder per name component. lazy_import_path() and lazy_import_replay_from() recursed over that chain without a recursion check, so a very long dotted name could overflow the C stack. Raise RecursionError instead, as the rest of lazy import resolution does. (cherry picked from commit 2588362) Co-authored-by: Brittany Reynoso <breynoso@meta.com>
1 parent e9d9673 commit 72ab890

3 files changed

Lines changed: 24 additions & 0 deletions

File tree

‎Lib/test/test_lazy_import/__init__.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,19 @@ def test_lazy_import_type_attribute_error_message(self):
316316
""")
317317
assert_python_ok("-c", code)
318318

319+
@support.run_with_limited_c_stack(depth=10_000, size=256 * 1024)
320+
def test_deeply_dotted_name(self):
321+
"""A long dotted name must not overflow the C stack."""
322+
m = types.SimpleNamespace()
323+
# Every lookup in m.m.m... succeeds, so resolve() only fails if the
324+
# C stack runs out.
325+
m.m = m
326+
ns = {"__builtins__": {"__lazy_import__": __lazy_import__,
327+
"__import__": lambda *args: m}}
328+
exec("lazy import " + ".".join(["m"] * 10_000) + " as x", ns)
329+
with contextlib.suppress(RecursionError):
330+
ns["x"].resolve()
331+
319332

320333
class SyntaxRestrictionTests(LazyImportTestCase):
321334
"""Tests for syntax restrictions on lazy imports."""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash when walking a very long chain of lazy import placeholders, as
2+
created by an aliased dotted import such as ``lazy import a.b.c as d``.
3+
:exc:`RecursionError` is now raised instead.

‎Objects/lazyimportobject.c‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,11 @@ static PyObject *
204204
lazy_import_path(PyLazyImportObject *m)
205205
{
206206
if (PyLazyImport_CheckExact(m->lz_from)) {
207+
if (_Py_EnterRecursiveCall(" while getting the name of a lazy import")) {
208+
return NULL;
209+
}
207210
PyObject *base = lazy_import_path((PyLazyImportObject *)m->lz_from);
211+
_Py_LeaveRecursiveCall();
208212
if (base == NULL) {
209213
return NULL;
210214
}
@@ -277,8 +281,12 @@ lazy_import_replay_from(PyThreadState *tstate, PyObject *mod,
277281
if (!PyLazyImport_CheckExact(lz->lz_from)) {
278282
return Py_NewRef(mod);
279283
}
284+
if (_Py_EnterRecursiveCallTstate(tstate, " while resolving a lazy import")) {
285+
return NULL;
286+
}
280287
PyObject *from = lazy_import_replay_from(
281288
tstate, mod, (PyLazyImportObject *)lz->lz_from);
289+
_Py_LeaveRecursiveCallTstate(tstate);
282290
if (from == NULL) {
283291
return NULL;
284292
}

0 commit comments

Comments
 (0)