Maybe paths like that and symlinks should be behind flags that are disabled by default? Specially if you are extracting content that you didn't generate.
Maybe paths like that and symlinks should be behind flags that are disabled by default? Specially if you are extracting content that you didn't generate.
I think all tar utilities that are in common usage disable extracting those sorts of paths by default.
It just happens that the go stdlib includes a tar implementation that was written from scratch (the go authors are allergic to re-using any C code and will NIH as much as they can).. and they probably don't want to fix it now because the go1 promise heavily discourages making changes like that, even if security is an exception to it.
As a random data point, since go and rust are often compared, the most popular tar extraction library in rust has a secure default for this: https://docs.rs/tar/0.4.35/tar/struct.Archive.html#method.un...
It's not about NIH, it's about this being a low-level tar parsing library, not a ready-to-use unpack tool or even a library with unpack functionality. I would never expect such a library to do any sort of automagic path mangling for me unless explicitly stated!
I see the same behaviour in libarchive (C library), microtar (C library), and calccrypto/tar (C _tool_ that is vulnerable to this). How would have re-using any C code helped here, if the top three (from Google search results) C tar libraries have the exact same behaviour? The same behaviour is in Python's tarfile, too.
Plus, even with Go1 stability guarantees, there's nothing preventing the Go standard library from adding a struct method like .SafePath() if it's deemed necessary - standard library types regularly get new fields/methods added to them.
> the most popular tar extraction library in rust has a secure default for this
... only on the high-level unpack methods. The low-level unsafe stuff is still there: https://docs.rs/tar/0.4.35/tar/struct.Entry.html (.path, .unpack). And that's basically the API level which Go's archive/tar provides. If that crate didn't provide unpack functionality, it would have been the exact same kind of footgun.
I can appreciate this but I disagree; the defaults should be the exact reverse -- do the safest thing by default and people who "know what they're doing" (famous last words IME) should have a path to do the less safe thing.
I lean towards having programming languages that are developed with the most mistake-prone 20-40% of programmers in mind, rather than the absolute best software engineers, though I certainly understand the hesitance to have languages come with training wheels by default.
a) someone uses this library for validating whether files contained are conforming a given layout, then passes the tarball to tar - differences in behaviour (tar strips leading .., while eg. the Rust module ignores all files with '..', no matter the position in the path) might cause a security issue.
b) silent removal of 'invalid' paths might cause unexpected behavior for when these paths actually are valid and expected (you might not have used ../ in tar, but I have); can imagine one hell of a debugging session coming from that one. I can also imagine codebases that filter tars for malware, and if they happen to skip ../-paths (because of the tar parser silently skipping them), they could let through the tarball unaffected, with some files unscanned, but extractable in GNU tar.
c) silent rewriting of 'invalid' paths might cause inconsistency between the behavior of different tar libraries, or might cause internal inconsistency due to two 'invalid' paths sanitizing to a single valid path; imagine someone relying on ../-stripping behaviour at some point, then that behavior changing due to switching libraries or a rearrangement of the files in the source tarball. Sanitizing is a complex issue, and any kind of universal logic might actually introduce more bugs than it solves.
d) false sense of security for more complex classes of bugs, like extracting a tarball with a symlink pointing outside of the root of the archive. What should the default behaviour be then, and why? Sometimes symlinking to `/etc` is exactly what the user needs, sometimes it's going to cause a bug further down the line. What about relative symlinks? What about cross-filesystem symlinks? What about the setuid bit? If you make users expect the library to do the right thing, it might cause them to think even less about possible security implications of more tricky situations. Writing user-controlled data to the filesystem is _always_ dangerous and there is no right way to do it, it all depends on the context.
I agree that the default behaviour of an _extraction_ library should probably be to sanitize paths. However, I cannot agree that this should be the default behavior of a thin file parser library, like archive/tar. The only thing Go should do, IMO, is explicitly state in documentation that acting on user-supplied tarballs is dangerous and great consideration should be taken before blindly extracting files to the local filesystem.
I can think of dozens of edge cases in all kinds of security controls that remain security weaknesses despite a large chunk of problems being addressed. The point is to address the bulk of the cases and not allow perfect to become the enemy of good.
> a) someone uses this library for validating whether files contained are conforming a given layout, then passes the tarball to tar - differences in behaviour (tar strips leading .., while eg. the Rust module ignores all files with '..', no matter the position in the path) might cause a security issue.
I would argue that any library that does not already fully resolve relative paths to do this is already faulty and this makes it no less safe. To draw an example, if at the time I pass in a relative path to say, "all files should exist under this directory" and provide "/tmp/somedir/../../usr/lib/" as that parameter, I would expect that this already resolves to "/usr/lib/" before doing any processing. This would eliminate the entire problem presented here.
> b) silent removal of 'invalid' paths might cause unexpected behavior for when these paths actually are valid and expected (you might not have used ../ in tar, but I have); can imagine one hell of a debugging session coming from that one. I can also imagine codebases that filter tars for malware, and if they happen to skip ../-paths (because of the tar parser silently skipping them), they could let through the tarball unaffected, with some files unscanned, but extractable in GNU tar.
I agree that silent removal is a problem. There should be errors/warnings attached to indicate that this has been done. This isn't a good counterpoint to actually having a sane default though. The very issue here is we have a clear security bug introduced in part due to the fact that the library does something seemingly unexpected precisely because it does not line up with existing tooling.
> c) silent rewriting of 'invalid' paths might cause inconsistency between the behavior of different tar libraries, or might cause internal inconsistency due to two 'invalid' paths sanitizing to a single valid path; imagine someone relying on ../-stripping behaviour at some point, then that behavior changing due to switching libraries or a rearrangement of the files in the source tarball. Sanitizing is a complex issue, and any kind of universal logic might actually introduce more bugs than it solves.
Again, I'm not advocating it be an invisible process, just that the default should be the safer of the two options for the bulk of cases.
> d) false sense of security for more complex classes of bugs, like extracting a tarball with a symlink pointing outside of the root of the archive. What should the default behaviour be then, and why? Sometimes symlinking to `/etc` is exactly what the user needs, sometimes it's going to cause a bug further down the line. What about relative symlinks? What about cross-filesystem symlinks? What about the setuid bit? If you make users expect the library to do the right thing, it might cause them to think even less about possible security implications of more tricky situations. Writing user-controlled data to the filesystem is _always_ dangerous and there is no right way to do it, it all depends on the context.
What you describe is not a code problem but a programmer / documentation problem, and is really about the idea that fixing one problem does not inherently fix another problem that it's not necessarily aimed at fixing. Clear documentation that outlines the steps that are taken, as well as the option to disable the feature if truly needed (as I suggested) would solve this entirely.
Also, this is a weird argument to make given this already exists with existing tooling. I expect if I name a file "../../../../etc/cron.hourly/evil.sh" and stick it into a tarball, then unpack that tarball to /tmp/ I will get a file named "../../../../etc/cron.hourly/evil.sh" in /tmp/, not a file named "evil.sh" in /etc/cron.hourly/. At the very least I expect to get a heapload of warnings/errors telling me about this and that I should unpack things with "--unsafe-please-never-do-this" or similar flags.
Though also, "we've seen this cause exploits in the wild multiple times, let's just let this footgun sit for 3 years".
They could have at least added a documentation note to the archive/tar page that there's a security issue you have to handle, which is what the unsafe version of the rust unarchive methods do. 3 years of not even updating docs is kinda unfortunate.
Unlike the go library, on the unsafe methods, the rust library includes a note explaining the security footgun.
It also provides 'unpack_in' on entry, so there's a secure and insecure variant.
The go library should at least include a note, but does not. The rust library does not have the same footgun because it is clearly documented and provides a secure alternative.