bpo-25532: Protect against infinite loops in inspect.unwrap() (#1717) · python/cpython@f9169ce (original) (raw)

3 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -505,13 +505,16 @@ def _is_wrapper(f):
505 505 def _is_wrapper(f):
506 506 return hasattr(f, '__wrapped__') and not stop(f)
507 507 f = func # remember the original func for error reporting
508 -memo = {id(f)} # Memoise by id to tolerate non-hashable objects
508 +# Memoise by id to tolerate non-hashable objects, but store objects to
509 +# ensure they aren't destroyed, which would allow their IDs to be reused.
510 +memo = {id(f): f}
511 +recursion_limit = sys.getrecursionlimit()
509 512 while _is_wrapper(func):
510 513 func = func.__wrapped__
511 514 id_func = id(func)
512 -if id_func in memo:
515 +if (id_func in memo) or (len(memo) >= recursion_limit):
513 516 raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
514 -memo.add(id_func)
517 +memo[id_func] = func
515 518 return func
516 519
517 520 # -------------------------------------------------- source code extraction
Original file line number Diff line number Diff line change
@@ -3554,6 +3554,19 @@ def test_builtins_have_signatures(self):
3554 3554 self.assertIsNone(obj.__text_signature__)
3555 3555
3556 3556
3557 +class NTimesUnwrappable:
3558 +def __init__(self, n):
3559 +self.n = n
3560 +self._next = None
3561 +
3562 +@property
3563 +def __wrapped__(self):
3564 +if self.n <= 0:
3565 +raise Exception("Unwrapped too many times")
3566 +if self._next is None:
3567 +self._next = NTimesUnwrappable(self.n - 1)
3568 +return self._next
3569 +
3557 3570 class TestUnwrap(unittest.TestCase):
3558 3571
3559 3572 def test_unwrap_one(self):
@@ -3609,6 +3622,11 @@ class C:
3609 3622 __wrapped__ = func
3610 3623 self.assertIsNone(inspect.unwrap(C()))
3611 3624
3625 +def test_recursion_limit(self):
3626 +obj = NTimesUnwrappable(sys.getrecursionlimit() + 1)
3627 +with self.assertRaisesRegex(ValueError, 'wrapper loop'):
3628 +inspect.unwrap(obj)
3629 +
3612 3630 class TestMain(unittest.TestCase):
3613 3631 def test_only_source(self):
3614 3632 module = importlib.import_module('unittest')
Original file line number Diff line number Diff line change
@@ -995,6 +995,10 @@ Library
995 995 - Issue #29581: ABCMeta.__new__ now accepts ``**kwargs``, allowing abstract base
996 996 classes to use keyword parameters in __init_subclass__. Patch by Nate Soares.
997 997
998 +- Issue #25532: inspect.unwrap() will now only try to unwrap an object
999 + sys.getrecursionlimit() times, to protect against objects which create a new
1000 + object on every attribute access.
1001 +
998 1002 Windows
999 1003 -------
1000 1004