How to configure and use jailed processes in FreeBSD
Safely Behind Bars
When managing access privileges on Unix, and thus on FreeBSD, you have basically two types of users: those with and those without administrative privileges. This model clearly reaches its limits, however, if you need to create a web administrator, for example. The web admin needs permissions to be able to change certain configuration files or start the HTTP daemon but should not be permitted to change the system configuration.
A solution to this problem would be adding more granularity to access privileges. In FreeBSD, you have the option of working with File Access Control Lists (FACLs) or deploying the Capsicum capability and sandboxing framework. Unfortunately, these approaches substantially increase the administrative overhead, which in turn could have a negative effect on security.
The chroot environment was invented as a way out of this dilemma, but it has some deficiencies from a security standpoint. For example, it is known that an FTP server that allows anonymous access can let users break out of the chroot environment. In the course of time, some improvements were added to chroot, but problems such as the influence of processes outside of chroot were ultimately not resolved.
The Idea Behind Jails
This is where is the concept of the jail comes in: Jails use the positive properties of chroot and at the same time provide absolute protection against manipulation of processes outside the jail. Because a jail relies on a subdirectory tree, a process within the jail cannot access directories and files on the outside. Furthermore, a process inside a jail cannot manipulate the host processes [1]-[5]. Jails thus offer a useful option for providing network services.
However, a jail will not increase the security of a daemon itself. If an FTP daemon has a vulnerability, then it will still have it in the jail, and an attacker could exploit this vulnerability and thus obtain access to the jail, where they might even escalate to root. However, you would still have a major security advantage, in that the attacker could only go about their dastardly deeds in the jail. They would not have access to the host system! In contrast to more granular access management, this approach does not involve much more overhead. Processes are unable to break out of the jail or influence other processes outside of the jail. (See also the "Tools for Managing Jails" box.)
Tools for Managing Jails
A few tools are available to let administrators manage jails on the host system [6]. Here's a brief look:
jail [-i] [-l -u <Username> | -U <Username>] <path> <hostname> <IP address> <command>
: Starts a jail in the specified path, with the specified IP address and the specified command. If the-l
option is set, the environment is set to the default values before the start. The specified command is executed with the rights of the host system (-u
) or with the rights of the user within the jail (-U
).jexec [ -u <username> | -U <username>] <JID> <command> <parameter for command>
: Runs a command with the specified parameters within a jail. It starts with the user rights of the host system (-u
) or with the privileges of the user within the jail (-U
). To uniquely identify the jail, you also need to specify the JID.jls
: Creates a list of all jails running at the moment. The jail identification numbers (JIDs), IP addresses, hostnames, and paths to the jails are output. The JID is a sequential number that clearly identifies the jail. The JID is important forkillall
.killall -j <JID> -<signal> <process>
: Sends a signal to a process within a jail. If you do not specify a process name and signal, the whole jail is killed. "Signal" refers to the usual signals in Unix, such as SIGTERM, SIGKILL, and so on.
Within a jail is a root user, albeit with a very restricted account. Among other things, the user cannot manipulate the host or IP address of the jail. Certain sysctl
MIBs let you restrict root's powers in the jail. It is possible to assign IPv4 or IPv6 addresses (or multiple IPv4 or IPv6 addresses) to a jail, thus creating a routing-enabled jail. The address is passed in on launching the jail. The loopback address (127.0.0.1) and its IPv6 counterpart (::1) can be linked with a jail. This is referred to as an internal jail.
Inside a jail, the user has access to a complete FreeBSD, which means that jails offer a kind of virtualization, like VMware or Xen. As of FreeBSD 8, you can even create jails within a jail. The concept of jails introduced in FreeBSD 4 has long since convinced vendors of other operating systems, too. In Solaris, for example, jails are known as zones.
Restrictions
Within a jail, important limitations exist as a result of the implementation. Remote Procedure Calls (RPCs) do not work in jail operations for security reasons. Thus, you have no way of using NFS within a jail. Daemon processes on the host must be configured very carefully to avoid address conflicts between the jail and the host. Loading and unloading of kernel modules within a jail are prohibited, as is creating device nodes. Mounting and unmounting of filesystems is not possible. Modifying the network configuration, network addresses, or even routing tables is prohibited. Access to raw sockets on the host system is no longer permitted, but within the jail, it is possible to access these types of sockets. Addressing semaphores of the host system is also not allowed.
On closer inspection, most of these restrictions will result in security gains compared with a chroot environment.
Implementing Jails
The system consists of two components: the jail
tool, which is the user interface between the kernel and userland, and kernel code, which is started by system call. The jail
tool requires the following parameters: the path to the jail, the hostname of the jail, the corresponding IP address, and the command to be run.
As you can see in the excerpt from the source code (Listing 1), the jail
function call contains a jail
structure (struct jail
: /usr/include/sys/jail.h
), in which the above-mentioned parameters are stored.
Listing 1
struct jail
01 main(int argc, char **argv) 02 { 03 struct jail j; 04 int i; 05 [...] 06 /* Populate the jail struct */ 07 memset(&j, 0, sizeof(j)); 08 j.version = 0; 09 j.path = <path to jail>; 10 j.hostname = <hostname of jail>; 11 j.ip_number = <IP address of jail>; 12 /* call system call */ 13 i = jail(&j); 14 [...] 15 execv(<command>, ...); 16 [...] 17 exit(0); 18 }
The jail tool locks itself up (i = jail(&j))
and creates a child process in this environment that then runs the desired command using execv()
. As mentioned previously, the jail
kernel function is called:
int jail(struct proc *p, struct *jail_args uap);
This function copies information stored in the kernel memory space uap
(struct jail) and stores some of the information in a C struct named prison
. Then, chroot()
is called with two parameters: the path of the jail and the calling process.
Buy ADMIN Magazine
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Most Popular
Support Our Work
ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.