Yes, very odd. Don't think I'll be upvoting this submission.
>Imagine if an entire OS had all the languages packaged this way so that everything could be “dot slashed” and executed but without actually installing the language to your host operating system.
This has been technically possible for decades, but the disadvantages (startup time, memory use, disk use) outweigh the advantages. (better security, assuming no VM escape bugs, which is an assumption you absolutely can't make, considering the long list of Xen CVEs) Breezily asserting the superiority of this solution without acknowledging any possible downsides is also strange... more press release than blog post.
Containers aren’t VMs. There’s no VM to escape, and Xen has nothing whatsoever to do with containers. Ironically, there’s a really good explanation of this topic which was given by the author at a conference.
Also, this is a write-up about something cool that author discovered. It’s about the fact that dot slash isn’t magic, but rather a feature which can be (ab)used to do something out of the ordinary. Sounds like a blog post to me.
See first comment about how it's a pivot root and if you are going to claim containers are insecure please first capture the flag on https://contained.af or gfy
tl;dr Docker != runc. runc has only had one known partial container breakout that required host coordination, and all of the runc CVEs are useless in rootless containers (which is what jessfraz is using).
Of the vulnerabilities you've listed (while searching "docker") only four were actually runc vulnerabilities, and most required you to be running root inside the container (or required you to have some form of privileged access on the host). Also, runc doesn't use chroot -- we use pivot_root.
You've also just done a full-text search for "docker" and several of the CVEs just have the word and are not actually vulnerabilities in "docker" nor runc. I don't understand what this is meant to show.
In particular, these are the only vulnerabilities in that list that are arguably related to your point.
* CVE-2017-16539 -- a default configuration error allowing a DoS if you're root in a non-user-namespaced container. The PoC in TFA uses user namespaces and thus this attack is pointless. Not to mention you shouldn't run as root anyway, please stop doing that.
* CVE-2016-9962 -- a partial container escape if you have another process joining the container, there were also several kernel bugs found as well as a result. Also requires root, and user namespaces had a fix for this in the next kernel version. Again, stop running stuff as root.
* CVE-2015-3627 -- an information disclosure that requires a malicious rootfs (so you have to have host-side coordination to set up the rootfs before the container starts), and was never present in "runc" just the library that predated it.
* CVE-2015-3629 -- a partial container escape that required significant host-side coordination (you have to change the configuration of the container to be malicious). Also wasn't present in "runc" just the library that predated it.
So, there has only ever been one plausible container breakout in runc to-date and it was CVE-2016-9962 (and it still required using bad practices). You could argue that CVE-2016-3697 was also a runc bug, but that code was only used by Docker at the time (more of a library bug than a container breakout bug). CVE-2015-3629 requires you to effectively create the container that you're going to break out of, so not viable from a "bad code in a container" perspective.
I'm not excusing these bugs, but they should be put in context. CVEs have CVSS scores for a reason -- they are not all created equal and you need to look at each one individually before coming to conclusions.
I do respect the folks who work on Xen, don't get me wrong, but I don't understand this argument for the security of Xen. If I only consider the 11-year-old floppy privilege escalation[1], then runc is already on better footing in terms of "which project had a worse example of a vulnerability resulting in containment escape". Whether this is due to the young age of containers on Linux, or because containers levarage many different security features, I'm not sure.
Source: I'm a maintainer of runc, and I found CVE-2016-9962.
The equiv command in Microsoft Windows is "assoc". You can run it with no args to see what programs are associated with what extensions in Windows. I mention this because of a security concern.
Perhaps it may be worth considering security practices for BINFMT_MISC before people find any sub-optimal defaults. In the past, clever people would exploit some of the default associations that applications would insert on Windows desktops. At some point, desktop file exploring apps may decide to hide "known extensions. The desktop will also leverage this association.
You need to be root to change binfmt_misc's configuration, so it's not analgous to Windows' file associations (which the user can also change).
I also believe that xdg-open (and the XDG scheme) is more similar to Windows' file associations than binfmt_misc, since XDG is more of a user-facing thing.
I believe you need to be a on an admin account in Windows to change global file associations. In fairness though, most people are logged in as an Administrator.
Certainly. I supposed the point I am trying to make is that a lot of old windows behaviors that lead to security incidents are finding their way into non-desktop linux machines. My servers have binfmt mounted, for example. SystemD appears to be bringing in a bit of desktop behavior to both desktops and servers. Maybe this is ok if people are aware?
binfmt_misc has existed for a much longer time than systemd (it was originally added in 1997[1]). It has been used for many features like emulating binaries "natively" by setting up binfmt_misc so that binaries are executed inside QEMU if the architecture is different. It has had many other similar uses for decades.
I'm not a fan of systemd in the slightest, but blaming systemd for an in-kernel facility that has existed since the mid-90s (and isn't even used by systemd to my knowledge) doesn't make any sense.
Too bad the more narrow minded segment of the HN population appears to have buried this one out of spite.. because this is some super pro badass shit, packaged in a hilarious format by jessfraz. Guess it's not everyone's kind of humor, a real tradgedy if you ask me.
Can you imagine a future where you don't have to install runtimes to run things anymore because they are all distributed with everything you need? It would be amazing and magical in many ways. No more reinstalling the OS because things got weird after X went berserk.
Being forced to pollute my perfect, pristine machines with multiple versions of Python, or having to even commit to a single version of anything seems less good than not having to.
I'd rather burn the extra HDD space to avoid risky installs messing up the system.
What you are describing is just a statically linked binary, the world of computing moved away from this well before I started my career but there are pros and cons about it, not only related to disk space.
The world of computing may well be moving back towards this strategy currently. The rise of single-static-binary runtimes (Go, Rust, new Java) and container runtimes (ad hoc, informally-specified static linking for sets of programs) seems to indicate that static linking is far from being consigned to the conceptual rubbish heap.
> Too bad the more narrow minded segment of the HN population appears to have buried this one out of spite.. because this is some super pro badass shit, packaged in a hilarious format by jessfraz. Guess it's not everyone's kind of humor, a real tradgedy if you ask me.
I don't know how other users feel, but I feel really annoyed when I have to read this kind of vague attack on the HNers. The fact is that, fortunately, HN is not an exteded circlejerk of badass things and weird humour.
For the rest of your comment, I haven't read the article yet, but the phrase "a future where you don't have to install runtimes to run things anymore because they are all distributed with everything you need?" makes me frighthened. That would mean that the same piece of code but slightly different repeated many times in a computing environment. Why would I need multiple versions of Python 3.5 or Ruby 2.3 is beyond me, except for development, where there are tools to manage multiple versions of interpreters and runtimes. And when Python 3.5 is declared vulnerable to the next big security vulnerability, switching to the hypothetical Python 3.5.1 would be a breeze, and I'd be secure; whereas with every package having its stuff vendored within its opaque format, you never know what's outdated and/or vulnerable.
I love the idea of executable single-file containers. I'd take it even farther. I've recently discovered a way to make polyglot scripts that are simultaneously executable on Linux, Mac, and Windows[1]. It would be pretty easy to embed binaries in these scripts, and by simply embedding a binary for each OS you'd have a cross-platform binary single-file application.
Of course three copies of the binary is not ideal, but it shouldn't even be necessary. With some deep hacking the platform-specific code could be minimized and the rest of the binary could be shared. Wouldn't it be fun to have truly platform-independent binary executables?
FWIW, you don't need to fiddle around with binfmt_misc (which requires root permissions) to execute a Go program as a script. The following shebang works:
///bin/true; exec /usr/bin/env go run "$0" "$@"
See https://stackoverflow.com/a/30082862/334761 for detailed explanation what's going on there. The only downside is that all exit codes != 0 get mapped to exit code 1 by `go run`. It will show the original exit code on stderr instead.
The downside of that approach is that it only works because shells try to interpret files if a simple execve(2) fails with ENOEXEC. If you wanted to use your fancy .go script with a program that just does execve(2) you'd be in trouble.
That's quite cool, but minor point of pedantry: that's not a shebang. A shebang is interpreted by the operating system, which uses it to find and launch an interpreter for a file.
Compared to that, the example snipped you provided is doing many more, and different things:
1. Launches a shell language interpreter: usually the system default POSIX-compliant sh, but could (rarely) be something else. Compared to "read the first line, and if it's a shebang launch python or whatever" in the kernel, this is massively complicated: it starts another whole runtime. Run "strace" on a file that just does "///bin/true; sleep 1000" to see exactly how much is happening just for that shell launch.
1. Executes /bin/true. It's probably present everywhere, and probably the right program, but still an assumption.
2. Evaluates a bunch of string slinging statements in the shell to interpolate arguments, then replaces itself via "exec".
3. Launches /usr/bin/env (hopefully the right program, and hopefully in the right place) to find the go interpreter.
4. env (hopefully) also calls "exec" again and then launches the go run command to start the compilation/execution sequence.
That's a lot of stuff. Pretty garden-variety stuff, but still a lot of additional complexity. A shebang adds none of it, though "trampolining" shebangs (which themselves are just programs to do basically this) can reintroduce some internally.
That's the beauty of the shebang as a trick for truly interpreted languages: it's extremely direct and simple as a means of getting your code executing on your desired runtime.
Or another cool idea use .preinit_array for shared bins and .init_array for static bins and inject a .seccomp, .unshare, etc section that gets called in the preinit, init. This way you can support both shared and static bins with a tiny size increase.
35 comments
[ 3.3 ms ] story [ 79.6 ms ] thread...
>Imagine if an entire OS had all the languages packaged this way so that everything could be “dot slashed” and executed but without actually installing the language to your host operating system.
This has been technically possible for decades, but the disadvantages (startup time, memory use, disk use) outweigh the advantages. (better security, assuming no VM escape bugs, which is an assumption you absolutely can't make, considering the long list of Xen CVEs) Breezily asserting the superiority of this solution without acknowledging any possible downsides is also strange... more press release than blog post.
Also, this is a write-up about something cool that author discovered. It’s about the fact that dot slash isn’t magic, but rather a feature which can be (ab)used to do something out of the ordinary. Sounds like a blog post to me.
https://nvd.nist.gov/vuln/search/results?adv_search=false&fo...
https://nvd.nist.gov/vuln/search/results?adv_search=false&fo...
Of the vulnerabilities you've listed (while searching "docker") only four were actually runc vulnerabilities, and most required you to be running root inside the container (or required you to have some form of privileged access on the host). Also, runc doesn't use chroot -- we use pivot_root.
You've also just done a full-text search for "docker" and several of the CVEs just have the word and are not actually vulnerabilities in "docker" nor runc. I don't understand what this is meant to show.
In particular, these are the only vulnerabilities in that list that are arguably related to your point.
* CVE-2017-16539 -- a default configuration error allowing a DoS if you're root in a non-user-namespaced container. The PoC in TFA uses user namespaces and thus this attack is pointless. Not to mention you shouldn't run as root anyway, please stop doing that.
* CVE-2016-9962 -- a partial container escape if you have another process joining the container, there were also several kernel bugs found as well as a result. Also requires root, and user namespaces had a fix for this in the next kernel version. Again, stop running stuff as root.
* CVE-2015-3627 -- an information disclosure that requires a malicious rootfs (so you have to have host-side coordination to set up the rootfs before the container starts), and was never present in "runc" just the library that predated it.
* CVE-2015-3629 -- a partial container escape that required significant host-side coordination (you have to change the configuration of the container to be malicious). Also wasn't present in "runc" just the library that predated it.
So, there has only ever been one plausible container breakout in runc to-date and it was CVE-2016-9962 (and it still required using bad practices). You could argue that CVE-2016-3697 was also a runc bug, but that code was only used by Docker at the time (more of a library bug than a container breakout bug). CVE-2015-3629 requires you to effectively create the container that you're going to break out of, so not viable from a "bad code in a container" perspective.
I'm not excusing these bugs, but they should be put in context. CVEs have CVSS scores for a reason -- they are not all created equal and you need to look at each one individually before coming to conclusions.
I do respect the folks who work on Xen, don't get me wrong, but I don't understand this argument for the security of Xen. If I only consider the 11-year-old floppy privilege escalation[1], then runc is already on better footing in terms of "which project had a worse example of a vulnerability resulting in containment escape". Whether this is due to the young age of containers on Linux, or because containers levarage many different security features, I'm not sure.
Source: I'm a maintainer of runc, and I found CVE-2016-9962.
[1]: https://xenbits.xen.org/xsa/advisory-133.html
Suppose that would admit not knowing something... certainly not something we should be encouraging!
i.e.
Perhaps it may be worth considering security practices for BINFMT_MISC before people find any sub-optimal defaults. In the past, clever people would exploit some of the default associations that applications would insert on Windows desktops. At some point, desktop file exploring apps may decide to hide "known extensions. The desktop will also leverage this association.I also believe that xdg-open (and the XDG scheme) is more similar to Windows' file associations than binfmt_misc, since XDG is more of a user-facing thing.
I'm not a fan of systemd in the slightest, but blaming systemd for an in-kernel facility that has existed since the mid-90s (and isn't even used by systemd to my knowledge) doesn't make any sense.
[1]: https://elixir.bootlin.com/linux/v4.15.7/source/fs/binfmt_mi...
Can you imagine a future where you don't have to install runtimes to run things anymore because they are all distributed with everything you need? It would be amazing and magical in many ways. No more reinstalling the OS because things got weird after X went berserk.
Being forced to pollute my perfect, pristine machines with multiple versions of Python, or having to even commit to a single version of anything seems less good than not having to.
I'd rather burn the extra HDD space to avoid risky installs messing up the system.
I don't know how other users feel, but I feel really annoyed when I have to read this kind of vague attack on the HNers. The fact is that, fortunately, HN is not an exteded circlejerk of badass things and weird humour.
For the rest of your comment, I haven't read the article yet, but the phrase "a future where you don't have to install runtimes to run things anymore because they are all distributed with everything you need?" makes me frighthened. That would mean that the same piece of code but slightly different repeated many times in a computing environment. Why would I need multiple versions of Python 3.5 or Ruby 2.3 is beyond me, except for development, where there are tools to manage multiple versions of interpreters and runtimes. And when Python 3.5 is declared vulnerable to the next big security vulnerability, switching to the hypothetical Python 3.5.1 would be a breeze, and I'd be secure; whereas with every package having its stuff vendored within its opaque format, you never know what's outdated and/or vulnerable.
Of course three copies of the binary is not ideal, but it shouldn't even be necessary. With some deep hacking the platform-specific code could be minimized and the rest of the binary could be shared. Wouldn't it be fun to have truly platform-independent binary executables?
[1] https://gist.github.com/jdarpinian/6860ddfd92b5b458a20ab6055...
What about packaging - e.g. this file having a proper icon on Windows? What about distribution via the app market platforms?
How do I go about upgrading the application?
Compared to that, the example snipped you provided is doing many more, and different things:
1. Launches a shell language interpreter: usually the system default POSIX-compliant sh, but could (rarely) be something else. Compared to "read the first line, and if it's a shebang launch python or whatever" in the kernel, this is massively complicated: it starts another whole runtime. Run "strace" on a file that just does "///bin/true; sleep 1000" to see exactly how much is happening just for that shell launch.
1. Executes /bin/true. It's probably present everywhere, and probably the right program, but still an assumption.
2. Evaluates a bunch of string slinging statements in the shell to interpolate arguments, then replaces itself via "exec".
3. Launches /usr/bin/env (hopefully the right program, and hopefully in the right place) to find the go interpreter.
4. env (hopefully) also calls "exec" again and then launches the go run command to start the compilation/execution sequence.
That's a lot of stuff. Pretty garden-variety stuff, but still a lot of additional complexity. A shebang adds none of it, though "trampolining" shebangs (which themselves are just programs to do basically this) can reintroduce some internally.
That's the beauty of the shebang as a trick for truly interpreted languages: it's extremely direct and simple as a means of getting your code executing on your desired runtime.