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