-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Path::join should concat paths even if the second path is absolute #16507
Comments
cc @kballard Some other libraries (Haskell, SML basis) only provide something akin to our current I don't see a great Windows story here, either: Honestly, it seems a bit weird to me to take something that's syntactically an absolute path and want to treat it as a relative path. In my experience using |
I agree with @aturon. The only sensible operation when joining an absolute path onto some other path is to get the absolute path back. Doing anything else is just weird, and only makes sense if you actually think of paths as strings, where "join" is "append, then normalize". I do not understand why Go's FWIW Ruby actually matches our join. At least, if you use |
In C++ using |
@retep998 C++ doesn't have a |
@kballard I was using the |
@retep998 Sounds like a really crappy path definition then. I guess that's what you get with design-by-committee. According to MSDN, |
Because |
The C++ committee has recently been dealing with this problem. |
C# (or rather the .NET standard library) also matches the current behavior: |
The Moreover, there is now a documentation indirection pointing to The Path reform should get ride of this dangerous behavior. The cc #20034 |
Note that this talks about not just absolute paths, but also relative paths that begin with
@steveklabnik What's our standing policy here? Do we prefer to have multiple copies of very similar docs?
This bothered me with the old path API as well, but I wasn't able to come up with a better solution. Do you have any suggestions? |
The PR #5023 added an I like Of course, this apply to Name ideas:
|
Just here to confirm when using a standard Windows function to join paths, absolute paths will replace the existing path wholesale. Drive relative paths such as |
If we're feeling really ambitious we could overload |
I'm going to close this particular issue as WONTFIX: we plan to stay with the current join semantics. (If someone strongly disagrees, at this point I think an RFC would probably be warranted, as the current behavior is from an approved RFC). As far as naming, we could potentially continue bikeshedding, but not on this issue. I will note that we should not use the same name for I would love to have a general convention for pairs of functions where one mutates and the other creates a new copy, but I don't have a good candidate. In any case, let's address that elsewhere. |
@aturon, what do you think about an unambiguous new Looking at the current Rust API, The The |
Seems I'm several years late to the party here, but I wanted to chime in and say I found this particular behavior rather unexpected, and worry it could potentially lead to security issues if attackers manage to clobber higher level paths by passing an absolute path to join, i.e. directory traversal. Granted that any code doing join on attacker-controlled paths should probably be doing quite a few checks to ensure they're getting the desired results, but it's certainly not clear from the outset that joining to an absolute path can have this outcome. You can imagine someone naively checking a path does not contain |
Chiming in to say this caught me out and cost me about an hour of struggle, and will cause a whole generation of insecure roll-your-own webservers who naively take in "/etc/passwd" as a URI, to be joined onto "./resources". Joining paths should descend deeper unless otherwise specified, and absolutely not climb out of the current directory. |
What you describe would also completely break the ability to join e.g. If you need security guarantees when taking in user-supplied paths, you need to enforce those guarantees yourself, because different people have different expectations. For example, even if you do want the "never climb out of a given root directory", you may still want to support |
There was a fun article about this I came across today by the Fuchsia developers: Dot Dot Considered Harmful
As it were, I have a crate that attempts to address some of these issues: canonical-path. The semantics aren't quite where I'd like them to be yet (despite a 1.0 version number), but if people are interested in working with paths in security critical contexts, I plan on continuing to address the shortcomings, and would like to put out a breaking 2.0 version with more restrictive behavior. |
Security minded path enforcement is actually really complicated due to crazy things on Windows. There have been so many security holes in browsers due to various techniques to escape out of a path. Basically, Rust should not try to make security guarantees regarding path concatenation because it is an extremely difficult task and impossible without hitting the filesystem. Giving people a false sense of security when concatenating paths would only make security worse! |
I don't think canonicalizing paths ahead of time is actually the solution to security issues. Canonicalization involves resolving symlinks as well. I think the correct solution for the general case of "don't let a user-supplied path escape the sandbox" is to standardize the user-supplied path (where "standardize" means remove all Of course, this approach does ignore symlinks, so e.g. if As the hacker news comments on that very article point out, there's a reason this sort of canonicalization is normally done in the kernel, specifically so the symlinks can't change in between checking the path and using the path. The same comments also mention the |
@kballard indeed the https://github.com/iqlusioninc/crates/blob/master/canonical-path/src/lib.rs#L129 This does not enforce the property that any joined paths are children of the current path, but merely that the resulting path is also canonical. I think it would be worthwhile to enforce the former property. The algorithm I'd use for that is slightly different from what you were originally suggesting, and works more like what you were alluding to in the second half of your post (the first two steps are what it does today):
Additionally, I think it'd probably make sense to ban all relative path components from the argument (i.e. the relative path to be joined). Indeed the current implementation contains a TOCTTOU-style race condition where directories/symlinks can be renamed/relocated after resolution. At some expense each borrow of the underlying |
Some more years later (:D), I randomly found this and I think the reason
Current // Return result, alternatively it could panic:
fn file_in_dir<I>(&self, components: I) -> Result<PathBuf, ComponentContainsSlashError>
where I: Copy + IntoIterator, <I as IntoIterator>::Item: AsRef<OsStr>;
// Alternatively, using newtype SlashlessOsStr that can only be created from str and guarantees to not contain slashes
fn file_in_dir<I>(&self, components: I) -> PathBuf
where I: Copy + IntoIterator, <I as IntoIterator>::Item: AsRef<SlashlessOsStr>;
// join renamed
fn as_base_to(&self, maybe_relative_trusted_path: &Path) -> PathBuf;
// Sloppy chroot assumes there are no symlinks, so /foo/../bar will become /bar
fn as_sloppy_chroot(&self, containing: &Path) -> PathBuf;
// Does exactly what chroot would: resolves symlinks in a way that makes sure they don't point outside
// I have no clue what it should do on windows if you use "c:\" in containing, possibly return error with kind InvalidInput
fn as_chroot(&self, containing: &Path) -> io::Result<PathBuf>; Open to bikeshedding obviously. It'd be nice to gather other use cases if there are such, make a set of reasonably-named methods with reasonable interfaces, then deprecate I may write a crate for this at some point. |
…=lnicola Remove `ffi_returns_twice` references This feature has just been [removed](rust-lang#120502)
To support processes running in mount namespaces that aren't the root one we can access the files in their mount via procfs's root directory. Not only this was broken in terms of the paths being joined, but also the way `Path::join` works is that the joined path starts with a slash (so it's recognised as absolute) it will replace the whole path (!). This was reported upstream in rust-lang/rust#16507. Test Plan ========= Ran lightswitch for a little bit, and containerised workloads worked fine.
To support processes running in mount namespaces that aren't the root one we can access the files in their mount via procfs's root directory. Not only this was broken in terms of the paths being joined, but also the way `Path::join` works is that the joined path starts with a slash (so it's recognised as absolute) it will replace the whole path (!). This was reported upstream in rust-lang/rust#16507. Test Plan ========= Ran lightswitch for a little bit, and containerised workloads worked fine.
Given:
I would expect that
a.join(&b)
would return/foo/bar
, however it returns/bar
. Given my experience w/ path joining in Ruby and Go, I would expect that join concats two paths and does some normalization to remove double slashes, etc...There is also a need for a fn that expands a path relative to a given location, which is what Path::join does today. In Ruby, it is named expand_path.
The text was updated successfully, but these errors were encountered: