Bug report
Bug description:
I recently read this article "bugs that Rust won't catch" and thought I'd check if CPython has similar issues in shutil, I found this.
shutil.move, in the cross-device move case (if os.rename raises OSError), calls _destinsrc(src, dst) to prevent moving a directory into itself, which would cause copytree to recurse infinitely. The guard compares path strings after os.path.abspath, which normalises e.g. . or .., but does not resolve symlinks. A symlink component anywhere in dst could make the string comparison miss, silently bypassing the guard.
(Note that _destinsrc is only called in the cross-device/cross-filesystem fallback path, when os.rename raises OSError and copytree is used instead.)
To fix this, _destinsrc could be updated to use realpath instead of abspath:
def _destinsrc(src, dst):
src = os.path.realpath(src)
dst = os.path.realpath(dst)
# ...
I have this branch which updates it (with a regression test added).
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
Bug report
Bug description:
I recently read this article "bugs that Rust won't catch" and thought I'd check if CPython has similar issues in
shutil, I found this.shutil.move, in the cross-device move case (ifos.renameraisesOSError), calls_destinsrc(src, dst)to prevent moving a directory into itself, which would causecopytreeto recurse infinitely. The guard compares path strings afteros.path.abspath, which normalises e.g..or.., but does not resolve symlinks. A symlink component anywhere indstcould make the string comparison miss, silently bypassing the guard.(Note that _destinsrc is only called in the cross-device/cross-filesystem fallback path, when
os.renameraisesOSErrorandcopytreeis used instead.)To fix this,
_destinsrccould be updated to userealpathinstead ofabspath:I have this branch which updates it (with a regression test added).
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs