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 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 the below image (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 only those which are limited at the top-level by a parameter passed to the chroot()
.
Let’s create the 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) {
char t_cwd[PATH_MAX];
getcwd(t_cwd, sizeof(t_cwd));
printf("Current dir before chroot(): %s\n", t_cwd);
chdir("/tmp/chroot/");
if (chroot("/tmp/chroot/") != 0) {
perror("chroot /tmp/chroot/");
return 1;
}
char a_cwd[PATH_MAX];
getcwd(a_cwd, sizeof(a_cwd));
printf("Current dir after chroot(): %s\n", a_cwd);
struct dirent *de;
DIR *dr = opendir("/");
while ((de = readdir(dr)) != NULL)
printf("%s\n", de->d_name);
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 you 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
Similar Posts