What is:  chroot – the system call and utility in Linux
Tags: linux, kernel, systemcalls
Published: 2019-03-23 09:49:32

---
title: What is:  chroot – the system call and utility in Linux
published: true
tags: Linux,kernel,system calls
canonical_url: https://rtfm.co.ua/en/what-is-chroot-the-system-call-and-utility-in-linux/
---

![](https://rtfm.co.ua/wp-content/uploads/2016/01/What-is-it-e1452250201867.jpg)`chroot()` was [added](https://en.wikipedia.org/wiki/Chroot#History) 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](https://rtfm.co.ua/what-is-linux-namespaces-primery-na-c-clone-pid-i-net-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](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard)):

[![](https://rtfm.co.ua/wp-content/uploads/2019/03/PR-1719-2.png "What is: chroot в Linux")](https://rtfm.co.ua/wp-content/uploads/2019/03/PR-1719-2.png)

```
$ 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:

[![](https://rtfm.co.ua/wp-content/uploads/2019/03/PR-1719-1.png "What is: chroot в Linux")](https://rtfm.co.ua/wp-content/uploads/2019/03/PR-1719-1.png)

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`](https://elixir.bootlin.com/linux/v5.0.3/source/fs/open.c#L519) file:

```
SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
  return ksys_chroot(filename);
}
```

And will return the [`ksys_chroot()`](https://elixir.bootlin.com/linux/v5.0.3/source/fs/open.c#L486):

```
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()`](https://elixir.bootlin.com/linux/latest/source/fs/fs_struct.c#L14) 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>>](https://lwn.net/Articles/604287/) and [here>>>](https://stackoverflow.com/a/28128030/2720802).

* * *

### `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`](https://en.wikipedia.org/wiki/FreeBSD_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`](https://rtfm.co.ua/linux-strace-otslezhivaem-vypolnenie-processa/):

```
$ 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`](http://man7.org/linux/man-pages/man1/ldd.1.html):

```
[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](https://blog.selectel.com/containerization-mechanisms-namespaces/)
- [Linux Virtualization – Chroot Jail](https://www.geeksforgeeks.org/linux-virtualization-using-chroot-jail/)
- [chroot, cgroups and namespaces — An overview](https://itnext.io/chroot-cgroups-and-namespaces-an-overview-37124d995e3d)
- [The somewhat surprising history of chroot()](https://utcc.utoronto.ca/~cks/space/blog/unix/ChrootHistory)
- [chroot(2) – Linux man page](https://linux.die.net/man/2/chroot)
- [Anatomy of a system call, part 1](https://lwn.net/Articles/604287/)


### Similar posts
  
- <small>03/03/2019</small> [What is: hard link, symlink, and inode in Linux?](https://dev.to/setevoy/what-is-hard-link-symlink-and-inode-in-linux-1399)<small> (0)</small>  
- <small>03/10/2018</small> [What is: Linux namespaces, примеры PID и Network namespaces](https://rtfm.co.ua/what-is-linux-namespaces-primery-na-c-clone-pid-i-net-namespaces/)<small> (0)</small>  
- <small>03/18/2017</small> [Linux: LVM – уменьшить home, увеличить root](https://rtfm.co.ua/linux-lvm-umenshit-home-uvelichit-root/)<small> (1)</small>  
- <small>10/20/2018</small> [Linux: увеличение размера раздела без LVM с сохранением данных на ext4](https://rtfm.co.ua/linux-uvelichenie-razmera-razdela-bez-lvm-s-soxraneniem-dannyx-na-ext4/)<small> (0)</small>