Repository navigation
ntpath.realpath() mishandles filenames that resemble drives #102475
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error3.11only security fixesonly security fixes3.10 (EOL)end of lifeend of life3.12only security fixesonly security fixes
on Mar 6, 2023 - added a commit that references this issue
on Mar 6, 2023 Any ideas on how to go about fixing this one? Keen to work on it but it seems (with regards to the linked issue) there are other things related to those to sort out first?
Thanks for taking a look!
Essentially:
ntpath.realpath()should not callntpath.join()internally. Instead, it should use a more naive form of joining, likesep.join()or simplyhead + sep + tail. Care must be taken not to double up slashes. A small utility function may be warranted.I suspect the main problematic case is here:
Line 697 in ccb5af7
tail = join(name, tail) if tail else name ... but there are other usages of
join()which will need some thought.Reacted by Kostya Farber and Serhiy StorchakaThanks for the detailed explanation. I'll have a go at it.
Reacted by Barney GaleAny ideas on how to go about fixing this one?
Define a custom
join()function inside ofntpath._getfinalpathname_nonstrict(). It can be simplified since it only joins two paths, the second of which is a simple relative path. For example:if isinstance(path, bytes): tail = b'' sep = b'\\' else: tail = '' sep = '\\' def join(path, tail): if path[-1:] == sep or not tail: return path + tail return path + sep + tail
Use this function for all of the four
join()calls in_getfinalpathname_nonstrict().Reacted by Barney Gale and Kostya FarberUse this function for all of the four
join()calls in_getfinalpathname_nonstrict().I said four
join()calls instead of 3 because I was counting the following as well:Lines 695 to 696 in 4d1f033
if path and not name: return path + tail The working directory on a non-existing drive should be the root path. For example, the following result is wrong:
>>> ntpath.exists('Z:') False >>> ntpath.realpath('Z:spam') 'Z:spam'
Eryk, is that function necessary in all four cases? I wonder if we can guarantee that
pathdoes or does not end with a separator in any of them.Any ideas on how to go about fixing this one?
Define a custom
join()function inside ofntpath._getfinalpathname_nonstrict(). It can be simplified since it only joins two paths, the second of which is a simple relative path. For example:if isinstance(path, bytes): tail = b'' sep = b'\\' else: tail = '' sep = '\\' def join(path, tail): if path[-1:] == sep or not tail: return path + tail return path + sep + tail
Use this function for all of the four
join()calls in_getfinalpathname_nonstrict().You've done all the work for me!
The issue also needs tests in "Lib/test_ntpath.py".
is that function necessary in all four cases? I wonder if we can guarantee that path does or does not end with a separator in any of them.
The result from
nt._getfinalpathname()ornt.readlink()may or may not end with a separator. For prepending totail, sincenamecomes fromntpath.split(), it should never contain a backslash. However,tailcould be empty, so we would needtail = (name + sep + tail) if tail else name, as opposed totail = join(name, tail). I'd rather keep the check for an emptytailinjoin().Reacted by Barney GaleIs os.path.join just wrong?
Is there a proper definition of a "canonical" path for Windows?
If not should the Python community create a definition? It will have to takes account of namespaces, drive letters, mount points, case sensitivity etc .
I think that one rule should be that If
p2is the resultos.path.realpath( p1 )then if a thing exists or can be created using pathp2it will be the same as that created / accessed via pathp1.I also think that os.path.realpath( 'c:/aa.b' ) should give a similar result to os.path.realpath( 'c:/a.b' )
p.s. see also some of the comments on issue #100162
2 remaining items
- added3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes3.16new features, bugs and security fixesnew features, bugs and security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directoryand removed3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixes
on Aug 8, 2026 - added a commit that references this issue
on Aug 29, 2026
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
The
realpath()docs say:Note the word "appended". In fact,
realpath()usesos.path.join()to join the path segments, and as we all know/love,os.path.join()supports resetting the drive or root, thus discarding prior parts. As a result:Linked PRs