Skip to content
Closed
6 changes: 6 additions & 0 deletions Lib/posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@ def realpath(filename, *, strict=False):
# the same links.
seen = {}

# How many symlinks can still be read (excluding caching)
remaining_symlinks = 40 # TODO: use limit set by OS

while rest:
name = rest.pop()
if name is None:
Expand Down Expand Up @@ -483,7 +486,10 @@ def realpath(filename, *, strict=False):
os.stat(newpath)
path = newpath
continue
if remaining_symlinks <= 0 and strict:
raise OSError(errno.ELOOP, "Too many symbolic links", newpath)
target = os.readlink(newpath)
remaining_symlinks -= 1
except OSError:
if strict:
raise
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,29 @@ def test_realpath_unreadable_symlink(self):
os.chmod(ABSTFN, 0o755, follow_symlinks=False)
os.unlink(ABSTFN)

@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
def test_realpath_too_many_symlinks(self):
depth = 40 + 1 # TODO: use limit set by OS + 1
try:
os.mkdir(ABSTFN)
os.symlink('.', f'{ABSTFN}/1')
for i in range(1, depth):
os.symlink(f'{i}'), f'{ABSTFN}/{i + 1}')
self.assertEqual(realpath(f'{ABSTFN}/{depth}'), ABSTFN)
with self.assertRaises(OSError):
realpath(f'{ABSTFN}/{depth}', strict=True)

# Test using relative path as well.
with os_helper.change_cwd(ABSTFN):
self.assertEqual(realpath(f'{depth}'), ABSTFN)
with self.assertRaises(OSError):
realpath(f'{depth}', strict=True)
finally:
for i in range(1, depth + 1):
os_helper.unlink(f'{ABSTFN}/{i}')
safe_rmdir(ABSTFN)

def test_relpath(self):
(real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
try:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`os.path.realpath` now raises :exc:`OSError` when *strict* mode is enabled and a path with too many symlinks is supplied.