6 ms·
chroot is a very simple facility that can provide meaningful security. So yes, it can be used as a security feature. People just need to understand its limita
chroot is a very simple facility that can provide meaningful security. So yes, it can be used as a security feature. People just need to understand its limitations. No, it won't prevent kernel exploits. Yes, root can escape it (1996 called? http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libc/sys/chroot.2.diff?r1=1.2&r2=1.3 http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libc/sys/ch... ) It's not going to protect you from a malicious or vulnerable process running as root; you're already rooted.
So once you leave out the exploits that require root, you're mostly left with the issue of ptrace, unless you also have another malicious process running on your system. Even if it ptrace couldn't help a process out of a chroot, it is still a big risk, so use good privsep hygiene (no shared UIDs outside the chroot), or disable it entirely, if it's not needed.
Worth noting, OpenBSD defaults to disallowing global ptrace, i.e. you can only trace child processes, such as those started by gdb.
I appreciate the depth of the commentary here (one of the reasons why I keep coming back to HN). Where would you encourage me to read more about best practices like you've just described?
What is the next, light-weight but more hardened solution after chroot? On *BSD, it's probably `jail`. On Linux, it's probably runC?
Only FreeBSD has jails, but it's not quite the same thing. Jails are more like containers. The evolution of the chroot concept on OpenBSD is pledge. On Linux it's seccomp.
runC creates Linux containers. Containers and VMs are not security solutions, rather system administration and provisioning solutions.
chroot() was originally implemented as a type of container. It allowed you to run programs (especially before the adoption of dynamic linking) from a private directory tree when building, installing, and testing software, without risking overwriting the real system. Basically, it's purpose was just like Docker.
But the use of chroot() as a security solution is different. You don't usually mount /dev or /proc in a chroot jail as that would defeat the purpose. The purpose is reducing the code surface of kernel and other privileged code available to software that might be abused to tickle bugs and elevate privileges.
Containers don't reduce code surface area at all, not to the kernel, nor usually to stock SUID and SGID binaries. All the same devices files _and_ system calls are still available, as well as typical SUID account and system management binaries. It might have security related benefits in the sense that you can, e.g., remove application-specific SUID binaries from some containers but not others. But that's no different than chroot, and so it's not really an improvement over chroot in that sense. More importantly, the code implementing Linux containers is complex and has opened up far more kernel bugs than it's closed. There are good reasons why most kernel developers, including even Linus for long time, resisted attempts to expand the chroot namespace model. In terms of security you'd be better off recompiling your Linux kernel without all the container namespace code.
VMs seem better security-wise until you realize a VM's interface to the host kernel is through device drivers. And the device driver model of monolithic Unix kernels is a security nightmare. Ironically, it wasn't until VMs came out that hackers had such easy and broad access to manipulating device driver code on the host. Unix's "everything is a file" approach always limited that ability. Where that approach broke down, like with ioctl, was always a hotbed of exploits.
Blackhats haven't had much trouble taking control of host kernels from guest VMs, although because that path is less well-trodden it's not something script kiddies are typically capable of. As VMs get simpler and move away from hardware and driver emulation, the security benefit of VMs will improve. But their utility as sandboxes for code you don't control will also be diminished. In many circumstances seccomp and pledge will remain the better option, especially from a developer's standpoint.
Note that SELinux doesn't limit access to system calls, and only indirectly limits code surface by making it easier to restrict access to logical resource objects. SELinux wouldn't have prevented or hindered in any way the recent recvmmsg() remote root exploit Linux. It's function is quite different than pledge and seccomp. SELinux can be used in tandem with seccomp. But given the real-world threat model out there, if you were to only rely on one, then seccomp is far more valuable, IMO.
> Only FreeBSD has jails, but it's not quite the same thing. Jails are more like containers. The evolution of the chroot concept on OpenBSD is pledge. On Linux it's seccomp.
> runC creates Linux containers. Containers and VMs are not security solutions, rather system administration and provisioning solutions.
As someone who maintains runC, I disagree with you quite vehemently. Containers are a security system. Yes, they've had their fair share of problems but that doesn't mean that
> chroot() was originally implemented as a type of container. It allowed you to run programs (especially before the adoption of dynamic linking) from a private directory tree when building, installing, and testing software, without risking overwriting the real system. Basically, it's purpose was just like Docker.
That is a very narrow view of what the purpose of containers is.
> But the use of chroot() as a security solution is different. You don't usually mount /dev or /proc in a chroot jail as that would defeat the purpose.
That's not correct. Many programs require /proc, /dev or /sys to function. Most of the uses I've seen of chroot do mount stuff like that in the rootfs. If you're an unprivileged user you haven't really increased the attack surface.
And even if you don't mount those filesystems, chroot doesn't namespace networking or process trees -- so those avenues of attack still exist. That _alone_ makes containers more secure than chroot.
> Containers don't reduce code surface area at all, not to the kernel, nor usually to stock SUID and SGID binaries. All the same devices files _and_ system calls are still available, as well as typical SUID account and system management binaries.
This is not necessarily true, given the existence of no_new_privs, seccomp and user namespaces in Linux. Not to mention that all of the runtimes I know of do not just mount /dev (instead it's a tmpfs with bindmounts for the devices needed by containers).
And not to mention that chroot is just a weaker version of mount namespaces (which is a far more powerful primitive for isolating processes).
> But that's no different than chroot, and so it's not really an improvement over chroot in that sense. More importantly, the code implementing Linux containers is complex and has opened up far more kernel bugs than it's closed.
That's just FUD. Yes, the way Linux went about creating namespaces wasn't the best way, but modern Linux containers are more secure than chroot. Chroot doesn't even protect against messing around with other processes on your host system! It doesn't protect against accessing things outside of your current root (pivot_root actually fixes that problem -- but you can't use it without using mount namespaces).
Mount namespaces and user namespaces have been a ripe area for Linux exploits. Many of the local privilege escalation exploits published since the shift to containers involved this code--either bugs in the code or because of unexpected, unintended consequences.
The reason why only root can call chroot is because the ability to change the namespace can cause havoc with other privileged code not expecting the namespace to be mutable in certain critical respects. chroot originally didn't require root permissions, but then people figured out all the security problems it caused in the Unix privilege model. And because it was so difficult to fix comprehensively given that model, the solution adopted was to limit it to root.
That difficulty is why more sophisticated and malleable namespace solutions were avoided for so long. And the recent history of exploits in Linux has only proven the original wisdom of that. Adding more complexity and degrees of freedom to namespaces (the ability to create isolated namespaces) only exacerbates those problems. And it's triply stupid to allow non-root users to manipulate namespaces like that, which is what Linus finally allowed and which some Linux distributions began allowing by default before more exploits were published. It's an approach that makes sense in many other contexts, but not in the context of the Unix process and privilege model. It works very well in Plan 9, but Plan 9 was carefully designed to avoid the same design decisions in Unix that make mutable namespaces so problematic.
seccomp doesn't save containers. It has zero to do with containers. It's a simple mechanism to limit syscalls by syscall number. In an abstract sense that's a namespace related issue (syscalls are in a shared namespace, and syscall numbers are paths into that namespace), but it's a real stretch to see that as a facet of containerization.
Containers can help people develop security solutions. I won't dispute that. They're a tool, just not one that is usefully scene as a principally security tool.
Regarding PID namespacing and signaling, that's just FUD about chroot jails. The PID namespace is indeed unaffected by chroot, but in a chroot jail you setuid to an unprivileged user. That's part of the protocol of a chroot _jail_. An unprivileged user cannot directly send signals to other processes without the same UID. On systems like OpenBSD every service is assigned a unique UID. On Linux the nobody user is too often abused, but major services like databases are still usually assigned a unique UID and so cannot be signaled by a chroot-jailed, unprivileged process. It's even possible to assign random UIDs to processes (e.g. among slave processes), as the kernel doesn't care what the integer value is as long it fits within some predefined range.
The chroot jail protocol is hackish, but I can't recall a bug in Linux or any other Unix that permitted an unprivileged user process to directly signal processes of a different user. Nor can I recall a bug in kernel chroot implementations that violated the defined semantics, at least not where Linux's new namespacing features weren't involved.
Given the reality of security issues on modern systems--that kernels are so complex that there's an endless bounty of exploits at the user-kernel interface, the chroot jail is a singularly simple and effective solution that addresses that problem, at least before pledge, seccomp, and capsicum came onto the scene.
Regarding the accusation of FUD regarding the security of new namespaces:
https://lwn.net/Articles/671641/
https://lwn.net/Articles/543273/
This blurb from the first article is apt:
All of the myriad interactions between various kernel
subsystems (capabilities, namespaces, security modules,
filesystems, /proc, and so on), especially given the "no
regressions for user space" policy, make these kinds of bugs
pretty much inevitable. One could imagine tossing out a
bunch of currently expected behavior to simplify the
complexity of those interactions, but that is not going to
happen, so these types of problems will crop up from time to
time.
That succinctly explains the crux of the issue. More flexible namespacing solutions (which is what containers are--a collection of advanced namespacing features) is fundamentally at odds with what's needed for improved security--simpler code and simpler semantics.
Containers provide lots of flexibility for users and developers. And with that flexibility you can devise some interesting and clever privilege separation solutions. But they're fundamentally relying on a foundation that was made significantly more complex and more bug-prone for the sake of providing that flexibility.
That flexibility exists--it must exist--for the benefit of solving system administration and provisioning chores. And that's why I argue containers aren't fundamentally a security solution, they're a DevOps solution. A powerful and effective one. But it's simply not in the same league as a chroot jail, pledge, seccomp, or Capsicum. None of those things provide any additional privileges nor design flexibility. They're _strictly_ mechanisms for taking away privilege and flexibility. And they're strictly intended to reduce the code interface surface between user processes on the one hand, and kernel and privileged processes on the other. That's their sole function, which means they can, at least theoretically, be kept simple and easy to maintain in the kernel.
EDIT: Technically Capsicum does provide new design flexibility with the introduction of file descriptors as handles to processes. I don't think any _stock_ system implements all of Capsicum, but both Linux and FreeBSD now support process descriptors. Lack of process descriptors was always a huge oversight in the Unix API model, and it's ridiculous it took so long to get them, and still only for Linux and FreeBSD at this point.
Can you explain why you think that seccomp is even related to chroot? The two are very different technologies with very different goals (in fact, chroot's original goal was _not_ security -- it was a clever hack by Bill Joy in the 80s to allow compiling BSDs on older BSDs...).
Even if we pretend that chroots are designed for isolation, seccomp is not. Seccomp is designed to be part of a sandboxing system. Capsicum is a new way of dealing with processes that require privileges. And pledge is sort of like seccomp but with questionable granularity.
The point is that sure you can call them "security systems" because they are all based on the idea of blocking things, but that's like saying that bananas and humans are all carbon-based life -- it's technically accurate but not very helpful. Especially since you then go on to talk about the isolation aspects of chroot -- which is something that seccomp, pledge and Capsicum do not give you.
> The evolution of the chroot concept on OpenBSD is pledge.
I think that's a dangerous way to look at it, and thinking of some named thing as a "solution" to making shit secure is exactly the kind of thinking that led people to treat chroot as a "jail" or "sandbox" and then be surprised that it doesn't actually contain their malicious process that runs as root and has access to various system resources.
Pledge does one thing, chroot does another thing, unix users and permissions do yet another thing, pf the firewall does its own thing, and exploit mitigations do yet another thing. Doing privilege separation right will help you get most out them all. If we wanted to view pledge as an evolution of something, that something would probably be systrace; both exist to restrict syscalls, and both exist(ed) on OpenBSD, but the design of pledge is quite different.
These are features that complement each other, and the more your applications employ them, the better off they are. None of them is supposed to be a single-switch solution that says "make this process secure". It's doesn't work that way, and it can't work that way. For instance, if you try to use chroot without privsep, it is quite likely that you end up having to mount things or add libraries and binaries or other files under the chroot, which does carry obvious risks.
On the other hand, when it's done well, the use of chroot in a privsep'd process is invisible to the user, and there is no need to build a "mini-system" inside the chroot. Unfortunately it never seemed to be a particularly popular in the Linux world where everyone seems to be thinking "sandbox" or something more complicated.
So when people say that most uses of chroot they've seen requires mounting /proc or doing other dangerous stuff in a chroot, they've probably just not noticed the quite-a-few uses of chroot that happen automatically under the hood...
I agree that they complement each other. Pledge isn't an evolution of chroot, it's an evolution of chroot jails. And I meant evolution in the sense of continuing the approach of implementation centric, non-policy-based security limits orchestrated by the developer, not the system administrator in terms of access to logical resources. Capsicum is also similar in that respect.
And I meant solution in the sense that it's a solution to limiting namespace visibility and visibility of vulnerable software interfaces. It's not a security "solution" in the sense of it guarantees, on its own complete safety. Security is a process, not a product. But in distinction to containers it is a security solution, just like a gun is a self-defense solution. Whereas containers are no more a security solution than metallurgy is a self-defensive solution. A container by itself doesn't limit anything, where as a chroot jail, however imperfectly done, does create a concrete a defensive posture by its very nature. Just like ASLR despite ROP attacks.
I should have been more careful in making those distinctions. It's especially important because chroot jails are something of a hack given the origin and original purpose of the chroot syscall, which leaves it open to lazy criticism. But it's still an effective hack as far as its limits go. Criticisms of chroot jails, and discussion of exploits, usually boil down to not properly creating the chroot jail, not because there's no way to ever create the chroot.
That it can be difficult to create the jail properly and to keep from breaking it later on as an application becomes more complex, such as by accidentally keeping a directory descriptor open across the jail creation, or mounting /proc, is a valid criticism. But as you say it's not as difficult as people make it seem. Setting up seccomp jails isn't trivial, either. With chroot, seccomp, SELinux, and any security method it's always going to be the case that some innocuous-seeming capability or resource preserved out of convenience turned out to be a portal for breaking out of the jail. And convenience is always a concern, otherwise we'd always limit processes to nothing but read(2) and write(2), sort of like the original seccomp that few people could make practical use of.