What is: chroot – the system call and utility in Linux
Arseny Zinchenko
Posted on March 23, 2019
chroot()
was added to the Version 7 Unix in 1979 and used for filesystem isolation.
In fact, it’s the predecessor of the whole current containerization idea, just now there are namespaces and cgroups are used while earlier chroot was used to create an environment which is isolated from a host and can be used for testing purposes, for example.
Also, the ch and root is an “abbreviation” from the change and root (of a filesystem).
Linux file system tree
The directories tree in Linux usually looks like next (see also Filesystem Hierarchy Standard):
$ tree -d -L 1 /
/
├── bin -> usr/bin
├── boot
├── data
├── dev
├── etc
├── home
├── lib -> usr/lib
├── lib64 -> usr/lib
├── lost+found
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -> usr/bin
├── srv
├── sys
├── tmp
├── usr
└── var
chroot()
allows to create a nested filesystem tree which can be demonstrated with the next picture:
Below we will take a closer look at the chroot()
with some C code example, and on the chroot
utility and its usage in an operating system.
chroot()
– the Linux system call
So, chroot is intended to limit access to a filesystem by changing its root.
I.e. instead of directories structure like this:
$ tree -d -L 1 /
/
├── bin -> usr/bin
├── boot
├── data
...
├── tmp
├── usr
└── var
A process will see the only those which are limited at the top-level by a parameter passed to the chroot()
.
Let’s create next directories to be used for example:
$ mkdir -p /tmp/chroot/{1,2,3,4}
And let’s write the next code in C:
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
int main(void) {
// check path before chroot()
char t_cwd[PATH_MAX];
getcwd(t_cwd, sizeof(t_cwd));
printf("Current dir before chroot(): %s\n", t_cwd);
// do chroot()
chdir("/tmp/chroot/");
if (chroot("/tmp/chroot/") != 0) {
perror("chroot /tmp/chroot/");
return 1;
}
// check path path after chroot()
char a_cwd[PATH_MAX];
getcwd(a_cwd, sizeof(a_cwd));
printf("Current dir after chroot(): %s\n", a_cwd);
// point dr struct to the "root"
struct dirent *de;
DIR *dr = opendir("/");
// run readdir() and list "root"'s content
while ((de = readdir(dr)) != NULL)
printf("%s\n", de->d_name);
// try to open /etc/passwd from a "host" filesystem
FILE *f;
f = fopen("/etc/passwd", "r");
if (f == NULL) {
perror("/etc/passwd");
return 1;
} else {
char buf[100];
while (fgets(buf, sizeof(buf), f)) {
printf("%s", buf);
}
}
return 0;
}
Here it will:
- check the current path before calling
chroot()
- call the
chroot()
- check current path again
- get the “root” content
- try to open the
/etc/passwd
file which is present on a “real” filesystem
Build it:
$ gcc chroot_example.c -o chroot_example
And run to check (with sudo
as chroot()
can be used by root only):
$ sudo ./chroot_example
Current dir before chroot(): /home/setevoy/Scripts/C
Current dir after chroot(): /
.
..
4
3
2
1
/etc/passwd: No such file or directory
The chroot()
itself is defined in the kernel’s open.c
file:
SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
return ksys_chroot(filename);
}
And will return the ksys_chroot()
:
int ksys_chroot(const char __user *filename)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
retry:
error = user_path_at(AT_FDCWD, filename, lookup_flags, &path);
if (error)
goto out;
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;
error = -EPERM;
if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT))
goto dput_and_out;
error = security_path_chroot(&path);
if (error)
goto dput_and_out;
set_fs_root(current->fs, &path);
error = 0;
dput_and_out:
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out:
return error;
}
Which in its turn will call the set_fs_root()
for a process:
void set_fs_root(struct fs_struct *fs, const struct path *path)
{
struct path old_root;
path_get(path);
spin_lock(&fs->lock);
write_seqcount_begin(&fs->seq);
old_root = fs->root;
fs->root = *path;
write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock);
if (old_root.dentry)
path_put(&old_root);
}
You can find good syscalls description here>> and here>>>.
chroot
– the Linux utility
To create an isolated space in Linux you can use chroot
utility:
$ which chroot
/usr/bin/chroot
$ file /usr/bin/chroot
/usr/bin/chroot: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=f3861107940247a67dbbf6343fa5ff1c1c70305c, stripped
Let’s create a catalog for our “jail” (FreeBSD's jail
is an advanced successor of UNIX's chroot)) with an isolated filesystem:
$ cd /tmp/
$ mkdir changed_root
Actually, the chroot
utility will call the same chroot()
system call – let’s check it with the strace
:
$ sudo strace -e trace=chroot chroot changed_root/
chroot("changed_root/") = 0
chroot: failed to run command ‘/bin/bash’: No such file or directory
+++ exited with 127 +++
The ‘/bin/bash’: No such file or directory error is caused by the fact that in this new environment there is no /bin
directory and the bash
executable.
Similarly, such an error will be returned if try to call any other program::
[setevoy@setevoy-arch-work /tmp] $ which ls
/usr/bin/ls
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root /usr/bin/ls
chroot: failed to run command ‘/usr/bin/ls’: No such file or directory
Let’s fix it – create /bin
directory in our /tmp/changed_root
and copy bash
file from a “host ” inside of this “container”:
[setevoy@setevoy-arch-work /tmp] $ mkdir changed_root/bin
[setevoy@setevoy-arch-work /tmp] $ cp /bin/bash changed_root/bin
[setevoy@setevoy-arch-work /tmp] $ file changed_root/bin/bash
changed_root/bin/bash: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=357034d1736cd97d2c8f8347045250dbd0de998e, stripped
Try again:
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory
Okay.
But now it’s caused because there are no necessary libs – chroot
just can’t tell about this.
Check the bash
‘s dependencies with the ldd
:
[setevoy@setevoy-arch-work /tmp] $ ldd /bin/bash
linux-vdso.so.1 (0x00007ffe37f16000)
libreadline.so.8 => /usr/lib/libreadline.so.8 (0x00007f39b13d2000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f39b13cd000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f39b1209000)
libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007f39b119a000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f39b153f000)
Create two more catalogs – /lib
and /lib64
in our new working directory:
[setevoy@setevoy-arch-work /tmp] $ mkdir changed_root/usr/lib changed_root/lib64
And copy libs files:
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libreadline.so.8 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libdl.so.2 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libc.so.6 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libncursesw.so.6 changed_root/usr/lib/
[setevoy@setevoy-arch-work /tmp] $ cp /lib64/ld-linux-x86-64.so.2 changed_root/lib64
Run the chroot
again:
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root/
bash-5.0#
Now we have bash
running here and all its built-in functions:
bash-5.0# pwd
/
But obviously – no other external utils will work here:
bash-5.0# ls -l
bash: ls: command not found
And this can be fixed in the same way as we did it for the bash
:
[setevoy@setevoy-arch-work /tmp] $ which ls
/usr/bin/ls
[setevoy@setevoy-arch-work /tmp] $ cp /usr/bin/ls changed_root/bin/
[setevoy@setevoy-arch-work /tmp] $ ldd /usr/bin/ls
linux-vdso.so.1 (0x00007ffdebbf5000)
libcap.so.2 => /usr/lib/libcap.so.2 (0x00007fa5b147d000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fa5b12b9000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fa5b14d8000)
[setevoy@setevoy-arch-work /tmp] $ cp /usr/lib/libcap.so.2 changed_root/usr/lib/
Other libs already are copied so let’s run ls
again:
bash-5.0# /bin/ls -l /
total 0
drwxr-xr-x 2 1000 1000 80 Mar 22 11:45 bin
drwxr-xr-x 2 1000 1000 120 Mar 22 11:37 lib
drwxr-xr-x 2 1000 1000 60 Mar 22 11:38 lib64
drwxr-xr-x 3 1000 1000 60 Mar 22 11:39 usr
See also
- Containerization Mechanisms: Namespaces
- Linux Virtualization – Chroot Jail
- chroot, cgroups and namespaces — An overview
- The somewhat surprising history of chroot()
- chroot(2) – Linux man page
- Anatomy of a system call, part 1
Similar posts
- 03/03/2019 What is: hard link, symlink, and inode in Linux? (0)
- 03/10/2018 What is: Linux namespaces, примеры PID и Network namespaces (0)
- 03/18/2017 Linux: LVM – уменьшить home, увеличить root (1)
- 10/20/2018 Linux: увеличение размера раздела без LVM с сохранением данных на ext4 (0)
Posted on March 23, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.