-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Cargo and Rustup safe file discovery. #3279
base: master
Are you sure you want to change the base?
Conversation
Since the settings file is private to Rustup, | ||
the following CLI options can be used to manage this setting: | ||
|
||
* `rustup set safe-directories add PATH` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does rustup set safe-directories add .
expand to the absolute path of the current directory? do other relative paths expand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect to process such paths through std::fs::canonicalize()
if that's what you mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for raising this point!
The current PR does not attempt to guess what the user intended, and adds it literally. However, I see that is not a good approach as I imagine something like rustup set safe-directories add .
would not be unreasonable.
I started down the path of figuring out the best way to deal with this, but I've run out of time/energy. Using fs::canonicalize
is difficult and fraught with problems, particularly on Windows. I want to avoid putting verbatim paths in the config (as they get exposed to the user). Unfortunately fs::absolute
isn't stable (I would very much prefer to use GetFullPathNameW
over anything else). I considered using Cargo's hacky normalizer, but that has its own problems. And I'm finding it difficult to consider the security considerations of these approaches.
I'll continue to work on it when I can.
text/0000-cargo-rustup-discovery.md
Outdated
This could significantly ease the burden of dealing with this issue, | ||
as it will avoid needing to set safe directories in almost all use cases where they would be needed. | ||
|
||
However, I feel like this approach is too risky. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OTOH this makes use-cases that use group-wide permissions (i.e. rwxrwx---
) and a umask setup in a certain way work mostly out of the box.
I'm somewhat invested in this, since I tried to utilize that sort of approach, though not with Rust...
text/0000-cargo-rustup-discovery.md
Outdated
(unless they have elevated permissions like root). | ||
I believe this is the case for all major operating systems and filesystems. | ||
|
||
One (major) known exception is if a network-mounted filesystem is configured to map all files to the local user, which is not uncommon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another major exception is UID reuse – where the attacker creates an user, poisons the filesystem and then later a genuine user gets created at that specific UID.
systemd comes to mind as an example of a tool that a) creates users left and right; and b) has mitigations for this problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I have added that as another concern. I personally don't feel like the risk here is very high, as there are likely other avenues than Rustup/Cargo for escalating once an attacker has control of a temporary new user. However, it definitely is a concern.
Should mention be made of TOCTOU concerns? Or is that not considered a threat? |
If you have a directory `Foo`, and you type `cd foo`, | ||
then the operating system will report the current directory is `foo`. | ||
|
||
I am not aware of an easy way to detect if a filesystem is case-sensitive. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A typical case-sensitivity check creates two files with names that only differ in case. If the second create fails, or listing the directory only returns one file, then the filesystem is case-insensitive.
But it's faster to get the metadata of an existing file, via a path with a different case:
- List the directory containing the file
a
- If the directory contains two entries for the file
a
that only differ by case, the filesystem is case-sensitive - Get the metadata of the file
A
, which has a name that only differs by case - If the query is successful, the filesystem is case-insensitive
- Otherwise, if it returns a file not found error, the filesystem is case-sensitive
Unfortunately there's no easy API for this, because Windows can change case-sensitivity by directory, macOS by process (rarely), and most OSes can change it by filesystem.
I don't entirely get the attacker model here. Let's say we are in the specific situation where there is a I agree that using files arbitrarily far up the folder hierarchy can be a problem, as described in the RFC. But I clearly trust the code in my current directory if I build+run it, so I don't see any harm coming from also trusting the |
This may be particularly difficult for a user running Windows `cargo.exe` from within WSL. | ||
Paths in this environment look like `\\wsl$\Ubuntu-20.04\home\eric\foo`. | ||
I don't know how common this use case is, as I would expect someone doing things in WSL would install the native rustup/cargo binaries instead of using the ones from Windows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That does sound like a tad pain. I even wrote a build definition that relies on being able to call the Windows installation of Cargo from WSL to make building easier.
I think that quite a few pieces of software have become unreasonably frustrating when trying to enforce permissions while running in WSL, fwiw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! I was not expecting anyone to be doing that, though I guess I'm biased by mostly using mingw instead.
I think the error message should provide something that should work to copy-and-paste, so I hope that should be sufficient. This is also something that we may be able to iterate on without breaking things.
Yeah, this also makes sense to me. Opening a directory in your shell (or editor, but they already have solutions for this problem) is an explicit action taken by the user. Opening it means they're saying "I, my person, wants to do something here." Hypothetically, if |
The original contained a note about that, but I removed it since I didn't want to list all the things that won't go wrong. I added a small note back in. I do not consider it a threat since the attacker would need to have write access to the user's files, and that means they already have escalated privileges which I consider out of scope for this fix. Did you have any particular threat scenario that you are concerned about? |
This was a point we discussed quite a bit, and it is difficult for me to confidently make this exception. I have added some more discussion in the "Honor the user of the current directory" section. I concede that the risk is small, but I am concerned about the possible inconsistency. If there is broad agreement that this exception is worth it, then I would consider it.
This was one of the leading concerns that led git to implement their fix. The concern is that navigating to a directory may not necessarily be a signal that "I trust everything in this directory" (particularly for beginners). The example is a shared directory used by multiple students as a scratch space, where they may |
No, When I do (To be clear, I would love to be able to do Or is the point that users are not aware that |
text/0000-cargo-rustup-discovery.md
Outdated
|
||
```text | ||
error: `C:\Projects\MyProject\Cargo.toml` is owned by a different user | ||
For safety reasons, Cargo does not allow opening manifests owned by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"For safety reasons" is pretty useless information. It's the kind of wording someone uses when they don't want to justify their actions towards the public or the press.
For users to be able to do an informed decision, this should explain what the associated safety risks are. Is this referring to build scripts and proc macros, or some other kind of safety risk?
I am thinking of something like this:
error: manifest file `C:\Projects\MyProject\Cargo.toml` is owned by a different user
If you proceed, cargo will build and run code as instructed by that file.
Even just 'cargo build' or 'cargo check' will run code controlled by the manifest.
[...]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I have tweaked the wording to briefly explain what the safety concern is.
Ok, I'm still trying to get a handle on what the threat model is. So we take ownership to mean that if the file gives another user rename/delete or write permissions then that is something the owner presumably intended and is therefore "safe" under this model. What about ancestor directories? The RFC says:
But surely if there's a symlink in the path or an ancestor directory can be renamed by another user then that can be exploited? Or is that not considered a threat here? |
On macOS, and on Linux with The hard link creator can't affect the contents of the file, so for this to be dangerous, there would have to be some existing file owned by the target user, at some other path in the same filesystem, whose content would be dangerous if interpreted as This issue doesn't affect Git's directory ownership check… I think… because macOS and Linux don't support hard links to directories, at least by default. (macOS does or did support them for HFS+ mounts, but not the newer APFS.) |
text/0000-cargo-rustup-discovery.md
Outdated
causing it to open repositories from every directory the user enters. | ||
|
||
`git` has been changed so that it will not open a repository owned by a different user, and instead return an error. | ||
For most cases, it will check that the ownership of the directory *containing* the `.git` directory matches the current user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might worth mentioning that a fix for CVE-2022-29187 (missing part of CVE-2022-24765) was released two weeks ago: git/git@3b0bf27
Basically, git now takes into account the ownership of gitdir and gitfile as well. Unlike git, this RFC proposes to check files themselves, so I don't think we need a revise accordingly.
|
||
## Rustup toolchain override discovery | ||
|
||
When searching for a `rust-toolchain`/`rust-toolchain.toml` file, Rustup will check that the ownership of the file matches the current user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this only needs to apply if the toolchain is specified using a local path rather than an official toolchain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in Granular capabilities, we didn't want to go down the route of trying to do fine-grained checks of the contents of files or determining exactly which parts would be safe and which wouldn't. I don't have confidence that we would be able to do that securely now, and particularly as things are added in the future. It might be possible to have something similar to what gitoxide has by marking what is safe and not (possibly at the type level), but that would likely be a very intrusive change.
I'd also not be so confident that official toolchains are "safe". For example, an attacker could force you to use an older version of Rust which has some vulnerability that they could then exploit in some way.
This could possibly be done in the future, but I'm not sure there would be a large value if it was done in just rustup and not cargo, since the false-positive scenario would likely involve both.
I'm sorry for letting this languish for so long. Certain issues came up last year, and then I intended to return to this in February, but other things interrupted again. I have pushed several updates to the RFC incorporating some feedback, and updating for more recent changes.
I've tried to clarify the RFC on the threats that this is intended to address. This is not quite related to build scripts or proc-macros. It is related to
I'm not sure if "intended" is quite the right word, since it may or may not be intentional. But I think it is somewhat outside of the scope of this RFC to have cargo or rustup police the ACLs of the files it accesses to require that they not be changeable by other users. I have added an extended TOCTOU section to try to address some of the concerns. I'll admit that this is one area where I feel the least confident in, so if anyone can provide counter-arguments with concrete examples, that would be extremely helpful. |
I pushed a change to add a temporary warning period to give users advance notice of this change. I'm hoping that should ease the disruption and make it easier for users to prepare for it. |
I'm moving to go ahead and move to merge this. This will also require the approval of the rustup team. However, since they are not configured for rfcbot, and rfcbot does not handle multi-team approvals very well, I'm going to handle them manually. This will not be merged without similar approval from that team. (rustup team: feel free to check a box, or leave a comment, or any blocking concerns.) @rfcbot fcp merge |
Team member @ehuss has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
rustup review tracking: |
As collecting feedback is the hardest problem, having a warning period helps resolve that and we can pivot if we need to. I'm good with moving forward with this. As a wild idea, we could possibly express this as a diagnostic that we change the level for (rust-lang/cargo#12235). The main issue being that we need to know the users desired level before we even parse to find the package/level. I mentioned in that Issue that this is a general problem and we might need some cargo-diagnostic control in the config file for these non-package, non-workspace use cases. |
Should you perhaps add a blocking concern mentioning the rustup team so rfcbot won't proceed until that's manually resolved? |
@rfcbot concern rustup-approval |
@ChrisDenton that would be good - I'm not quite sure how to do that - I haven't interacted with the RFC automation so far. https://www.rust-lang.org/governance/teams/dev-tools#Rustup%20team is up to datish (I'm not sure if @kinnison wants to step down until he's more available or not), so any leadership bits that need to be set should be set already. I will try the bot though, just in case. @rfcbot concern rustup hasn't reviewed Context for that: This started while @kinnison was active, but he's stepped away for a while, and I haven't had time to read or think about this thing end to end. I've given it a quick glance, and I think its not clear what the threat model we're actually tackling is (for instance, if its 'shared parent directories can permit injection of arbitrary code via toolchain files', then the next release of rustup will correct that. But a toolchain file could still pick a pre-safe-aware version of cargo, and then attack that. But neither of these address the things that vscode and I presume other IDEs aim at with their 'trusted directory' UI, which is downloading of arbitrary git repos from the internet. Introducing this will be a breaking change (by definition - an opt in security feature rarely works), so I'd like us to at least consider all the cases that are clear and visible today, which I will prioritise amongst the time I have available to do rustup contributions. I appreciate the RFC has been around for a while, but this is the nature of volunteer personal-time contributions. |
I have submitted a PR to step down from the team for now, I'm not going to be able to offer help in the medium term at least. |
While security is the main intention of the RFC, I want to mention that the RFC is also functionally necessary on some trusted but weirdly configured shared systems: rust-lang/cargo#12080 In my case, this is ETH Zürich's HPC cluster Euler. The OS creates a file under the parent of the user's home directory whenever an
IMO this is a very janky solution, but as a user I can't do anything about it. Nonetheless, most other software that does recursive lookups (git, Python with modules) handle this without issue, but Cargo just breaks when a |
@cbeuw I can imagine Cargo breaking on existing but unreadable files even with this RFC implemented. I suggest you file a bug for that (and perhaps work on a PR). |
Ok, so full review of the RFC finally - appreciate the patience with volunteer timescales. A nit: On Windows C:\ is not world writable by default. The default permissions are read-only + create folders for users, with only Administrators having full write access. Threat actors that can create The threat statement : I propose we consider expanding this RFC's scope a little:- explicitly consider these threats and align with IDE behaviour. For instance, refusing to build a downloaded directory that uses any settings that can be escalated (e.g. build.rs, rustc wrappers and so on). The impact should be minimal, and it avoids have two back to back contract breaks, rather than one. I realise the RFC rules this out today:
I think the existing initiatives like Moving on, I agree that Rustup will need this. Even with relative path toolchains removed, a hostile local directory that has been prepared can reference a hostile absolute path based toolchain : refusing relative paths makes downloading repositories safe, not running from arbitrary local directories. To me it makes sense to honour I don't agree with this line:
I don't think it makes sense for https://github.com/ehuss/rfcs/blob/cargo-rustup-discovery/text/0000-cargo-rustup-discovery.md#cargo-config-discovery doesn't specify about the behaviour when a mismatch occurs. I suspect the intent is to error. While I agree that
Not being TOCTOU safe opens risks that do not require creating files owned by the victim. For instance, a shared lab with world writable common directory allows for a victim created On the drawbacks. Rustup does have system configuration, so the ability to clear the setting would be useful if we decide to use rustup's config store. I agree that we shouldn't honor the user of the current directory: its not obvious at all that cd'ing to a directory means trusting its contents. For instance shell prompts that query Cargo metadata could be silent ways that arbitrary code would run just by cding. On the Check-the-ownership-of-directories-while-traversing: the overhead should be extremely low but we could test easily enough. But a middle-ground would be to behave as though we had. Concretely when we find a mismatched config file, before erroring, see if the parent directory would have triggered stopping searching, and if so, just ignore the file and stop searching. On WSL: I don't think we should do anything special here. Yes, Microsoft has muddied the waters with some transparent glue, but the expected behaviour is definitely installing host-native tools and rustup versions. On checking for case sensitivity... this is probably impossible in all cases, but a decent approximation is to assume that Linux is always case sensitive (e.g. ignoring mounted FAT fs's), and on windows query the file system https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa . There may be a per-directory setting as well - there are windows CLI tools that can control that, but I haven't found the underlying API. @ChrisDenton probably knows :). |
You can check per-directory case sensitivity using I think insisting that users use the canonical case in config files, etc is a fair compromise in an initial implementation. Though admittedly it will annoy some users and those users may strongly disagree with me on this point. |
Thanks @rbtcollins for the review!
Thanks for the clarification. I didn't want to get into the details of when the root is writeable since I believe it is a complicated question (depending on various factors). I tried to update the text to make it clearer that it only applies in some situations.
I may be misunderstanding this suggestion, but it sounds like a very large change with a large impact. The intent of this RFC is that the vast majority of users will not be affected and won't even know this is a thing. This sounds like it is suggesting that every directory needs to be approved before it can be used, which sounds like it would affect essentially everyone. Can you clarify what is meant here, or why it seems like it should be a minimal impact?
I'm not sure I understand this completely. Are you suggesting that rustup should check for I would have some pretty high concerns about rustup trying to parse and understand cargo's config files. Trying to coordinate changes across the tools I think will be challenging, and potentially confusing (users may use rustup without cargo, for example). I'd also be concerned about the performance impact, since toml parsing can be a little slow, and the proxy startup time is already a concern.
The intent is that rustup is setting the environment variable from rustup's config. The place that lists the three options is specifying what cargo will read. The next section is for rustup which specifies what rustup will read. I altered the text to try to make that clear.
Perhaps I'm not understanding this suggestion properly, but as I'm reading it, it seems to be suggesting that no errors are ever generated? That seems to be a pretty large change to this RFC, perhaps you can clarify it in more detail? In general, I don't think this is an option because that could lead to fragmented or broken configurations that could be quite challenging to diagnose and fix. For example, Cargo's workspaces could be broken such that a workspace member would load, but the workspace configuration would not. Also, the intent of the error is to be an alarm to alert the user something suspicious is happening. Although we want to avoid false positives as much as possible (and almost all errors are going to be false positives, since exploiting this is probably going to be very unlikely), I don't think we want to avoid the main purpose of the check.
This is an interesting point, and I'd like to dive into understanding it in more depth in a followup. Just FYI, I'm mostly basing this off of git's implementation, which also does not do handle-based checks, and thus has limited TOCTOU protections. Implementing something that combines traversal, checks, and opening of files will likely be intrusive, making it harder to incorporate this change. |
@rust-lang/rustup Just checking in again to see if this is something ya'll can look at for approval? Thanks! |
@ehuss I have no more questions regarding this RFC. Can you tick the box for me? Thanks! Update: I've made rust-lang/rustup#3935 to track the implementation on Rustup's side. |
Ticked my box, LGTM. |
This list needs to be updated since @ChrisDenton has currently joined the team :) |
This RFC is a proposal to fix a security issue with how Cargo and Rustup discover their files.
In consultation with the Security Response Working Group, we decided to disembargo this and solicit feedback and testing before this change is made. We feel like the relative security risk of this is low compared to the other risks involved with using these tools. Additionally, we think that the risk is high that this will be a very disruptive change. We would like to get feedback from the community and let you have a chance to test and prepare your environments ahead of time instead of pushing a new stable release immediately.
However, we are planning to move this change through quickly so that the protection is available to users as soon as possible. Our current thinking is to aim for stabilizing it in July for the 1.64 release which will be released in September. The implementation will be available on nightly soon, and a pre-release version of Rustup will also be made available as soon as possible.
Rendered