-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Insecure behavior in std::process::Command on Windows #87945
Comments
You should check out this PR #87704 |
Yes, that would solve the security problem. It would also end up breaking invoking any batch files or other programs that people are running right now without using a suffix, which would be less desirable. I"ll make a comment on the PR. |
cc @rust-lang/libs |
Seems like @ChrisDenton's PR already solves this and doesn't introduce the breakage that @bk2204 was worried about. I'm assuming we can close this when #87704 merges. |
There's potential breakage if anyone depended on any of those implicit paths that #87704 will no longer search. That may still be the way to go -- I'm just saying that it's not entirely worry-free. |
Indeed. I am happy to change my PR so it's a closer match to the existing behaviour (current directory aside). This would fix the immediate issue while having the lowest chance of breaking things. This would also allow for a separate decision on removing implicit paths at a later date. Hm, actually that does sound like a good idea. |
#87704 fixed the specific issue raised here (looking for the exe in the current directory) but didn't close this. I guess partly because the PR was opened before this issue was filed and also because it was mentioned that the uses of the current exe directory is also an issue:
Which @yaahc felt at the time was a compelling reason to remove:
The impact of removing the current directory from the search path seems to have been minimal. Therefore I'd like to propose removing the exe's directory from the search path. To spell out the changes to the search strategy, I've listed it here with the changed crossed out:
|
It looks like searching the current directory is historical baggage on windows since CreateProcess does that by default.
Aren't those two on the PATH by default? I mostly like the unix approach. If it's relative and not in your PATH it fails. Except for the case where you don't have PATH set at all (absent, not empty), then |
I'd love for us to simplify this to just checking PATH but my concern is possible breakage.
They are indeed by default. But they can be removed or reordered. So removing the special handling would be an observable change (albeit hopefully a low impact one). |
Providing a Windows developer perspective on this: Existing Behaviors:
Security Concerns:The question to ask here is: "what can an attacker do that they couldn't do before?". With loading from the current directory, this is an issue: when opening an item via Explorer the current directory is the item's directory, if that is writable by an unprivileged user and it is a privileged user that is opening the item then the unprivileged user may be able to influence what code is running beyond the document being opened. (e.g., Imagine if "*.dog" is opened by "Doggo.exe" which internally invokes "Scritches.exe" expecting it to be on the PATH - an attacker can then place "Rover.dog" and a malicious "Scritches.exe" in a globally writable directory and so cause the malicious exe to be run even though the victim didn't click on it and Rover.dog is completely legitimate). With loading from the same directory as the executable, this is not an issue: if the executable's directory (and, thus, the executable) is writable by an unprivileged user, then they can control what code the privileged user is running by replacing the original executable itself - no need to mess with search paths, etc. (Given the previous example, if "Doggo.exe" and "Scritches.exe" are both in a globally writable directory, then the attacker can replace Doggo.exe with a malicious version rather than trying to exploit the legitimate Doggo.exe's use of Scritches.exe). I know that there are a lot of "user-local installation" applications that place their binaries into a user-writable location, but these all rely on placing the files within a user's directory thus a different (unprivileged) user cannot write to those files: only the owner (which requires the attacker to already control that account) or privileged users (who can already write to the system files) can write to these files, neither of which gains anything by doing this. Recommendation:I would leave |
There are a couple of problems with the current approach. First, it works differently on different systems. Simply using The one relevant different is that on Unix, an empty component is considered the current directory, but because many Windows systems end up with an empty The additional problem with loading files from the directory that the executable is in is temporary directories or download directories. If I download files with a browser, they won't be overwritten unless I specifically ask to do that. If I have a legitimate Rust binary that is supposed to invoke, say, While I admit I'm not a Windows developer (although I play one in my role as maintainer of Git LFS), my experience is the default behaviour of |
The (by default) insecure behaviour of Has there been similar CVEs or other documented exploits that took advantage of the executable's directory being in the search path? The absence of such CVEs would not necessarily undermine the argument for removal but their presence would certainly help bolster the case. |
The most common instance of this that gets CVEs is DLL hijacking, where a program in a temporary directory loads DLLs that are beside it. However, there's also this article:
It goes on to describe a particular NVIDIA driver that had this vulnerability. This is not the only instance; it was just the easiest one to find. DLL hijacking vulnerabilities are more common, so it's actually a little difficult to find vulnerabilities where an executable was the problem, but I assure you that this has been a problem with self-extracting executables from time to time when they extract to the temporary directory. |
There appears to be a security vulnerability in std::process::Command, specifically on Windows. The documentation for the
new
function states this:What this does not say is that these implementation limitations cause the program to be executed from the current directory, even if that is not in
PATH
. This is a vulnerability and this behavior has been known to be insecure on Unix for many years.As a result, it is not possible to use Rust to invoke other than an absolute path when the current directory is untrusted, such as when a user is working in cloned Git repository. Moreover, this fact is not even documented, and as such, I would reasonably expect that directories outside of
PATH
are not searched.I've attached a tarball of a cargo project which contains two trivial programs. To reproduce the problem, do the following:
path-check
.cargo build
.target\debug
.$env:Path = "C:\Users\User\.cargo\bin"
to set a fixedPATH
without the current directory.& '.\path-check.exe'
.exploit.exe
is also invoked and that its output is displayed.I used a Windows 10 Development VM for this purpose. I don't habitually use Windows; I'm mostly a Linux user, so my apologies if the steps are hard to understand.
I realize that the current functionality exists because Rust looks only for
.exe
files and not other types of files, and it therefore it otherwise passes files which do not exist inPATH
toCreateProcessW
, which has the insecure behavior. However, just because Microsoft has designed an insecure interface does not mean Rust should permit the same behavior.The approach that Go uses for searching for executables, other than the use of the current directory, is generally good. It uses
PATHEXT
(or, if absent, a hardcoded list) to look for extensions, and then considers each component inPATH
(rejecting empty components), looking for each extension in turn. This algorithm (with the current directory) is used by CMD, and therefore doing the same thing without the current directory would be normal and expected for Windows users, and also secure.Note that unlike on Unix, on Windows empty components in
PATH
should not be treated as the current directory. Unfortunately, many Windows machines contain a trailing semicolon inPATH
and as such, that would preserve insecure semantics.I originally reported this to the private security list, but it was determined that this behavior had been discussed publicly before, and thus, opening an issue was appropriate. It remains a vulnerability, and a CVE should still be issued, though.
This came to my attention because Go has the same insecure behavior and they have deliberately chosen to retain the vulnerability for compatibility, so every Go program that runs on Windows (including Git LFS, which I maintain) must contain special code to work around this. Since Rust has already documented secure behavior (using
PATH
), all that needs to be done is actually fixing the implementation, which is less of a problem.Meta
rustc --version
:I've verified that the vulnerable code exists in a recent HEAD.
The text was updated successfully, but these errors were encountered: