diff --git a/Lib/shutil.py b/Lib/shutil.py index 45cbe4c855b462..d3ac5dc5c50a73 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -940,8 +940,8 @@ def move(src, dst, copy_function=copy2): return real_dst def _destinsrc(src, dst): - src = os.path.abspath(src) - dst = os.path.abspath(dst) + src = os.path.realpath(src) + dst = os.path.realpath(dst) if not src.endswith(os.path.sep): src += os.path.sep if not dst.endswith(os.path.sep): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 13a3487382dfcf..067144cf1cd790 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2914,6 +2914,25 @@ def test_destinsrc_false_positive(self): finally: os_helper.rmtree(TESTFN) + @os_helper.skip_unless_symlink + def test_destinsrc_symlink_bypass(self): + # gh-149835: a symlink component in dst must not allow dst to + # appear outside src in string space while being physically inside. + tmp = self.mkdtemp() + src = os.path.join(tmp, 'src') + os.makedirs(src) + # tmp/link -> tmp (one level up) + link = os.path.join(tmp, 'link') + os.symlink(tmp, link) + # raw path: tmp/link/src/sub - no src prefix in string space + # real path: tmp/src/sub - physically inside src + dst = os.path.join(link, 'src', 'sub') + self.assertTrue( + shutil._destinsrc(src, dst), + msg='_destinsrc failed to detect dst inside src via symlink ' + '(dst=%s, src=%s)' % (dst, src), + ) + @os_helper.skip_unless_symlink @mock_rename def test_move_file_symlink(self): diff --git a/Misc/NEWS.d/next/Library/2026-05-14-17-00-00.gh-issue-149835.kQ8mZ7.rst b/Misc/NEWS.d/next/Library/2026-05-14-17-00-00.gh-issue-149835.kQ8mZ7.rst new file mode 100644 index 00000000000000..dedd3d9c934c0c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-14-17-00-00.gh-issue-149835.kQ8mZ7.rst @@ -0,0 +1,4 @@ +Fix :func:`shutil.move` so that the guard against moving a directory into +itself is not bypassed when the destination contains a symlink component. +:func:`!shutil._destinsrc` now resolves symlinks with :func:`os.path.realpath` +instead of using :func:`os.path.abspath`.