-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
proposal: path/filepath: addition of SecureJoin helper #20126
Comments
I agree that a method like this would be useful/make it easier to do the right thing. +1 |
Regular reminder: https://golang.org/wiki/NoMeToo |
I'm sorry, I'm not clear on precisely what you are proposing. Can you write the docs and function signature of the function that you want? Thanks. |
@ianlancetaylor I linked to sample implementations, but sure here's the signatures and docstrings I would use. The actual name of the functions could be improved (I couldn't think of a nice name for // CleanComponent lexically sanitises the given string so that it is a
// lexically safe path to use in filepath.Join and similar functions, such that
// Join(a, CleanComponent(b)) will always be a child path (or equal to) a.
func CleanComponent(component string) string // EvalSymlinksInScope returns the given path name after the evaluation of any
// symbolic links in the scope of the given root path. Absolute symlinks will
// be resolved relative to the given root, and the returned string will always
// be within the provided root path. The given path must be within the root to
// begin with.
func EvalSymlinksInScope(path, root string) (string, error) The implementation of // EvalSymlinks is equivalent to EvalSymlinksInScope(path, "/"), with all
// symlinks being evaluated in the scope of the root of the filesystem tree.
func EvalSymlinks(path string) (string, error) {
return EvalSymlinksInScope(path, "/")
} |
How would that work on windows? Alex |
@alexbrainman I wrote the docstring to explain it in terms that most programmers would understand (that EvalSymlinksInScope is a generalisation of the EvalSymlinks function) -- |
Thank you for trying. I consider myself a programmer, but I fail to see how EvalSymlinksInScope would work.
What is "the scope of the given root path."?
That is not what Windows does. Why should EvalSymlinksInScope work differently to Windows?
You are proposing new functions for path/filepath package, and the package "implements utility routines for manipulating filename paths in a way compatible with the target operating system-defined file paths". I expect your proposal to consider all operating systems that Go supports. Especially that you are trying to do "the right thing" when it comes to path safety. Unless these functions are for Unix only? Is that your intention? Alex |
The intention is to allow for a program to evaluate symlinks in a similar way to how the symlink would be evaluated if you preceded the call with // Imagine these were preceded with the following shell commands:
// $ mkdir -p /tmp/root/some/directories
// $ ln -s /some/symlink /tmp/root/some/directories/symlink
// $ ln -s ../../../../../../etc/passwd /tmp/root/some/symlink
// $ ln -s /youlose /some/symlink
EvalSymlinksInScope("/tmp/root/some/directories/symlink", "/") // = "/youlose"
EvalSymlinksInScope("/tmp/root/some/directories/symlink", "/tmp") // = "/tmp/some/symlink"
EvalSymlinksInScope("/tmp/root/some/directories/symlink", "/tmp/root") // = "/tmp/root/etc/passwd"
EvalSymlinksInScope("/tmp/root/some/symlink", "/") // = "/etc/passwd"
EvalSymlinksInScope("/tmp/root/some/symlink", "/tmp") // = "/tmp/etc/passwd"
EvalSymlinksInScope("/tmp/root/some/symlink", "/tmp/root") // = "/tmp/root/etc/passwd" The main things to notice is that it would mean that a program that wants to evaluate symlinks within a "root filesystem" 'context' they don't have to worry about escapes from the root filesystem (and also symlinks within that filesystem are "safe"). The Windows equivalents to the above I imagine operate in a similar way, but I'm not familiar enough with Windows symlinks to be sure.
I am not fully familiar with how symlinks work on Windows, can you enlighten me? The purpose of If we had to limit it to Unix I would be saddened (especially since Microsoft has worked on the implementation in Docker, which leads me to believe there is a sane equivalent to this behaviour on Windows).
I'm aware that I apologise for being sloppy and using the Unix equivalent in a hypothetical docstring, but that shouldn't be construed as "I don't understand that |
If I understand this correctly, the ultimate goal is to sanitize a user provided path in such a way that you can join it to an application provided path with the assurance that the joined path is always contained in the application path. If that's the case, do all of these need to be exposed or would an API like the following suffice:
Do the other functions have uses that I'm not imagining? |
@jimmyfrasche [I had a comment earlier that detailed what the proposed functions uses are, but I've thought about it some more and below is my only real gripes with You're actually right that with
Though aside from those two concerns, I'm open to the idea. Do you think my concerns are warranted? Also I'm a bit saddened because |
SecureJoin is probably a bad name. It's just the first thing I thought of. I'm not married to it. I'm using it just to call it something. I'm not opposed to EvalSymlinksInScope or CleanComponent. I just can't think of a concrete use case for either of them other than to implement a SecureJoin. Again, this could be paucity of imagination or lack of research on my part. I've never needed anything like this. I realize there could be a need that I am unaware of. I'm happy to be proven wrong because that would mean I learned something new. Regardless, even if CleanComponent and EvalSymlinksInScope go in, I'd still want something like a SecureJoin to let people know "this is what you want 90% of the time: use this, this way, in these circumstances and then you wont have these problems". Even if it's relatively straightforward to construct a SecureJoin with those primitives, it doesn't mean everyone will do so correctly or even realize that it's what they want to do or that they need to use those specific primitives to do it safely. The easier it is to be secure and the more obvious it is how to be secure, the more programs will be secure.
That assumes there's a use for EvalSymlinksInScope outside of doing a SecureJoin. Where I'm getting lost with this is trying to Imagine a situation where you would want to EvalSymlinksInScope without the ultimate goal of doing a SecureJoin.
What does it mean to ensure there are no path traversals when you can't be sure there's no path traversals until you hit the file system and verify nothing resolves out of scope? That question is half rhetorical and half ignorant. Unless I'm missing something, it seems like you either hit the file system and know it's actually safe or you do some lexical preprocessing so that it appears to be safe but you can't actually tell until you go to use it, at which point you need to use SecureJoin anyway. In your example, wouldn't it be just as effective to store the raw path in the DB and do the SecureJoin against it when it's used? Even if you do preprocessing, wouldn't you still need to use SecureJoin after you pull it from the DB to make sure there are no symlink shenanigans? It seems like the preprocessing would just make the DB table look prettier at best and give a false sense of security at worst. (I spend a good part of my work week staring into databases, so I'm not opposed to making the data look prettier, mind you). |
You are obviously know more about this subject then me.
I don't know much about the subject myself. I am learning on the job (as I fix problems in Go). I am learning that there is no single symlink type. Instead there are "directory symbolic links", "file symbolic links" (CreateSymbolicLink) and "directory junctions" (there is no documented Windows API to create those). I am learning that there is no Windows API to read symlink target, like syscall.Readlink. I am learning that some symlinks are handled by Windows and its services transparently without any documentation what these are and how they work (see issue #18555 and #19922). That is all I can think of. I hope it helps you, but I don't see how.
I have no idea what you are talking about. Sorry.
You forget that your new API have to work on plan9 too. I know nothing about plan9, but I know it does not have symlinks.
I think it is up to you to sell your proposal to Go users, including Windows users. If you did not consider any OS, but Unix, then your proposal does not belong to path/filepath package. But I will spent more time investigating your idea, if you point to the "windows version" of the code in Docker. I am also more interested to see some tests for these functions that test some Windows paths - that should help me understand what is proposed. Thank you. Sorry if I am bit critical of your proposal, but I really cannot see how it could be useful for me personally. And I have been using Windows and Go for awhile. What about other Windows Go users. Alex |
Yeah, you're right -- you've convinced me. And ultimately it's very trivial to take a full path and a "root path" and convert them into the two arguments for |
I linked it in the original section, but here it is again -- though the function is called
I just checked and couldn't find tests in Docker's source code for the windows integration. The original authors of this extension of Docker's
In which case,
It's a bit hard to explain if you're not familiar with containers (or at least |
Thank you for the link, but none of that code is tested on windows - tests are specifically disabled for windows. That just goes to show how useful it for windows.
Maybe it is a good sign that this does not belong in path/filepath? Alex |
There are no unit tests for Windows. The code itself is tested through integration tests (
If Let me give you an example with Windows paths (though note that I don't use Windows so I can't verify that this example is sane, it's just coming from my reading of this documentation):
I would anticipate that the following functions will return the following values (though I'm not entirely sure about the
If you wanted actual usecases, I've updated the original description, but here they are in case you can't see them:
I'm sure you'll agree those examples are not just Unix-specific. |
I think you got parameters wrong way around:
I am sorry but how do you propose to implement SecureJoin? And how is that useful? There is no C:\someroot\Windows directory. Where would you use the "C:\someroot\Windows\System32"?
I know nothing about these. But I am just an average user.
Do you mean code that creates symlinks should be careful. Perhaps, yes. Isn't it up to the archive/tar user to consider all that? BTW, only administrator can create symlinks on Windows. So it is not common occurance.
For this you should use filepath.EvalSymlinks, and then check the result is valid as per whatever rules the program has. Alex |
It has already been implemented and I've already linked to implementations. Please read them.
Okay, so then don't use this function. Problem solved. I've given several examples of security vulnerabilities that have occurred because a function like this was not part of the standard library and people look inside
Not just code that creates symlinks. Code that interacts with paths that may contain symlinks must also be careful. But "being careful" is hard and should be part of the standard library. I don't understand why you appear to be against making the lives of Go developers easier. I'm not suggesting that someone force Go users to use this utility, but the usefulness of such a function in the standard library is quite clear IMO...
That works if you want to disable effective use of symlinks, it doesn't work if you want symlinks to work in a way that a user might expect. |
@cyphar you lost me. I can only make judgment about real problems. Your problem seems to be too general for my brain. Sorry. Maybe others will be more helpful. Alex |
Before putting something into the standard library, can we start with a good shared implementation somewhere that everybody can use? |
@bradfitz Yup, I've been working on it and I'll push my implementation to https://github.com/cyphar/filepath-securejoin in the coming days. |
Still waiting for implementation, but this all seems way too complex. |
https://github.com/cyphar/filepath-securejoin is the implementation, I haven't had time to write tests yet. I will add them in the coming days when I have a chance. |
I noticed you use os.Readlink to implement that. But os.Readlink is broken on windows - see, for example, #10935, #18555 and #19922 for details. Alex |
Doesn't that imply that the Go stdlib is broken and should be fixed accordingly (as opposed to my implementation)? Though I would like to have some advice on how to write tests that work sanely between Windows and *nix (especially since |
I am not sure what that implies. Alex |
I understand, I'm just not sure what I'm meant to do with that information... 😕 Are you saying I should fix |
I do not expect you to fix os.Readlink on Windows. I do not know how to fix it myself. Perhaps it is not possible to implement os.Readlink for some situations on Windows. I suspect os.Readlink was inspired by unix readlink - but Windows does not have similar API. Perhaps you can implement filepath.SecureJoin without os.Readlink. Can you use os.Stat instead? os.Stat should work on windows.
Sorry but I do not know. I still do not see any use for filepath.SecureJoin personally. Alex |
@cyphar, I think @alexbrainman's point is that we can't offer a portable function like SecureJoin that's implemented using pieces that aren't currently portable or fully implemented on all operating systems. And we're not interested in adding OS-specific stuff in the standard library. So if SecureJoint can't work everywhere consistently, it's a no go. That doesn't mean you have to fix ReadLink on Windows, though. It just means any inclusion in the standard library requires that the addition works portably everywhere in the same way. |
So,
As I've mentioned in the thread before, I don't believe there is a fundamental reason why SecureJoin cannot work on Windows. I will admit that UNC path semantics are something I am not familiar with (I haven't used Windows in any real capacity for ~8 years) but I don't see why multiple drives or other quirks should cause fundamental issues here. I'm not entirely sure how testing should be done with regards to the Unix equivalent to As an aside, I have ~95% test coverage on |
Actually I just checked and |
I am happy to test your code - I have Windows computers. Just ping me when you need help.
Yes EvalSymlinks is currently broken as os.Stat was broken before CL https://go-review.googlesource.com/#/c/41834/ was submitted. People complained about os.Stat, but no one created issues for EvalSymlinks. I am planing to fix EvalSymlinks in a similar way to os.Stat fix + GetFinalPathNameByHandle (as per https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ suggestion). I am not sure when I will do that. Feel free to use EvalSymlinks if that achieves what you need. Alex |
It seems like the right next step is for the community to converge on a shared implementation, maybe @cyphar's. There doesn't seem to be a compelling reason for the standard library to provide this - it's a very niche kind of thing, and it's unclear to me that it's even possible to do 100% correctly at all from a library (as opposed to from inside the operating system kernel). |
Any chance to move |
@AkihiroSuda, |
As an aside, @rsc is basically correct that doing scoping in a completely correct way is not really possible outside of the kernel. You can do scoping in a way where you can detect changes in the path after you've scoped it (which we don't do at the moment -- because it's very hard and only works on Linux, and also relies on some kernel bugs that really need to be fixed) -- so it could be done in a fail-safe way if an attacker wins in the race against you. |
Currently the standard library does not provide any "simple" helper to avoid
filepath.Join
path traversals. As a result, I've had to include similar functions in several projects:Docker has several implementations that are pretty similar.
runC / libcontainer has one which is used by several other projects.
umoci.
The intention of this addition is to make it much easier for Go developers to do "the right thing" when it comes to path safety. Note that the above examples (intentionally) only operate lexically, but
SecureJoin
should operate on the actual filesystem in order to earn the "secure" label.Docker already has an implementation of symlink evaluation within a root filesystem (called
FollowSymlinkInScope
), which safely resolves all symlink components of a path as though the process was in achroot
. The algorithm used by that code could be used to implementSecureJoin
.There are many Go programs that really need this sort of helper function:
Any container runtime or program that interacts with container root filesystems. If a user controls the root filesystem, they can trick said runtime into evaluating files on the host unless the program implements some sort of rooted evaluation of a path.
Users of
archive/tar
where the archive might be malicious need to be careful of cases where an archive entry's name contains a symbolic link controlled by the archive. Unless the archive's paths are securely joined with the extraction point of the archive, it's possible that such an archive will cause thearchive/tar
user to access files outside the extraction point of the archive.ftp
servers or other file sharing servers that have to operate on user controlled directories. If a path contains user-controlled symlinks they may be able to access paths outside of the context that a file sharing server may expect.Web servers with user-defined paths have similar issues.
With helper methods like these in the standard library, recent issues like
minio
's path traversal vuln would hopefully happen less often. And with a name likeSecureJoin
one would hope that users will be incentivised to use this method (and may in fact become aware of the security issues of usingfilepath.Join
with user-controlled path components).One important thing to note is that symlink races will be out-of-scope for
SecureJoin
, simply because it's not possible to solve that problem within a standard library package.If you'd like I could create a new project that contains just these two helper function implementations so they can be better reviewed for inclusion before I create a PR on gerrit.
The text was updated successfully, but these errors were encountered: