
chroot() был добавлен в Version 7 Unix в 1979 году и используется для изоляции файловой системы.
По сути, является предшественником вообще всей идеи нынешней контейнеризации, только в современных системах используются namespaces и cgroups, а раньше применяли chroot для создания изолированного от хоста рабочего окружения, которое могло использоваться для тестирования.
Собственно, ch и root и является «аббревиатурой» от change и root — изменить корень (файловой системы).
Содержание
Дерево файловой системы Linux
Структура каталогов в Linux как правило выглядит следующим образом (см. 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
[/simterm]
chroot() позволяет создать «вложенное» дерево системы, которое можно проиллюстрировать так:
Ниже рассмотрим сам системный вызов chroot() на примере кода на Си, и утилиту chroot и её применение в операционной системе.
chroot() — системный вызов
Задача chroot — ограничить доступ к файловой системе, изменив корневую файловую систему для процесса.
Вместо всего дерева каталогов:
[simterm]
$ 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
[/simterm]
Процесс увидит только те, которые ограничены на верхнем уровне параметром, заданным при вызове chroot().
Создадим каталог для примеров:
[simterm]
$ mkdir -p /tmp/chroot/{1,2,3,4}
[/simterm]
И используем такой код:
#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;
}
Тут мы:
- проверяем путь к текущему каталогу до вызова
chroot() - выполняем
chroot() - проверяем текущий путь ещё раз
- получаем содержимое «корневого» каталога
- и пробуем открыть файл
/etc/passwd, который существует в «реальной» системе
Собираем:
[simterm]
$ gcc chroot_example.c -o chroot_example
[/simterm]
Проверяем:
[simterm]
$ 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
[/simterm]
Сам системный вызов chroot() определяется в open.c:
SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
return ksys_chroot(filename);
}
И возвращает 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;
}
Которая в свою очередь и вызывает set_fs_root() для процесса:
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);
}
Хорошое описание по структуре системных вызовов есть тут>> и тут>>>.
Утилита chroot
Для создания изолированного пространства в Linux можно использовать утилиту chroot:
[simterm]
$ 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
[/simterm]
Создадим каталог для нашей «клетки» (jail во FreeBSD является именно «продвинутым» развитием chroot) с изолированной файловой системой:
[simterm]
$ cd /tmp/ $ mkdir changed_root
[/simterm]
Собственно утилита chroot и выполнит системный вызов chroot() — проверяем с strace:
[simterm]
$ 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 +++
[/simterm]
Ошибка ‘/bin/bash’: No such file or directory вызвана тем, что в новом окружении, очевидно, нет каталога /bin и исполняемого файла bash.
Аналогично будет выдана ошибка на любую другую программу:
[simterm]
[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
[/simterm]
Попробуем исправить её — создадим каталог /bin в нашем /tmp/changed_root, и скопируем исполняемый файл bash с «хоста» в этот «контейнер»:
[simterm]
[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
[/simterm]
Пробуем ещё раз:
[simterm]
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root /bin/bash chroot: failed to run command ‘/bin/bash’: No such file or directory
[/simterm]
Okay.
Но сейчас ошибка вызвана уже тем, что нет необходимых библиотек, хотя утилита chroot просто не умеет об этом нормально сообщить, и выводит то же самое сообщение.
Проверяем зависимости bash:
[simterm]
[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)
[/simterm]
Создаём каталоги /lib и /lib64 в нашем новом корневом каталоге:
[simterm]
[setevoy@setevoy-arch-work /tmp] $ mkdir changed_root/usr/lib changed_root/lib64
[/simterm]
И копируем библиотеки:
[simterm]
[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
[/simterm]
Пробуем ещё раз:
[simterm]
[setevoy@setevoy-arch-work /tmp] $ sudo chroot changed_root/ bash-5.0#
[/simterm]
Тут у нас будут работать встроенные функции самого bash:
[simterm]
bash-5.0# pwd /
[/simterm]
Но, очевидно, что не будут работать внешние утилиты:
[simterm]
bash-5.0# ls -l bash: ls: command not found
[/simterm]
Что исправляется тем же образом, что мы применяли для запуска bash:
[simterm]
[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/
[/simterm]
Остальные файлы библиотек у нас уже есть — проверяем работу ls теперь:
[simterm]
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
[/simterm]
Ссылки по теме
- 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






