os.path.realpath('loop/../link') does not resolve link #117546
Copy link
Copy link
Closed
Labels
3.12only security fixesonly security fixes3.13only security fixesonly security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
Description
If os.path.realpath() encounters a symlink loop on posix, it prematurely stops querying paths and resolving symlink targets. This differs from coreutils realpath:
$ cd /tmp
$ ln -s loop loop
$ ln -s target link
$ realpath -m loop/../link
/tmp/target
$ python -c 'import os.path; print(os.path.realpath("loop/../link"))'
/tmp/linkIt also differs from how lstat() errors are handled:
$ realpath -m missing/../link
/tmp/target
$ python -c 'import os.path; print(os.path.realpath("missing/../link"))'
/tmp/targetLinked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.12only security fixesonly security fixes3.13only security fixesonly security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
Activity
eryksun commentedon Apr 4, 2024
As Barney noted, this is only an issue on POSIX. The non-strict implementation on Windows ignores a symlink loop to resolve as much as possible. Also, on Windows, for the given examples, the ".." component gets resolved first, which is like the
-L(i.e.--logical) option of coreutilsrealpath. That makesos.path.realpath()on Windows partially non-strict even withstrict=True. Nowadays the system implements this logical path handling in both the user-mode runtime library and the kernel. (In older versions of Windows, the kernel implemented POSIX-like physical path parsing when resolving the target of a relative symlink. For example, for "loop\..\target", it used to resolve "loop" before it resolved "..".)pythonGH-117546: Fix symlink resolution in `os.path.realpath('loop/..…
barneygale commentedon Apr 5, 2024
Thanks Eryk!
I suppose users could do
realpath(normpath(path))to getrealpath --logicalbehaviour on POSIX.encukou commentedon Apr 8, 2024
This behaviour goes back to Python 2. IMO, we should be very careful when changing it. The status quo seems fine.
OTOH, it would be nice if
realpathwas idempotent.I don't feel qualified to review this.
barneygale commentedon Apr 9, 2024
Appreciate the feedback.
Serhiy has separately approved the change, but given your reservations, I won't merge yet. Hopefully another core dev comes along to share their views and perhaps cast the deciding vote (if not I'll summon one in due course).
This bug is pretty rare. To hit it you need to supply a path that:
..segment(s) that walk up past the loop (e.g.loop/.., orloop/blah/../../)The unlikelihood of hitting all three might explain why I couldn't find any existing bug reports, despite the bug going way back :)
encukou commentedon Apr 10, 2024
Serhiy is the os.path expert, he should get the deciding vote. I am careful because I'm not familiar with os.path maintenance :)
I agree that this is an edge case you hopefully won't see in production.
GH-117546: Fix symlink resolution in `os.path.realpath('loop/../link'…
barneygale commentedon Apr 10, 2024
Thanks! Fix merged into
main. I'm not planning to backport because the fix would need to be different (due to #114848) and because the bug is so rare. If anyone would like it backported, please leave a comment and I'll re-open.pythonGH-117546: Fix symlink resolution in `os.path.realpath('loop/..…