Stupid cmd.exe tricks: Entering a directory that doesn’t exist, then immediately leaving

I discovered that cmd.exe lets you enter a nonexistent directory, as long as you leave it before anybody notices.

rem acts like cd C:\Windows
cd C:\doesnt-exist\..\Windows

rem acts like cd C:\Windows
cd C:\really\doesnt-exist\..\..\Windows

rem acts like type C:\Windows\win.ini
type C:\doesnt-exist\..\Windows\win.ini


This is handy if you have a full path to a file on the clipboard and you want to access the parent directory. For example, to chdir into the parent directory, you can type cd, a space, and then paste the full path, and then append \.. and hit Enter.

rem suppose clipboard contains C:\directory\with\file.txt
rem The next line acts like cd C:\directory\with
cd C:\directory\with\file.txt\..


This trick works because cmd.exe does some path simplification before calling into the file system. It sees the .. and says, "Oh, I can do that myself!" and uses it to counteract the previous directory. The previous directory is never accessed, so the command processor doesn't notice that it never existed.

Tags

1. ACM-er says:

just tried on BASH, and it doesn’t work :(

1. Kevin says:

That’s unsurprising. On Unix, foo/bar/.. and foo/ can be entirely distinct directories, if bar is a symbolic link to somewhere else.

1. Technically, that’s also the case in Windows, since C:\foo\bar could be a symbolic link to D:\baz\doh. The difference is that on FAT/NTFS/ReFS, . and .. aren’t real directory entries and have implicit behavior, whereas on Linux they are hard links I believe. So C:\foo\bar\saw\..\.. will still resolve as C:\foo in this case.

1. Peter Doubleday says:

I’m probably wrong on this. But it isn’t really anything to do with hard links, soft links, symlinks, or anything. It’s just to do with how the shell (and in Linux, by extension, the libraries) deal with a path.

As Raymond says, the Windows CMD shell chooses to ignore the possibility that C:/foo/bar might actually be a symlink to D:/baz (in which case, “..” would take you back to D:) and simply goes back and forth across the path graph you give it. Which, arguably, is a more intuitive way of handling a path-name with “magic” redirects that behave differently, depending upon whether links of any kind are involved. And of course, arguably, it isn’t …

But as long as the libraries work the same way as the shell, I don’t really care, in either case. It’s fair to say though that, if you are building say X-Windows from scratch, and the make file is full of gibberish like “../.././foo/bar/../shenanigans/../video” you are going to have a hard time figuring out which particular hard or soft link caused your build to fail, three hours in. (Jamie Zawinski used to complain about this.)

1. ranta says:

Common Lisp does not overload the meaning of “..”; instead, it defines the :UP and :BACK keywords for the semantic and syntactic parent directories. Implementations do not have to support both, though. See CLHS §19.2.2.4.3 “Restrictions on Examining a Pathname Directory Component”.

2. Joshua says:

Nope. They’re implicitly resolved on Linux. Try pointing .. somewhere else with a disk editor. It will be ignored. Source: I wrote a Linux fs driver for my Master’s.

2. How does this interact with traverse checking? Is that being bypassed technically? https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc739389(v=ws.10)

1. florian says:

Path name canonicalization can go without directory access, i.e. also for GetFullPathName().

Unfortunately, the feature explained in this article does not seem to work with path name auto-completion …

2. Nothing was traversed at all, so there was nothing to bypass. Traversal checking bypass doesn’t let you traverse into something that doesn’t exist.

3. ig says:

Is it actually a cmd.exe trick? (i.e. is it really cmd.exe which does that path simplification?)

I mean – sure, you can use it in cmd.exe, but (similarly to the previous one – using forward slashes instead of backslashes) it’s something that Win32 API supports – you can call CreateFile(“C:\\doesnt-exist\\..\\Windows\win.ini”, …) and you get a valid handle.
You can call SetCurrentDirectory(“C:\\doesnt-exist\\..\\Windows”) and C:\Windows becomes the process’s current directory.

1. Aged .Net Guy says:

I agree it’s more widespread than just cmd.exe. On e.g. Win8 open a fresh Notepad, type some gibberish, then do a [Save as]. In the file name textbox enter a valid absolute folder path followed by “\_nonexistent directory_\..\newfilename.txt”. It saves just fine in the “parent” of _nonexistent directory_ .

It’s certainly possible that the same path simplification logic was built into various MS tools. IMO it’s far more likely that the path canonicalization is implemented just once deeper in the file system. For security if nothing else it seems to me one would want to canonicalize = sanitize potentially malicious input before starting to walk the path into the actual device folder and ACL hierarchy.

1. The Save As dialog can’t use GetFullPathName because it needs to resolve relative paths against something different from the current directory. Instead, it uses PathCombine, which happens to implement the same algorithm. So yes, the same path simplification logic exists in multiple places.

2. rbmm says:

of course that is not related to cmd at all. this is generic win32 path conversions rules. in concrete case (cd in cmd) conversion was in GetFullPathNameW api. simply example:
WCHAR path[MAX_PATH];
GetFullPathNameW(L”y:\\xxx\\..\\zzz”, RTL_NUMBER_OF(path), path, 0);
// y:\\zzz in exit
also the same conversions will be inside RtlDosPathNameToNtPathName_U[_WithStatus]

1. It may be that GetFullPathNameW has this same feature, but cmd.exe doesn’t use GetFullPathNameW, as I recall. But it’s been a while since I last checked. Maybe they use it now.

1. rbmm says:

no, cmd.exe always (i check from xp up to win10) use GetFullPathNameW inside int ChangeDirectory(PCWSTR, CHANGE_OP) and exactly in this function was path conversion (https://i.imgur.com/Eo0w07C.png – win10, https://i.imgur.com/3WBeOui.png – xp). but more frequently conversion was inside RtlDosPathNameToNtPathName_U[_WithStatus] api

also, sorry as side note to canceling a thread pool callback series (only just view this, but comments here already closed) – exist much more simply and clean solution here – TpSetCallbackFinalizationCallback function for set own FinalizationCallback and free memory(object) associated with PTP_TIMER exactly in this callback. as result code will be look like

VOID NTAPI FinalizationCallback(PTP_CALLBACK_INSTANCE /*Instance*/, PVOID Context)
{
delete reinterpret_cast(Context);
}

if (TimerContext* ctx = new TimerContext)
{
TP_CALLBACK_ENVIRON cbe;
TpInitializeCallbackEnviron(&cbe);
TpSetCallbackFinalizationCallback(&cbe, FinalizationCallback);

if (PTP_TIMER pti = CreateThreadpoolTimer(OnTimer, ctx, &cbe))
{
//..
}
else
{
delete ctx;
}
}

4. Joker_vD says:

Obligatory comment about Plan 9, the OS that managed to have this trick (“foo/bar/../” is exactly the same as “foo/”) in the FS with symbolic links of a sort, and without performance penalties. Source: Rob Pike, “Lexical File Names in Plan 9 or Getting Dot-Dot Right”.

5. Rich Shealer says:

Is there a contract for this behavior or is it an implementation detail that may change?

1. I would consider it a (handy) implementation detail. Okay for interactive use, but I wouldn’t use it programmatically.

6. Someone says:

I read Project Zero’s explanation of this: