Skip to main content

Taking a deeper dive into Linux chroot jails

Dive deeper into the chroot command and learn how to isolate specific services and specific users.
Taking a deeper dive into chroot jails

In part one, How to setup Linux chroot jails, I covered the chroot command and you learned to use the chroot wrapper in sshd to isolate the sftpusers group. When you edit sshd_config to invoke the chroot wrapper and give it matching characteristics, sshd executes certain commands within the chroot jail or wrapper. You saw how this technique could potentially be useful to implement contained, rather than secure, access for remote users.

Expanded example

I'll start by expanding on what I did before, partly as a review. Start by setting up a custom directory for remote users. I'll use the sftpusers group again.

Start by creating the custom directory that you want to use, and setting the ownership:

# mkdir -p /sftpusers/chroot
# chown root:root /sftpusers/chroot

This time, make root the owner, rather than the sftpusers group. This way, when you add users, they don't start out with permission to see the whole directory.

Next, create the user you want to restrict (you need to do this for each user in this case), add the new user to the sftpusers group, and deny a login shell because these are sftp users:

# useradd sanjay -g sftpusers -s /sbin/nologin
# passwd sanjay

Then, create the directory for sanjay and set the ownership and permissions:

# mkdir /sftpusers/chroot/sanjay
# chown sanjay:sftpusers /sftpusers/chroot/sanjay
# chmod 700 /sftpusers/chroot/sanjay

Next, edit the sshd_config file. First, comment out the existing subsystem invocation and add the internal one:

#Subsystem sftp /usr/libexec/openssh/sftp-server
Subsystem sftp internal-sftp

Then add our match case entry:

Match Group sftpusers
ChrootDirectory /sftpusers/chroot/
ForceCommand internal-sftp
X11Forwarding no
AllowTCPForwarding no

Note that you're back to specifying a directory, but this time, you have already set the ownership to prevent sanjay from seeing anyone else's stuff. That trailing / is also important.

Then, restart sshd and test:

[skipworthy@milo ~]$ sftp sanjay@showme
sanjay@showme's password:
Connected to sanjay@showme.
sftp> ls
sftp> pwd
Remote working directory: /
sftp> cd ..
sftp> ls
sftp> touch test
Invalid command.

So. Sanjay can only see his own folder and needs to cd into it to do anything useful.

Isolating a service or specific user

Now, what if you want to provide a usable shell environment for a remote user, or create a chroot jail environment for a specific service? To do this, create the jailed directory and the root filesystem, and then create links to the tools and libraries that you need. Doing all of this is a bit involved, but Red Hat provides a script and basic instructions that make the process easier.

Note: I've tested the following in Red Hat Enterprise Linux 7 and 8, though my understanding is that this capability was available in Red Hat Enterprise Linux 6. I have no reason to think that this script would not work in Fedora, CentOS or any other Red Hat distro, but your mileage (as always) may vary.

First, make your chroot directory:

# mkdir /chroot

Then run the script from yum that installs the necessary bits:

# yum --releasever=/ --installroot=/chroot install iputils vim python

The --releasever=/ flag passes the current local release info to initialize a repo in the new --installroot, defines where the new install location is. In theory, you could make a chroot jail that was based on any version of the yum or dnf repos (the script will, however, still start with the current system repos). 

With this tool, you install basic networking utilities like the VIM editor and Python. You could add other things initially if you want to, including whatever service you want to run inside this jail. This is also one of the cool things about yum and dependencies. As part of the dependency resolution, yum makes the necessary additions to the filesystem tree along with the libraries. It does, however, leave out a couple of things that you need to add next. I'll will get to that in a moment.

By now, the packages and the dependencies have been installed, and a new GPG key was created for this new repository in relation to this new root filesystem. Next, mount your ephemeral filesystems:

# mount -t proc proc /chroot/proc/
# mount -t sysfs sys /chroot/sys/

And set up your dev bindings:

# mount -o bind /dev/pts /chroot/dev/pts
# mount -o bind /dev/pts /chroot/dev/pts

Note that these mounts will not survive a reboot this way, but this setup will let you test and play with a chroot jail environment.

Now, test to check that everything is working as you expect:

# chroot /chroot
bash-4.2# ls
bin dev home lib64 mnt proc run srv tmp var boot etc lib media opt root sbin sys usr

You can see that the filesystem and libraries were successfully added:

bash-4.2# pwd
bash-4.2# cd ..

From here, you see the correct root and can't navigate up:

bash-4.2# exit

Now you've exited the chroot wrapper, which is expected because you entered it from a local login shell as root. Normally, a remote user should not be able to do this, as you saw in the sftp example:

[skipworthy@milo ~]$ ssh root@showme
root@showme's password:
[root@showme1 ~]# chroot /chroot

Note that these directories were all created by root, so that's who owns them. Now, add this chroot to the sshd_config, because this time you will match just this user:

Match User leo
ChrootDirectory /chroot

Then, restart sshd.

You also need to copy the /etc/passwd and /etc/group files from the host system to the /chroot directory:

[root@showme1 ~]# cp -vf /etc/{passwd,group} /chroot/etc/

Note: If you skip the step above, you can log in, but the result will be unreliable and you'll be prone to errors related to conflicting logins

Now for the test:

[skipworthy@milo ~]$ ssh leo@showme
leo@showme's password:
Last login: Thu Jan 30 19:35:36 2020 from
-bash-4.2$ ls
-bash-4.2$ pwd

It looks good. Now, can you find something useful to do? Let's have some fun:

[root@showme1 ~]# yum --releasever=/ --installroot=/chroot install httpd

You could drop the releasever=/, but I like to leave that in because it leaves fewer chances for unexpected results.

[root@showme1 ~]# chroot /chroot
bash-4.2# ls /etc/httpd
conf conf.d conf.modules.d logs modules run
bash-4.2# python
Python 2.7.5 (default, Aug 7 2019, 00:51:29)

So, httpd is there if you want it, but just to demonstrate you can use a quick one-liner from Python, which you also installed:

bash-4.2# python -m SimpleHTTPServer 8000
Serving HTTP on port 8000 ...

And now you have a simple webserver running in a chroot jail. In theory, you can run any number of services from inside the chroot jail and keep them 'contained' and away from other services, allowing you to expose only a part of a larger resource environment without compromising your user’s experience.

New to Linux containers? Download the Containers Primer and learn the basics.

Topics:   Linux   Containers  
Author’s photo

Glen Newell

Glen Newell has been solving problems with technology for 20 years. As a Systems Engineer and administrator, he’s built and managed servers for Web Services, Healthcare, Finance, Education, and a wide variety of enterprise applications. More about me

On Demand: Red Hat Summit 2021 Virtual Experience

Relive our April event with demos, keynotes, and technical sessions from
experts, and sign up to attend breakout sessions June 15–16.

Related Content