Chroot: The Magical Healing Powers of the Original Linux Virtualization Tool

Chroot lets you create isolated environments

Chroot is a powerful yet often overlooked tool in the Linux administrator‘s toolbox. It was one of the earliest forms of virtualization on Unix and Linux systems, with roots dating back to Version 7 Unix in 1979. Over 40 years later, it‘s still a valuable utility for tasks ranging from system recovery to testing and security.

At its core, chroot provides a way to run a command or interactive shell with a special root directory, essentially creating an isolated filesystem environment. When you change the apparent root directory with chroot, the running process is limited to accessing only those files and directories located under the new root. This "chroot jail" safely restricts what the process can see and access on the host system.

How Chroot Works

Under the hood, chroot relies on the chroot() system call to change the root directory for the calling process. This system call is available in all major Unix and Linux variants, and is also exposed through the chroot command line utility.

Here‘s the signature of the chroot() system call in C:

int chroot(const char *path);

When a process calls chroot(), the kernel changes its idea of the root directory (represented by /) to the specified path. From then on, any file paths accessed by the process are treated as being relative to this new root. The process is effectively placed in a "chroot jail" and can no longer see or access files outside this directory tree.

It‘s important to note that chroot only isolates the filesystem, not other system resources. A chrooted process still has access to the same memory, processes, devices, and privileges as it did before entering the chroot. For more complete isolation, you need to combine chroot with other techniques like namespaces and cgroups (which is how containers work).

Another key thing to understand is that chroot doesn‘t actually change the running process‘s current working directory. So after a chroot() call, you typically need to also call chdir("/") to move into the new apparent root directory. This is why most invocations of the chroot command look something like this:

chroot /path/to/newroot /bin/bash -c "cd / && /bin/bash"

This changes root to the specified directory, switches into that directory, and then launches a new interactive shell to work inside the chroot environment.

Rescuing Systems and Recovering Accounts with Chroot

One of the most valuable uses for chroot is in recovering access to a system when you‘ve been locked out. Perhaps you‘ve forgotten the root password on a system, or fat-fingered an important config file and lost access. In cases like these, chroot can be a lifesaver.

The basic process goes like this:

  1. Boot the system from live media like a USB drive or DVD
  2. Mount the root filesystem of the installed system to a directory like /mnt
  3. Use chroot to change the apparent root to this /mnt directory
  4. You now have full access to the installed filesystem and can add accounts, change passwords, fix configuration files, etc.

For example, let‘s say you‘re locked out of a system and need to reset a forgotten root password. After booting into a live environment and mounting the root filesystem to /mnt, you can chroot in and use the passwd command to set a new password:

$ sudo chroot /mnt
# passwd 
New password: 
Retype new password:
# exit

And just like that, you‘ve recovered access to the system! The chroot environment allowed you to run the passwd command as if you had booted normally into the installed system.

You can use the same basic technique to recover deleted files, fix broken package databases, edit configuration files, and much more. Chroot gives you a way to perform system maintenance and recovery even when you can‘t boot normally.

Setting Up Isolated Test Environments

Another handy use for chroot is setting up isolated environments for testing and development. By installing an application or service inside a chroot, you can freely experiment without worrying about conflicts or damage to the host system.

Creating a chroot environment involves a few steps:

  1. Create a new directory that will serve as the root for the chroot
  2. Install the necessary files and libraries into this chroot directory, including any application files
  3. Use the chroot command to launch a shell inside this new environment

For example, let‘s say you want to run multiple instances of the Apache web server for testing, without interfering with the host system‘s web server. You can set up chroot environments like this:

$ mkdir /chroots/apache1 /chroots/apache2
$ dnf --installroot=/chroots/apache1 install httpd
$ dnf --installroot=/chroots/apache2 install httpd
$ chroot /chroots/apache1 /bin/bash

Inside the chroot, you can now configure and launch the Apache web server independently of the host system or other chroots. Each chroot has its own separate set of config files, log files, and web content.

The isolated nature of chroots makes them useful for testing upgrades, trying out new configurations, and debugging problems without risk to production systems. You can even install different Linux distributions or incompatible library versions in a chroot to fit the needs of a particular application.

On Debian and Ubuntu systems, you can use the debootstrap tool to quickly set up chroot environments based on different releases:

$ sudo debootstrap --variant=buildd --arch amd64 bionic /chroots/ubuntu-bionic

This single command downloads and installs a minimal Ubuntu 18.04 (Bionic) system into a chroot at /chroots/ubuntu-bionic. You can then chroot into this environment and install additional packages as needed.

Running 32-bit Apps on 64-bit Systems

Another neat trick chroot can do is allow you to run applications compiled for a different CPU architecture than the host system. A common example is running legacy 32-bit Linux programs on a 64-bit host.

To set this up, you create a chroot environment containing the 32-bit libraries and supporting files needed by the application. The chroot serves as a compatibility layer, translating between the 32-bit app and the 64-bit host system.

On an RPM-based system like Fedora, you can use the mock tool to automate much of this process. Mock creates chroot environments using configuration files that specify which packages and architectures to include.

For example, to create a 32-bit chroot on a 64-bit Fedora system, you first install the 32-bit versions of key packages like glibc and libstdc++. Then you use mock to set up the chroot:

$ dnf install glibc.i686 libstdc++.i686
$ mock --root fedora-32 --init
$ mock --root fedora-32 --install myapp.i686.rpm
$ mock --root fedora-32 --shell

Inside the chroot shell, the 32-bit app can now run successfully. The app thinks it‘s running on a 32-bit host, even though the outer host system uses 64-bit. This setup also works for other CPU architecture combinations, such as running PowerPC binaries on an x86 host.

Securing Network Services in a Chroot Jail

Chroot also has a long history of being used as a security tool. By running a network service inside a chroot jail, you can limit the damage that can be done if the service is compromised by an attacker.

The basic idea is to create a minimal chroot environment that has only the files and directories needed for the service to run. That way, if an attacker breaks in, they are trapped inside the jail with no access to the rest of the host system.

Popular services that are commonly chrooted for security include BIND, Apache, Sendmail, and Postfix, among others. Many distributions even ship separate packages intended for running these services in a chroot, such as bind-chroot and httpd-chroot.

To set up a chroot jail for a network service, the process goes something like this:

  1. Create a new directory to serve as the chroot
  2. Copy in the executable and supporting libraries needed to run the service
  3. Create an empty directory hierarchy with familiar paths like /etc, /var, and /tmp
  4. Set file permissions and ownership as needed
  5. Configure the service to run inside the chroot
  6. Launch the service, now jailed inside the restricted environment

Here‘s an example of configuring BIND to run in a chroot:

$ dnf install bind-chroot
$ cp -a /var/named/chroot/var/named/master /var/named/chroot/var/named/slave
$ chown -R root:named /var/named/chroot/var/named
$ sed -i ‘s|pid-file "/run/named/named.pid";|pid-file "/var/run/named.pid";|‘ /etc/named.conf
$ sed -e ‘s|/usr/sbin/named|/usr/sbin/named -t /var/named/chroot|‘ /usr/lib/systemd/system/named.service
$ systemctl daemon-reload
$ systemctl restart named

This installs the BIND chroot package, copies over the necessary zone files and configs, adjusts file permissions and the PID file location, and then updates the systemd unit to launch BIND inside the chroot. The BIND process is now confined to the /var/named/chroot directory tree.

While not foolproof, chroot jails add an important layer of security around vulnerable network services. An attacker who gains control of the jailed process still has to find a way to break out of the chroot to compromise the rest of the host system.

Some additional security best practices when using chroot:

  • Run the chrooted process under its own dedicated user account, not as root
  • Mount filesystems like /proc and /sys read-only or not at all inside the chroot
  • Use AppArmor or SELinux to further confine the chrooted process
  • Keep the chroot environment as minimal as possible (no compilers, admin tools, etc.)

Following these guidelines can turn a basic chroot jail into a fairly robust security boundary for isolating risky network services.

Performance and Overhead

Because chroot operates at the kernel level, the performance overhead is generally quite low. A chrooted process runs at essentially the same speed as an unchrooted process.

The main costs of chroot come from the additional storage space needed for the chroot environment, and the administrative overhead of setting up and maintaining that environment.

However, these costs are still low compared to virtual machines. A chroot environment typically consumes megabytes of disk space, versus gigabytes for a full VM image. And since it‘s just a directory tree, administering a chroot is simpler than managing a VM.

But if you need more isolation than chroot alone provides, you may want to investigate container technologies like Docker and LXC. These use chroot in combination with other kernel features to provide more complete isolation of memory, processes, and networks. The trade-off is greater complexity and overhead compared to a simple chroot.

Conclusion

While often overshadowed by newer technologies, chroot remains a valuable tool for Linux administrators and developers. Its unique ability to create isolated filesystem environments makes it useful for tasks like repairing broken systems, testing software, running incompatible applications, and securing network services.

Although it has limitations, chroot shines as a lightweight and flexible utility for creating quick and easy isolation. For more than 40 years, it has proven its worth and carved out a niche in the Linux ecosystem. Its ongoing survival alongside more modern alternatives is a testament to the usefulness of this original Unix virtualization tool.

So the next time you need to wall off a process or play in a sandbox, consider reaching for chroot. It just might be the magic you need!

Similar Posts