In May I committed a new feature to FreeBSD-current (it will be in the FreeBSD 15 release, I have no plans to merge this to 14). This feature is called “Service Jails”. When you enable it, it takes a service (something which is started by an rc-script at boot or by hand via service(8)) and starts it in a jail(8). It can do this with any service, and with no more than 2 lines of configuration.
For those which don’t know, a jail is some kind of container technology. We have this technology since 1999 (so it pre-dates Docker by 14 years). It served as an inspiration for Solaris zones.
Too good to be true?
Containerizing some software with only 2 lines of code (if a service is “Service Jail ready”, only one line) sounds amazing, and is in no way comparable to a docker file or a normal jail config. This sounds a bit too good to be true. And that is correct. The service Jails framework is somewhere between a fully isolated container, and no containerization at all.
The biggest difference is that a Service Jail has full access to the entire filesystem of the host (or parent jail), except for chflags(1). This means if your service runs as root in the Service Jail, and it is compromised, the attacker is able to read your password database, and modify nearly any file content (and as such on next boot anything can happen). The only exception to this is, if this service already has provisions to run in a chroot and this is enabled. In that case only files in the chroot can be modified by an attacker.
What are the benefits?
Compared to running a service on the host itself without putting it into a jail you have created yourself and tailored to only the software you need, you have the benefit of limiting what the software (or an intruder) is able to do, but not the benefit of a minimal software install.
When you enable a Service Jail for a particular service which is not Service Jail ready and you do not provide a Service Jail config, the service is started inside a jail without any network access and full access to the filesystem. A Service Jail ready service which normally needs network access, can be limited by a custom config to not have any network access at all (or only to IPv6 and not IPv4, or vice versa). This means you can limit this software to not access the network despite not having it inside a VM.
The Service Jail also doesn’t allow to:
- mount filesystems (and on purpose there is no provision so far to optionally allow this),
- open raw sockets (can be enabled),
- open sockets of protocol stacks that have not had jail functionality added (IPv4, IPv6, local UNIX sockets and routing stuff are jail-aware) to them (can be enabled),
- lock/unlock physical pages in memory (can be enabled),
- use System V IPC facilities (can be enabled),
- use debugging facilities for unprivileged processes,
- see processes from the host or other jails,
and all the other stuff which is prohibited in jails by default.
When you enable network access (IPv4 and/or IPv6) for a particular service, the Service Jail inherits all the IPs of the host (or parent jail). This means you can not run two services which want to listen on the same port by this. But as you can limit it to get only access to IPv4 and not IPv6 (and vice versa), it means you could run two different services for IPv4 and IPv6, or you can test scenarios where only one IP stack is available but have the host itself configured for dual-stack network access.
How to get most out of Service Jails?
With the possibility to allow unprivileged users to open privileged ports (sysctl net.inet.ip.portrange.reservedhigh=0
) and having the service started as non-root (sysrc servicename_user=MyServiceUser
), a Service Jail provides a very good benefit for a simple one-line config change (sysrc servicename_svcj=YES
).
In this case filesystem access is restricted to what this particular user is able to read / write, only processes started by the service are visible to the service, and all the other jail-restrictions apply. An intruder may as such do bad things to this particular service, but not to other services on the system.
For a read-only webserver this may mean an attacker may be able to modify some log files, but can not see other processes running on the system and deducting from them what is the most valuable next step in the attack.
For a read-only php-fpm service it may mean that the attacker can run some in-memory code to spawn a botnet, but not compromise other parts of the host or access System V memory locations of a database (if the php-fpm service is not configured to allow access to System V resources).
Further reading
The rc.conf(8) man page contains more info about what can be enabled for Service Jails (search for “svcj” and “SERVICE JAILS”). The rc scripting article explains how to make a service Service Jails ready, and the FreeBSD handbook contains a section how to enable and configure Service Jails.
What’s next?
The base system services are either made Service Jails aware, or configured to not run inside a service jail (e.g. a fsck doesn’t make sense to run in a jail). Not all of the services are tested with Service Jails. Give them a try and send a bug report in case something doesn’t work.
The FreeBSD ports collection has about 1500 services. I’ve either committed already some patches or send patches to the maintainers for some of the high profile ports (like webservers, databases, DNS servers, …) to make some of them Service Jails ready, but there are too much services to do that all myself. Feel free to submit some patches for them.